The Falcon Programming Language in a Nutshell

by Giancarlo Niccolai

In late 2003, I had the problem of making business-critical decisions and performing maintenance actions in real time, analyzing data that was passing through the servers I was charged with controlling. Data throughput was on the order of thousands of messages per second, each of which was made of complex structures and possibly nested maps, whose size was measured in kilobytes.

The applications in charge of those controls were already almost complete, and they were heavily multithreaded by design. The only thing missing was the logic-processing engine. That would have been the perfect job for a scripting language, but the memory, CPU, threading, responsiveness and safety constraints seemed to be a hard match.

After testing the available solutions, I decided to try to solve the problem by writing a scripting language from the ground up, taking into consideration those design constraints. After the decision was made to move forward, useful items commonly found missing from other scripting languages were added to the design specification. So, Falcon mainly was designed from the beginning to meet the following requirements:

  • Rapidly exchange (or use directly) complex data with C++.

  • Play nice with applications (especially with MT applications) and provide them with ways to control the script execution dynamically.

  • Provide several programming paradigms under the shroud of simple, common grammar.

  • Provide native multilanguage (UTF) support.

  • Provide a simple means to build script-driven applications, easily and efficiently integrated with third-party libraries.

As soon as I was able to script the applications that drove the initial development and meet these ambitious targets in terms of overall performance, I realized that Falcon may be something useful and interesting for others also, so I went open source.

The project is now reaching its final beta release phase, and Falcon has become both a standalone scripting language and a scripting engine that can drive even the most demanding applications.

The Falcon programming language now is included with many high-profile distributions, including Fedora, Ubuntu, Slackware, Gentoo and others. If your distribution doesn't include it yet, you can download it from www.falconpl.org, along with user and developer documentation.

Falcon currently is ported for Linux (32- and 64-bit), Win32 and Solaris (Intel). Older versions work on Mac OS X and FreeBSD. We are porting the newer version shortly, and a SPARC port also should be ready soon.

The Language

Falcon is an untyped language with EOL-separated statements and code structured into statement/end blocks. It supports integer math (64-bit) natively, including bit-field operators, floating-point math, string arrays, several types of dictionaries, lists and MemBuffers (shared memory areas), among other base types and system classes.

Morphologically, Falcon doesn't break established conventions, for example:

function sayHello()
  printl( "Hello world!")
end

// Main script:
sayHello()

You can run this script by saving it in a test file and feeding it into Falcon via stdin, or by launching it like this:


$ falcon <scriptname.fal> [parameters]

We place great emphasis on the multiparadigm model. Falcon is based on an open coding approach that seamlessly merges procedural, object-oriented, functional and message-oriented programming. We're also adding tabular programming, sort of a multilayer OOP, but we don't have the space to discuss that here. Each paradigm we support is generally a bit “personalized” to allow for more comfortable programming and easier mingling with other paradigms.

Falcon Procedural Programming

Falcon procedural programming is based on function declaration and variable parameters calls. For example:

function checkParameters( first, second, third )
  > "------ checkParameters -------"
  // ">" at line start is a short for printl
  if first
     > "First parameter: ", first
  end

  // ... and single line statements
  // can be shortened with ":"
  if second: > "Second parameter: ", second
  if third: > "Third parameter: ", third
  > "------------------------------"
end

// Main script:
checkParameters( "a" )
checkParameters( "b", 10 )
checkParameters( "c", 5.2, 0xFF )

You can use RTL functions to retrieve the actual parameters passed to functions (or methods). Values also can be passed by reference (or alias), and functions can have static blocks and variables:

function changer( param )
  // a static initialization block
  static
     > "Changer initialized."
     c = 0
  end

  c++
  param = "changed " + c.toString() + " times."
end

// Main script:
param = "original"
changer( param )
> param           // will be still original
changer( $param ) // "$" extracts a reference
> param           // will be changed
p = $param        // taking an alias...
changer( $param ) // and sending it
> p               // still referring "param"

Again, RTL functions can be used to determine whether a parameter was passed directly or by reference.

The strict directive forces the variables to be declared explicitly via the def keyword:

directive strict=on

def alpha = 10 // we really meant to declare alpha
test( alpha )  // call before declaration is allowed

function test( val )
  local = val * 2   // error: not declared with def!
  return local
end

Falcon has a powerful statement to traverse and modify sequences. The following example prints and modifies the values in a dictionary:

dict = [ "alpha" => 1,
  "beta" => 2,
  "gamma" => 3,
  "delta" => 4,
  "fi" => 5 ]

