Here is a simple example of Suneido code:
/* multi-line
comment */
calc = function (x, y = 0, dbl = false)
{
sum = x + y // single line comment
if dbl is true
sum *= 2
return "result is " $ sum
}
calc(123) => "result is 123"
calc(123, 456) => "result is 579"
calc(10, dbl:, y: 5) => "result is 30"
calc("123") => "result is 123"
Notice:
- C/C++/Java style comments, either /*...*/ or //...
- Functions are declared using the keyword “function”
- Functions (and classes) are "values" – they can be assigned to variables, passed to functions, etc.
- Function arguments can have default values
- Function
calls can use named arguments, in which case order is not
significant. If a value isn’t supplied for named argument, it defaults
to true.
- Semicolons are not required (but are allowed)
- Variables do not need to be declared, and may contain any type – i.e. Suneido is a dynamically typed language
- is, isnt, not, and, or are normally used but ==, !=, !, &&, || are allowed
- Suneido
includes all of the C/C++ operators, including assignment operators
(e.g. *=), increment, decrement, and bit manipulation, and adds
regular expression operators =~ and !~
- Automatic conversions between numbers and strings.
- String
concatenation is done with the "$" operator, not by overloading "+".
This makes "123" + 456 and 123 $ "456" unambiguous.
- Suneido
includes the same control statements as C/C++ if-else, while,
do-while, switch, but unlike C/C++ conditions must evaluate to true or
false or you’ll get an error. Switches are also slightly different –
you can’t "fall through" cases, and cases allow multiple expressions
rather than integer constants. There is also a "foreach" statement
(see below).
Functions can take variable numbers of arguments:
max = function (@args)
{
max = args[0] // will throw exception if no args
foreach (value x in args)
if (x > max)
max = x
return max
}
max(3, 6, 2, 5) => 6
max("joe", "fred", "sam", "mike") => "sam"
@args puts all the arguments into a Suneido "object" (see below).
foreach iterates through all the values in the object, in this case all
the arguments.
You can also pass a pre-assembled set of arguments, using @.
ages = #(23, 67, 34, 19)
max(@ages) => 67
Memory is garbage collected – there is no explicit allocation or
freeing. However, finalization is not supported yet, so resources (e.g.
Windows handles) must be explicitly released.
The basic data types in Suneido include: boolean (True and False), number, string, and object.
Suneido has a single numeric type – decimal floating point. Keeping
numbers in decimal rather than binary allows exact representation of
decimals, e.g. for amounts of money. Numbers have 16 digits of
precision with an exponent range of plus or minus 127.
Strings
are not null terminated so they can store arbitrary binary data as
well as text. Strings are immutable i.e. there is no way to "modify" an
existing string. (Objects are the only basic data type that is
mutable.) This means that substring (s.Substr(i,n)) does not need to do
any copying – it just creates a new string that points to part of the
old one. For speed, concatenation is "lazy"; it creates a linked list
of string segments, which are automatically combined when a single
string is required. This greatly reduces the amount of allocation and
copying required to manipulate strings.
Suneido could be called
a "pure" object-oriented language in the sense that all values
(including literals) are "objects" that can have methods. For example:
"hello world".Substr(3,2) => "lo"
97.Chr() => "a"
However, unlike some object-oriented languages such as Smalltalk or
Java, Suneido has standalone functions as well as methods in classes.
User defined methods can be added to the built-in classes by creating
methods in specially named classes: Numbers, Strings, Dates, Objects,
etc.
Suneido has only two "scopes", global and local. Global
names must be capitalized i.e. start with an upper case letter. Globals
are either builtin (to the executable) or user defined in libraries in
the database. Global names are not variables – they cannot be assigned
to by code. Names that start with a lower case letter are local to a
single function. Currently there are no packages or modules with
separate namespaces.
Like Java, Suneido compiles to a stack oriented "byte code" which is then interpreted. For example:
fn = function (x) { return x * 100 }
fn.Disasm()
=> push auto x
push literal 100
*
return
Linking is done dynamically at run time. This means there are no
restrictions on compiling code with calls to undefined functions or
inheriting from undefined base classes. Of course, if you try to
actually access something undefined, you’ll get a run time error.
Objects
Suneido has a single "universal" container type – objects. Unlike
languages like Python or Ruby, you don't have to worry about what type
of container to use. Objects can be used as vectors (single dimensional
arrays) or as "maps" or both at the same time. Internally, objects
have a vector part and a hash map part. Classes and instances are
implemented as objects containing methods and data members.
x = Object()
for (i = 0; i < 6; ++i)
x[i] = i // access as a vector
x.Add("six") // same as x[6] = "six"
x => #(0, 1, 2, 3, 4, 5, "six")
x = Object(name: "fred", age: 25)
x.married = True
x => #(name: "fred", age: 25, married: True)
x.name => "fred"
m = "age"
x[m] => 25 // same as x.age
Exceptions
Suneido has exception handling similar to C++ or Java, with try, catch,
and throw. However, Suneido exceptions are strings rather than class
instances.
try
...
catch (exception)
...
The catch portion is optional if you simply want to ignore exceptions:
try Database("destroy mytable")
An uncaught exception calls a Handler function – defined in the standard library as the debugger.
You can throw your own exceptions:
if (x < 0)
throw "square root: invalid negative argument: " $ x
Blocks
Suneido supports Smalltalk style "blocks". Basically, a block is a
section of code within a function, that can be called like a function,
but that operates within the context of the function call that created
it (i.e. shares its local variables).
Blocks can be used to
implement user defined "control constructs". (In Smalltalk, all control
constructs are implemented with blocks.) For example, you could
implement your own version of "foreach":
for_each = function (list, block)
{
for (i = 0; i < list.Size(); ++i)
block(list[i])
}
list = #(12, 34, 56)
for_each(list)
{ |x| Print(x) }
=> 12
34
56
Suneido treats a block immediately following a function call as an additional argument.
Blocks can also be used to execute sections of code in specific
"contexts". For example, the Catch function traps exceptions and
returns them. (This is useful in unit tests to verify that expected
exceptions occur.)
catcher = function (block)
{
try
return block()
catch (x)
return x
}
catcher( { xyz } ) => "unitialized variable: xyz"
But the interesting part is that a block can outlive the function call
that created it, and when it does so, it keeps its context (set of
local variables). For example:
make_counter = function (next)
{ return { next++ } }
counter = make_counter(10)
Print(counter())
Print(counter())
Print(counter())
=> 10
11
12
In this example, make_counter returns a block. The block returns next++. You see this type of code in Lisp / Scheme.
Classes
Classes are read-only objects containing methods (functions) and members (data). For example:
counter = class
{
New(start = 0)
{ .count = start }
Next()
{ return .count++ }
}
ctr = new counter
Print(ctr.Next())
Print(ctr.Next())
Print(ctr.Next())
=> 0
1
2
Within a method, the special name "this" refers to the current object.
So members and methods of the current object can be referred to as
"this.name" but this is normally shortened to simply ".name".
Member / method names are public if they start with an upper case letter
and private if they start with a lower case letter. Private names can
only be accessed by methods of the class. In the above example, "Next"
is a public method and "count" is a private member.
New is the
constructor. (C++ and Java use the name of the class instead of "New",
but this wouldn't work for Suneido because it allows "anonymous"
classes i.e. with no name.) Base class New's are automatically called
first. You can supply arguments to base class constructors by making
the first statement of New a call to "super". (Similar to Java.)
class
{
New(x, y, z)
{
super(x, y)
...
"super" can also be used to call base class versions of methods:
mystack = class : Stack
{
Push(x)
{
...
super.Push(x)
...
}
Normally, "calling" a class is the same as creating an instance. (This
can be changed by defining a method called "CallClass".) So the
following are equivalent:
ctr = counter(5)
ctr = new counter(5)
Any method in Suneido can be a "class" or "static" method, providing it
doesn't need to set any members. If a method is called on a class
rather than an instance, "this" will be the class itself, and therefore
read- only.
If a nonexistent member is accessed, Suneido will
call a method with the same name with no arguments, if present. This
allows you to change a data member to a method without having to alter
code that uses it. (Bertrand Meyer calls this "Uniform Access" – a
client should be able to access a property of an object using a single
notation, whether the property is implemented by memory or
computation.)
If a nonexistent method is called, Suneido will
call a method called "Default" if it is present. This can be used to
implement delegation, or to handle methods that are not pre-determined.
Since classes and instances are objects, and have methods to
get their base class, it is easy for code to inspect methods and
members. Java calls this "reflection".
Stack.Members()
=> #("Pop", "Top", "Push")
Object syntax can also be used to access methods or members "indirectly":
m = "Add"
ob[m](value) // equivalent to ob.Add(value)
"Missing" Features
- no declarations
- no preprocessor i.e. #define or #include
- no enums
- no multiple inheritance (although this may be added)
- no operator overloading
- no goto
- no explicit pointers
- no explicit free'ing of memory
- no separate module or package namespaces
- no non-local returns from blocks
- no "protected" members / methods (just private and public)
- no "static" class data members (just class methods)
Summary
The Suneido language is:
- small, simple
- object-oriented
- dynamically typed - no declarations
- functionally similar to Smalltalk
- familiar to anyone who has programmed in C, C++, Java etc.
- safe - automatic memory management (garbage collection) and no pointers