382 lines
11 KiB
Plaintext
382 lines
11 KiB
Plaintext
# This file is written by Andy Ross, and is protected by GPLv2.0
|
|
|
|
# A no-op function used below to get this file to run. Ignore and read on...
|
|
var dummyFunc = func { 1 }
|
|
|
|
#
|
|
# Literal numbers can be decimal, exponential, or hex constants. All
|
|
# numbers are stored internally as IEEE double-precision values.
|
|
#
|
|
var n1 = 3;
|
|
var n2 = 3.14;
|
|
var n3 = 6.023e23;
|
|
var n3 = 0x123456;
|
|
|
|
#
|
|
# Two identical string literals with different quotes. Double quotes
|
|
# use typical escape sequences. Single quotes treat everything as
|
|
# literal except for embedded single quotes (including embedded
|
|
# whitespace like newlines). Double quotes handle the following
|
|
# C-like escapes: \n \r \t \xnn \"
|
|
#
|
|
var s1 = 'Andy\'s "computer" has a C:\righteous\newstuff directory.';
|
|
var s2 = "Andy's \"computer\" has a C:\\righteous\\newstuff directory.";
|
|
|
|
#
|
|
# Literal lists use square brackets with a comma-separated expression
|
|
# list.
|
|
#
|
|
var list1 = ["a", "b", 1, 2];
|
|
|
|
#
|
|
# Literal hashes (or objects -- same thing) use curlies and colons to
|
|
# separate the key/value pairs. Note that the key can be either a
|
|
# symbol or a literal scalar. Object definitions will typically want
|
|
# to use symbols, lookup tables of other types will be more
|
|
# comfortable with literals.
|
|
#
|
|
var hash1 = { name : "Andy", job : "Hacker" };
|
|
var EnglishEspanol = { "one" : "uno", "two": "dos", "blue" : "azul" };
|
|
|
|
#
|
|
# Both vectors and hashes use square brackets for the lookup operation:
|
|
#
|
|
list1[0] == "a";
|
|
hash1["name"] == "Andy";
|
|
|
|
#
|
|
# A simple function. By default, arguments are passed in the "arg"
|
|
# array, not unlike perl. Note that this is just an assignment of an
|
|
# (anonymous) function argument to the local "log_message" variable.
|
|
# There is no function declaration syntax in Nasal.
|
|
#
|
|
var log_message = func {
|
|
print(arg[0]);
|
|
}
|
|
|
|
#
|
|
# You can also pass named arguments to a function, thus saving the
|
|
# typing and performance costs of extracting them from the arg array.
|
|
#
|
|
var sqrt = dummyFunc;
|
|
var dist = func(x1, y1, x2, y2) {
|
|
var dx = x2-x1;
|
|
var dy = y2-y1;
|
|
return sqrt(dx*dx + dy*dy);
|
|
}
|
|
dist(0,0,1,1); # == sqrt(2)
|
|
|
|
#
|
|
# These arguments can have default values, as in C++. Note that the
|
|
# default value must be a scalar (number, string, function, nil) and
|
|
# not a mutable composite object (list, hash).
|
|
#
|
|
var read = func(bytes, flags=0) { }
|
|
|
|
#
|
|
# Any extra arguments after the named list are placed in the "arg"
|
|
# vector as above. You can rename this to something other than "arg"
|
|
# by specifying a final argument name with an ellipsis:
|
|
#
|
|
var listify = func(elements...) { return elements; }
|
|
listify(1, 2, 3, 4); # returns a list: [1, 2, 3, 4]
|
|
|
|
#
|
|
# You can use a "var" qualifier to make sure that an assigment is made
|
|
# to a local variable, and not one from the enclosing scope. This is
|
|
# good practice in general, although it is not required. Note that
|
|
# this is not a "declaration", just a qualifier on the "=" operator.
|
|
#
|
|
var innerFunc = func {
|
|
for (var dist=0; dist<100; dist += 1) {
|
|
# Does not interfere with the "dist" symbol defined above
|
|
}
|
|
}
|
|
|
|
#
|
|
# Nasal has no "statements", which means that any expression can appear
|
|
# in any context. This means that you can use an if/else clause to do
|
|
# what the ?: does in C. The last semicolon in a code block is
|
|
# optional, to make this prettier.
|
|
#
|
|
var abs = func(n) { if (n<0) { -n } else { n } }
|
|
|
|
#
|
|
# But for those who don't like typing, the ternary operator works like
|
|
# you expect:
|
|
#
|
|
var abs = func(n) { n < 0 ? -n : n }
|
|
|
|
#
|
|
# Nasal supports a "nil" value for use as a null pointer equivalent.
|
|
# It can be tested for equality, matching only other nils.
|
|
#
|
|
var listNode = { data : ["what", "ever"], next : nil };
|
|
|
|
#
|
|
# Nasal's binary boolean operators are "and" and "or", unlike C.
|
|
# unary not is still "!" however. They short-circuit like you expect
|
|
#
|
|
var toggle = 0;
|
|
var a = nil;
|
|
if (a and a.field == 42) {
|
|
toggle = !toggle; # doesn't crash when a is nil
|
|
}
|
|
|
|
#
|
|
# Looping constructs are mostly C-like. The differences are that
|
|
# there is no do {} while (); construct, and there is a foreach, which
|
|
# takes a local variable name as its first argument and a vector as
|
|
# its second.
|
|
#
|
|
var doSomething = dummyFunc;
|
|
|
|
var stillGoing = 0;
|
|
while (stillGoing) { doSomething(); }
|
|
|
|
for (var i=0; i < 3; i = i+1) {
|
|
elem = list1[i];
|
|
doSomething(elem);
|
|
}
|
|
|
|
foreach(var elem; list1) { doSomething(elem) } # Shorthand for above
|
|
|
|
#
|
|
# There is also a "forindex", which is like foreach except that it
|
|
# assigns the index of each element, instead of the value, to the loop
|
|
# variable.
|
|
#
|
|
forindex(var i; list1) { doSomething(list1[i]); }
|
|
|
|
#
|
|
# Define a class object with one method, one field and one "new"
|
|
# contstructor. The "new" function is not special in any way; it
|
|
# simply returns an initialized object with the "parents" field set
|
|
# appropriately. Member functions can get their local object (the
|
|
# equivalent of the "this" pointer in C++) as the "me" variable.
|
|
#
|
|
var Class1 = {};
|
|
|
|
Class1.new = func {
|
|
var obj = { parents : [Class1],
|
|
count : 0 };
|
|
return obj;
|
|
}
|
|
|
|
Class1.getcount = func {
|
|
me.count = me.count + 1;
|
|
return me.count;
|
|
}
|
|
|
|
var c = Class1.new();
|
|
print(c.getcount(), "\n"); # prints 1
|
|
print(c.getcount(), "\n"); # prints 2
|
|
print(c.getcount(), "\n"); # prints 3
|
|
|
|
# Note that member *get* operations recurse into the parents array.
|
|
# But *set* operations always go to the local object. You can't
|
|
# corrupt a parent class via OOP operations on its instances (but you
|
|
# *can* get to it via hand-inspection of the parents arrays).
|
|
var c2 = Class1.new();
|
|
c2.getcount = func { return 12345 }; # custom "derived" function!
|
|
|
|
print(c2.getcount(), "\n"); # prints 12345
|
|
print(c.getcount(), "\n"); # prints 4, Class1.getcount is unchanged
|
|
|
|
#
|
|
# This creates an identical class using alternative syntax.
|
|
#
|
|
var Class2 = {
|
|
new : func {
|
|
var obj = {};
|
|
obj.parents = [Class2];
|
|
obj.count = 0;
|
|
return obj;
|
|
},
|
|
getcount : func {
|
|
me.count = me.count + 1;
|
|
return me.count;
|
|
}
|
|
};
|
|
|
|
#
|
|
# Advanced strings: Nasal strings are always arrays of bytes (never
|
|
# characters: see the utf8 library if you want character-based
|
|
# equivalents of substr() et. al.). They can be indexed just like in
|
|
# C (although note that there is no nul termination -- get the length
|
|
# with size()):
|
|
#
|
|
var string = "abcdefghijklmnopqrstuvwxyz";
|
|
var ascii_sum = 0;
|
|
for (var i=0; i<size(string); i+=1) { ascii_sum += string[i]; }
|
|
|
|
#
|
|
# You can use backquotes to write UTF8 character constants
|
|
#
|
|
if (`A` != 65) { print("ASCII violation bug!\n"); }
|
|
if (`©` != 169) { print("Unicode violation bug!\n"); }
|
|
|
|
#
|
|
# And you can mutate strings by assigning to their indices, as long as
|
|
# they are not constants. You
|
|
# can make a mutable string either with the append operator or the
|
|
# bits.buf() function.
|
|
#
|
|
var ascii_lc = func(string) {
|
|
var mutable = string ~ "";
|
|
for (var i=0; i<size(mutable); i+=1) {
|
|
if (mutable[i] >= `A` and mutable[i] <= `Z`) {
|
|
mutable[i] += (`a` - `A`);
|
|
}
|
|
}
|
|
return mutable;
|
|
}
|
|
print(ascii_lc("ABCDEFG"), "\n"); # prints "abcdefg"
|
|
|
|
#
|
|
# Advanced vectors: The lookup index can be negative, where -1
|
|
# indicates the last element in the vector (or string).
|
|
#
|
|
var next_to_last = list1[-2];
|
|
|
|
#
|
|
# Remember that strings look syntactically like vectors of bytes; so
|
|
# conversely, the "~" concatenation operator works equally well to
|
|
# concatenate vectors:
|
|
#
|
|
var joined_list = [1, 2, 3] ~ [4, 5, 6];
|
|
print(joined_list, "\n");
|
|
|
|
###
|
|
### Now some fun examples:
|
|
###
|
|
|
|
#
|
|
# Make a "inverted index" hash out of a vector that returns the index
|
|
# for each element.
|
|
#
|
|
var invert = func(vec) {
|
|
var hash = {};
|
|
for (var i=0; i<size(vec); i = i+1) {
|
|
hash[vec[i]] = i;
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
#
|
|
# Use the return value of the above function to do an "index of"
|
|
# lookup on a vector
|
|
#
|
|
var vecfind = func(vec, elem) { return invert(vec)[elem]; }
|
|
|
|
#
|
|
# Joins its arguments with the empty string and returns a scalar.
|
|
# Note use of "~" operator to do string concatenation (Nasal's only
|
|
# funny syntax).
|
|
#
|
|
var join = func {
|
|
var s = "";
|
|
foreach(var elem; arg) { s = s ~ elem; }
|
|
return s;
|
|
}
|
|
|
|
#
|
|
# Labeled break/continue syntax puts the label in as an extra first
|
|
# argument to the for/while/foreach.
|
|
#
|
|
var doneWithInnerLoopEarly = dummyFunc;
|
|
var completelyDone = dummyFunc;
|
|
# not supported now
|
|
for (#OUTER;
|
|
var i=0; i<100; i = i+1) {
|
|
for (var j=0; j<100; j = j+1) {
|
|
if (doneWithInnerLoopEarly()) {
|
|
break;
|
|
} elsif (completelyDone()) {
|
|
break #OUTER;
|
|
}
|
|
}
|
|
}
|
|
|
|
##
|
|
## A rockin' metaprogramming hack. Generates and returns a deep copy
|
|
## of the object in valid Nasal syntax. A warning to those who might
|
|
## want to use this: it ignores function objects (which cannot be
|
|
## inspected from Nasal) and replaces them with nil references. It
|
|
## also makes no attempt to escape special characters in strings, which
|
|
## can break re-parsing in strange (and possibly insecure!) ways.
|
|
##
|
|
var dump = func(o) {
|
|
var result = "";
|
|
if (typeof(o) == "scalar") {
|
|
var n = num(o);
|
|
if (n == nil) { result = result ~ '"' ~ o ~ '"'; }
|
|
else { result = result ~ o; }
|
|
} elsif (typeof(o) == "vector") {
|
|
result = result ~ "[ ";
|
|
if (size(o) > 0) { result = result ~ dump(o[0]); }
|
|
for (i=1; i<size(o); i=i+1) {
|
|
result = result ~ ", " ~ dump(o[i]);
|
|
}
|
|
result = result ~ " ]";
|
|
} elsif (typeof(o) == "hash") {
|
|
var ks = keys(o);
|
|
result = result ~ "{ ";
|
|
if (size(o) > 0) {
|
|
var k = ks[0];
|
|
result = result ~ k ~ ":" ~ dump(o[k]);
|
|
}
|
|
for (i=1; i<size(o); i=i+1) {
|
|
var k = ks[i];
|
|
result = result ~ ", " ~ k ~ " : " ~ dump(o[k]);
|
|
}
|
|
result = result ~ " }";
|
|
} else {
|
|
result = result ~ "nil";
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#
|
|
# Functional programming A: All function expressions are inherently
|
|
# anonymous lambdas and can be used and evaluated mid-expression:
|
|
#
|
|
# (Note the parens around the function expression. This is necessary
|
|
# because otherwise the parser would read a func following an
|
|
# assignment as a "code block" instead of a subexpression. This is
|
|
# the rule that allows you to omit the semicolon at the end of a
|
|
# normal function definition. Oh well, every language has a syntactic
|
|
# quirk or two...)
|
|
#
|
|
var a = (func(n) { n + 1 })(232); # "a" now equals 233
|
|
|
|
#
|
|
# Functional programming B. All expressions have a value, the last
|
|
# expression in a code block is the return value of that code block.
|
|
# There are no "statements" in Nasal, although some expressions
|
|
# (assignment, duh) have side effects. e.g. The "if" expression works
|
|
# both for code flow and as the ?: expression in C/C++.
|
|
#
|
|
var factorial = func(n) { if (n == 0) { 1 }
|
|
else { n * factorial(n-1) } }
|
|
print(factorial(10), "\n");
|
|
|
|
#
|
|
# Functional programming C: Lexical closures. Functions "remember"
|
|
# the context in which they were defined, and can continue to use the
|
|
# local variables in the outer scope even after their creator has
|
|
# returned.
|
|
#
|
|
var getcounter = func { var count = 0; return func { count = count + 1 } }
|
|
var mycounter = getcounter();
|
|
print(mycounter(), "\n"); # prints 1
|
|
print(mycounter(), "\n"); # prints 2
|
|
print(mycounter(), "\n"); # prints 3
|
|
print(mycounter(), "\n"); # prints 4...
|
|
|
|
#
|
|
# Functional programming D: see the documentation on the functions
|
|
# bind(), call(), closure() and caller(). Basically, every piece of
|
|
# the symbol lookup environment is inspectable and mutable at runtime.
|
|
#
|