for key, value in dict
  // Before first, ">>" is a short for "print"
  forfirst: >> "The dictionary is: "

  // String expansion operator "@"
  >> @ "$key=$value"

  .= "touched"

  formiddle: >> ", "
  forlast: > "."
end

// see what's in the dictionary now:
inspect( dictionary )

Notice the string expansion operator in the above code. Falcon provides string expansion via naming variables and expressions and applying an explicit @ unary operator. String expansions can contain format specifiers, like @ "$(varname:r5)", which right-justifies in five spaces, but a Format class also is provided to cache and use repeated formats.

Both user-defined collections and language sequences provide iterators that can be used to access the list traditionally. Functional operators such as map, filter and reduce also are provided.

Falcon Object-Oriented Programming

A Falcon script can define classes and instantiate objects from them, create singleton objects (with or without base classes) and apply transversal attributes to the instances. The provides keyword checks for properties being exposed by the instances:

// A class
class Something( initval1, initval2 )
  // Simple initialization can be done directly
  prop1 = initval1
  prop2 = nil

  // init takes the parameters of the class
  // and performs more complex initialization
  init
     self.prop2 = initval
     > "Initializer of class Something"
  end

  function showMe()
     > "Something says: ", self.prop1, "; ", self.prop2
  end
end

// A singleton instance.
object Alone
  function whoAmI()
     > "I am alone"
  end
end

// an instance
instance = Something( "one", "two" )
instance.showMe()

//"Alone" is already an instance
if Alone provides whoAmI
  Alone.whoAmI()
end

Falcon has a Basic Object Model (BOM), which is available in all the items. Objects and classes can override some methods. For example, passing an item to the > print operator causes its toString BOM method to be called, and that can be overridden as follows:

object different
  function toString()
     return "is different..."
  end
end

> "the object... ", different

Falcon supports multiple inheritance, but it disambiguates it by forcing inheritance initialization and priority, depending on the order of the inheritance declarations.

Classes also support static members that can be shared between objects of the same class and methods with static blocks that can work as class-wide initializers. Methods can be retrieved and also called directly from classes when they don't need to access the self object, providing the semantic of C++/Java/C# static methods.

It is possible to merge normal procedures with methods by assigning procedures to properties:

function call_me()
  if self and self provides my_name
     > self.my_name
  else
     > "Sorry, you didn't call me right."
  end
end

object test
  prop1 = nil
  my_name = "I am a test!"

  function hello()
     > "Hello world from ", self.my_name
  end
end

// normal calls
call_me()

// using the procedure as a method
test.prop1 = call_me
test.prop1()

// or a method as a procedure
proc = test.hello
test.my_name = "a renamed thing"

// see: proc will dynamically use the right "self"
proc()
Attributes

Attributes are binary properties that can be either present or not present for a specific instance or object, regardless of its class.

Attributes have a great expressive power, and in Falcon, they indicate what an object is, what it has and what it belongs to, depending on the context. For example, we can define a ready attribute that indicates the objects ready for elaboration:

// declaring an attribute "ready"
attributes: ready

class Data( name )
  name = name

  function process()
     > "Processing ", self.name, "..."
  end
end

// create 10 processors
processors = []
for i in [0:10]
  processors += Data(i)
  if i > 5: give ready to processors[i]
end

// work with the ready ones
for d in ready
  d.process()
end

RTL provides several functions to manipulate attributes.

The has and hasnt operators check for the presence of an attribute. For example:

attributes: ready
class SomeClass
  //... other class data ...
  // born ready!
  has ready
end

item = SomeClass()
if item has ready
  > "Item was born ready!"
end
Functional Programming

The base construct of Falcon functional programming is the callable sequence, also known as Sigma. At the moment, the only sequence supported is the array, but other types of sequences (such as lists) should be supported soon.

Basically, a Sigma is a delayed call that can work like this:

function test( a, b, c )
  > "Parameters:"
  > a
  > b
  > c
end

// direct
test( "one", "two", "three" )

// indirect
cached = [ test, "four", "five", "six" ]
cached()

The call respects the procedural paradigm (variable parameters), and the array is still a normal vector that can be accessed and modified through the standard language operators and RTL functions.

This delayed call is still not a full “functional context evaluation”. The proper functional evaluation process is called Sigma reduction. It recursively resolves Sigmas from inner to outer and left to right when they are at the same level, substituting them with their return value.

Special functions known by the VM as Etas start and control functional evaluation; the simplest Eta function is eval(), which initializes and performs a basic Sigma reduction.

For example, the expression “(a+b) * (c+d)” can be written in a Lisp-like sequence:

