a42ca6e514
typed (file) still needs work, but at least the typed directory is gone
211 lines
7.6 KiB
Plaintext
211 lines
7.6 KiB
Plaintext
= render "pages/misc/menu"
|
||
|
||
%h1= title "Soml Syntax"
|
||
|
||
Soml was a step on the way. While i was still thinking about VM's and c++ ,
|
||
the year was 2015.
|
||
%br
|
||
I had thought that some kind of typed layer was needed, and that one could
|
||
implement the higher level in a language. A typed language, sort of like c++,
|
||
that would be used to implement the code of ruby.
|
||
%br
|
||
A little like PyPy has a core that only uses a small set of python, that can,
|
||
a la crystal, be fully type inferred.
|
||
%br
|
||
This idea turned out to be wrong, or difficult. Because the bridge between the
|
||
typed an untyped was unclear or i didn't get it to work. The current system
|
||
works by typing self, but not it's instances. A difficult mix to express in a
|
||
language.
|
||
%br
|
||
This led me to abandon soml and rewrite the functionality as vool and mom layers.
|
||
But Soml was working, the parser is still about and there are some interesting
|
||
=link_to "benchmarks" , "soml_benchmarks.html"
|
||
that came from it and really validated the calling convention.
|
||
|
||
%h2 Top level Class and methods
|
||
%p The top level declarations in a file may only be class definitions
|
||
%pre
|
||
%code
|
||
:preserve
|
||
class Dictionary < Object
|
||
int add(Object o)
|
||
... statements
|
||
end
|
||
end
|
||
%p
|
||
The class hierarchy is explained in
|
||
= succeed "," do
|
||
%a{:href => "parfait.html"} here
|
||
%p
|
||
Methods must be typed, both arguments and return. Generally class names serve as types, but “int” can
|
||
be used as a shortcut for Integer.
|
||
%p
|
||
Code may not be outside method definitions, like in ruby. A compiled program starts at the builtin
|
||
method
|
||
= succeed "," do
|
||
%strong init
|
||
%strong Space.main
|
||
%p
|
||
Classes are represented by class objects (instances of class Class to be precise) and methods by
|
||
Method objects, so all information is available at runtime.
|
||
%h2 Expressions
|
||
%p
|
||
Soml distinguishes between expressions and statements. Expressions have value, statements perform an
|
||
action. Both are compiled to Register level instructions for the current method. Generally speaking
|
||
expressions store their value in a register and statements store those values elsewhere, possibly
|
||
after operating on them.
|
||
%p The subsections below correspond roughly to the parsers rule names.
|
||
%p
|
||
%strong Basic expressions
|
||
are numbers (integer or float), strings or names, either variable, argument,
|
||
field or class names. (normal details applicable). Special names include self (the current
|
||
receiver), and message (the currently executed method frame). These all resolve to a register
|
||
with contents.
|
||
%pre
|
||
%code
|
||
:preserve
|
||
23
|
||
"hi there"
|
||
argument_name
|
||
Object
|
||
%p
|
||
A
|
||
%strong field access
|
||
resolves to the fields value at the time. Fields must be defined by
|
||
field definitions, and are basically instance variables, but not hidden (see below).
|
||
The example below shows how to define local variables at the same time. Notice chaining, both for
|
||
field access and call, is not allowed.
|
||
%pre
|
||
%code
|
||
:preserve
|
||
Type l = self.type
|
||
Class c = l.object_class
|
||
Word n = c.name
|
||
%p
|
||
A
|
||
%strong Call expression
|
||
is a method call that resolves to the methods return value. If no receiver is
|
||
specified, self (the current receiver) is used. The receiver may be any of the basic expressions
|
||
above, so also class instances. The receiver type is known at compile time, as are all argument
|
||
types, so the class of the receiver is searched for a matching method. Many methods of the same
|
||
name may exist, but to issue a call, an exact match for the arguments must be found.
|
||
%pre
|
||
%code
|
||
:preserve
|
||
Class c = self.get_class()
|
||
c.get_super_class()
|
||
%p
|
||
An
|
||
%strong operator expression
|
||
is a binary expression, with either of the other expressions as left
|
||
and right operand, and an operator symbol between them. Operand types must be integer.
|
||
The symbols allowed are normal arithmetic and logical operations.
|
||
%pre
|
||
%code
|
||
:preserve
|
||
a + b
|
||
counter | 255
|
||
mask >> shift
|
||
%p
|
||
Operator expressions may be used in assignments and conditions, but not in calls, where the result
|
||
would have to be assigned beforehand. This is one of those cases where soml’s low level approach
|
||
shines through, as soml has no auto-generated temporary variables.
|
||
%h2 Statements
|
||
%p
|
||
We have seen the top level statements above. In methods the most interesting statements relate to
|
||
flow control and specifically how conditionals are expressed. This differs somewhat from other
|
||
languages, in that the condition is expressed explicitly (not implicitly like in c or ruby).
|
||
This lets the programmer express more precisely what is tested, and also opens an extensible
|
||
framework for more tests than available in other languages. Specifically overflow may be tested in
|
||
soml, without dropping down to assembler.
|
||
%p
|
||
An
|
||
%strong if statement
|
||
is started with the keyword if_ and then contains the branch type. The branch
|
||
type may be
|
||
= succeed "." do
|
||
%em plus, minus, zero, nonzero or overflow
|
||
%em If
|
||
may be continued with en
|
||
= succeed "," do
|
||
%em else
|
||
%em end
|
||
%pre
|
||
%code
|
||
:preserve
|
||
if_zero(a - 5)
|
||
....
|
||
else
|
||
....
|
||
end
|
||
%p
|
||
A
|
||
%strong while statement
|
||
is very much like an if, with off course the normal loop semantics, and
|
||
without the possible else.
|
||
%pre
|
||
%code
|
||
:preserve
|
||
while_plus( counter )
|
||
....
|
||
end
|
||
%p
|
||
A
|
||
%strong return statement
|
||
return a value from the current functions. There are no void functions.
|
||
%pre
|
||
%code
|
||
:preserve
|
||
return 5
|
||
%p
|
||
A
|
||
%strong field definition
|
||
is to declare an instance variable on an object. It starts with the keyword
|
||
field, must be in class (not method) scope and may not be assigned to.
|
||
%pre
|
||
%code
|
||
:preserve
|
||
class Class < Object
|
||
field List instance_methods
|
||
field Type object_type
|
||
field Word name
|
||
...
|
||
end
|
||
%p
|
||
A
|
||
%strong local variable definition
|
||
declares, and possibly assigns to, a local variable. Local variables
|
||
are stored in frame objects, in fact they are instance variables of the current frame object.
|
||
When resolving a name, the compiler checks argument names first, and then local variables.
|
||
%pre
|
||
%code
|
||
:preserve
|
||
int counter = 0
|
||
%p
|
||
Any of the expressions may be assigned to the variable at the time of definition. After a variable is
|
||
defined it may be assigned to with an
|
||
%strong assignment statement
|
||
any number of times. The assignment
|
||
is like an assignment during definition, without the leading type.
|
||
%pre
|
||
%code
|
||
:preserve
|
||
counter = 0
|
||
%p Any of the expressions, basic, call, operator, field access, may be assigned.
|
||
%h2 Code generation and scope
|
||
%p
|
||
Compiling generates two results simultaneously. The more obvious is code for a function, but also an
|
||
object structure of classes etc that capture the declarations. To understand the code part better
|
||
the register abstraction should be studied, and to understand the object structure the runtime.
|
||
%p
|
||
The register machine abstraction is very simple, and so is the code generation, in favour of a simple
|
||
model. Especially in the area of register assignment, there is no magic and only a few simple rules.
|
||
%p
|
||
The main one of those concerns main memory access ordering and states that object memory must
|
||
be consistent at the end of the statement. Since there is only only object memory in soml, this
|
||
concerns all assignments, since all variables are either named or indexed members of objects.
|
||
Also local variables are just members of the frame.
|
||
%p
|
||
This obviously does leave room for optimisations as preliminary benchmarks show. But benchmarks also
|
||
show that it is not such a bit issue and much more benefit can be achieved by inlining.
|