Nasal 1.0 review
DownloadNasal is a language that I wrote for use in a personal project
|
|
Nasal is a language that I wrote for use in a personal project. Ostensibly it was because I was frustrated with the dearth of small-but-complete embeddable scripting languages, but of course I really wrote it because it was fun.
It is still young and incomplete in a few places, but is under active development and has been integrated as the extension language for the FlightGear simulator.
Documentation is still sparse. There is a design document available, which talks at length about the "why's" behind the design of Nasal and includes documentation for the built-in library functions.
More useful to the experienced programmer is the tutorial-style sample code, which explains and demonstrates all the syntax features of the language.
Like perl, python and javascript, nasal uses vectors (expandable arrays) and hash tables as its native data format. This is a well-understood idiom, and it works very well. I felt no need to rock the boat here.
Like perl, and unlike everything else, nasal combines numbers and strings into a single "scalar" datatype. No conversion needs to happen in user code, which simplifies common string handling tasks.
Like perl, but unlike python, hash keys must by scalars in nasal. Python supports a "tuple" constant type that can be used as well, but there is no equivalent in nasal (you can't use vectors as keys because they might change after the insertion).
Like perl and python, nasal uses a # character to indicate and end-of-line comment. There is no multiline begin/end comment syntax as in Javascript.
Like perl, nasal functions do not have named parameters. They get their arguments in a vector named "arg", and can extract them however they like. Unlike perl, Nasal takes advantage of this feature to do away with function "declaration" entirly; see below.
Like python, there is no hidden local object scope in a function call. The object on which a method was called is available to a function as a local variable named "me" (python calls this "self" by convention, but because nasal has no declared function arguments, there is no opportunity to change it).
Like perl, "objects" in nasal are simply hash tables. Looking an item up by name in a hash table and extracting a symbol for an object are just different syntax for the same operation (but read on for an important exception):
a["b"] = 1 means the same thing as: a.b = 1
The above paragraph is a minor lie. The "dot" syntax is also the clue to the interpreter to "save" the left hand side as the "me" reference if the expression is used as a function/method call. That is, these expressions are not equivalent (one is a plain function call, the other a method invocation on the object "a"):
a["b"](arg1, arg2) isn't the same as: a.b(arg1, arg2)
Like javascript, nasal lacks a specific "class" syntax for OOP programming. Instead, classes are simply objects. Each object supports a "parents" member array; symbol lookup on the object at runtime bounces to the parents (and the parents' parents) if the symbol is not found in the hash. The parents field is just like any other object field, you can set it however you like and even change it at runtime if you are feeling especially perverse.
Like lisp, javascript and perl, nasal supports lexical closures. This means that the local symbol namespace available to your function when it is assigned remain constant over time. If you don't know what this means, you don't need to care. It is this feature that allows functions to use variables declared in the outer scope when it is defined (e.g. seeing "module" variables).
Like all other scripting languages, functions are just symbols in a namespace, but unlike all other scripting languages, there is no function "declaration" syntax. A function is always an anonymous object (a "lambda," in the parlance), which you assign to a variable in order to use. Like so:
myfunction = func { arg[0] + 1 }
myfunction(1); # returns 2
One annoyance of this feature is that Nasal functions don't have unique internal "names". So a debugging or exception stack trace can only give you a source line number, and not a function name as reference.
Nasal has a straightforward, readable syntax which is closest to javascript among other scripting languages. Like later versions of javascript, it includes has a hash lookup syntax as well as an object field accessor syntax (that is, you can do both a.b and a["b"]).
Unlike python, nasal has a grammar which is not whitespace-sensitive. This doesn't make python hard to write, and it arguably makes it easier to read. But it is different from the way the rest of the world works, and makes python distinctly unsuitable for "inline" environments (consider PHP, Javascript, ASP or in-configuration-file scripts) where it needs to live as a plain old string inside of another program's code or data file.
Nasal garbage collects runtime storage, so the programmer need not worry about manual allocation, or even circular references. The current implementation is a simple mark/sweep collector, which should be acceptable for most applications. Future enhancements will include a "return early" capability for latency-critical applications. The collector can be instructred to return after a certain maximum delay, and be restarted later. Fancy items like generational collectors fail the "small and simple" criteria and are not likely to be included.
Like python, nasal supports exception handling as a first-class language feature, with built-in runtime-inspectable stack trace. Rather like perl, however, there is no special "try" syntax for exception handling, nor inheritance-based catching semantics. Instead, you call a "try" function on another function, and inspect the return value on your own. Code simply calls die with an argument list, which is returned from the closest enclosing try() invocation. Elaborate exception handling isn't really appropriate for embedded scripting languages. [NOTE: this isn't finished yet]
Nasal tries to be stricter than perl. Operations like converting a non-numeric string value to a number, reading or writing past the end of an array or operating on a nil reference, which are generally legal in perl, throw exceptions in nasal. Perl sometimes bends over backwards to do something "reasonable" with your instructions (e.g. what's the boolean truth value of a hash reference?); nasal doesn't try ("error: non-scalar used in boolean context at line 92")
Nasal is very small, very simple, written in ANSI C, and generally an excellent choice for embedded applications. It uses a simple and transparent syntax interpretable by a simple "bracket matching and operator precedence" parser. It does not depend on any third party libraries other than the standard C library. It does not depend on third party tools like (f)lex and yacc/bison. It builds simply and easily, supports a reasonably simple extension API and cohabitates well with other code.
Nasal makes no use of the processor stack when running recursive code. This is important for embedded languages as it provides the ability to "exit early" from a Nasal context. An outside application may have realtime constraints, and Nasal can be instructed to run for only a certain number of "cycles" before returning. Later calls will automatically pick up the interpreter state where it left off.
Nasal provides "minimal threadsafety". Multithreaded operations on Nasal objects are safe in the sense that they cannot crash or corrupt the interpreter. They are not guaranteed to be atomic. In particular, poorly synchronized insertions into containers can "drop" objects into oblivion (which is OK from an interpreter stability standpoint, since the GC will clean them up normally). Race conditions have to be the programmer's problem anyway, this is just another symptom. Garbage collection will block all threads before running. [NOTE: this part is still unimplemented.]
What's New in This Release:
This release contains the updates that have been available in SimGear for some time now.
Important new functionality includes bugfixes, many performance enhancements, a declared function argument syntax, a ternary (?:) operator, indexable and mutable string objects, interpreter thread safety features, and much work to the "standard" library (including stdio, bitfields, Unix system calls, and PCRE regular expressions).
Nasal 1.0 keywords