function add( a, b ): return a+b
function mul( a, b ): return a*b

> "(2+3)*(4+5)= ", eval(.[mul .[add 2 3] .[add 4 5]])

The .[] notation is shorthand for array declarations whose elements are separated by white space instead of an explicit “,”.

Falcon RTL comes with a rich set of Etas, such as iff (functional if), cascade (which joins more standard calls in a single sequence), floop and times (different styles of functional loops), map, filter, reduce and many others.

Functional sequences can be parameterized through closure and references. For example, the above example can be made parametric in this way:

// add and mul as before...
function evaluator( a, b, c, d )
  return .[eval .[mul .[add a b] .[add c d]]]
end

tor = evaluator( 2,3,4,5 )
> "(2+3)*(4+5)= ", tor()

Traditional functional operators, such as map, filter and reduce, are supported, but the out-of-band item system expands their functionality.

Out-of-band items are items marked with a special flag through the oob() function. Although they are normal items in every other aspect, this special mark indicates that they hold unexpected, special or somehow extraordinary value traveling through functional sequences. Although this is not a direct support for monadic calculus, monads can be implemented at the script (or binary module) level through this mechanism.

Falcon also supports Lambda expressions and nested functions.

We currently are working on some extensions to make Sigmas even more configurable—for example, parameter naming (similar to Lisp field naming) and access from the outside to the unbound variables used in the sequence.

Falcon functional programming merges with OOP, as Sigmas can be set as object properties, and object methods can be used as Kappas (Sigma-callable header symbols):

object SomeObj
  a_property = 10
  function myProp( value )
     return self.a_property * value
  end
end

> "5*10=", eval( .[SomeObj.myProp 5] )
Message-Oriented Programming

Because attributes are a very flexible means of declaring dynamic Boolean properties and a set of “similar” objects, we have used them as the main driver for message-oriented programming.

Basically, objects and instances with a certain attribute can receive messages built for that attribute's holders. The target objects will receive messages through a method named after the attribute.

The rest of the message-oriented programming support is built on this basic mechanism—message priority queues, automatic event dispatching, inter-agent messaging services and so on.

As a minimally meaningful sample would require 50–100 lines (messages are among many agents), we'll skip it here, and try to explain what's nice about message-oriented programming.

The main point is that you can summon remote execution in unknown objects willing to participate in the message without direct knowledge of them. Messages can carry anything, including methods or whole Sigma sequences for remote execution in foreign objects.

Messages don't even need to be point to point. The message receivers cooperatively can form a reply by adding something to the forming return value. For example, a central arbiter can send a “register” message, and every object willing to register can add itself to a queue of items willing to register in a queue traveling with the message. The queue even can contain target register procedures to be invoked by the arbiter once the register message processing is complete.

An example that easily displays the power of this paradigm is the implementation of an assert/retract/query mechanism.

A central object registering assertion listens for messages of these three types. Any part of the program then can send an assertion, a name bound with executable code, which can be anything, including code generated dynamically or loaded from plugins. Items in need of some algorithm can then query the system (sending a query message) asking for it to be provided.

If available, the code is returned, and it can be invoked by the agents in need of it.

You also can do this through a global dictionary, where code is associated with algorithm names, but that approach requires all users of the code to know the central dictionary and to interact with it. Asking a smoke cloud to take care of arbitrating the code repository is easier, simpler, more modular, more flexible and allows for central checking and managing. When that comes at no additional performance cost because of the language integration, it's an obvious advantage.

Some Things We Didn't Say

Stuffing all the things that Falcon can do for you into a short article is not easy, mainly because the things some will find useful may be useless for others. We didn't discuss co-routines, the indirect operator, the upcoming tabular programming, the reflexive compiler, the Falcon Template Document system (our active server pages), the multithreading module or many other things we've done and are doing to make Falcon the best language we can.

A DBI module already is available for interacting directly with MySQL, Postgre and SQLite3, and ODBC and Firebird will be ready soon too. A module for SDL is standing, and we're starting to work on a generic binding system to provide full support for Qt, GTK, GD2 and many other libraries.

We are still a small group, and the language specifications are still open. So, if this project interests you, and you want to add some binding or test some paradigm/language idea, we welcome you.

Giancarlo Niccolai was born in Bologna, Italy, and he graduated in 1992 in IT at Pistoia. He currently works as IT designer and consultant for software providers of the most important financial institutions on the continent. He previously has worked with many open-source projects and consistently participates in the xHarbour (XBase compiler) Project. He has expertise in several programming languages and deep interests in natural languages and linguistic/physiology sciences.

Load Disqus comments

Firstwave Cloud