(comments are in parentheses, can nest, and can span multiple lines) (the following are special characters: []().:') (for now everything else can be used in words) (numbers, strings, words) 1 'string' a foo a-word-with-dashes (keywords: # !) (special forms: import if when while set and or) (application is like lisp) [+ 1 2] (returns 3) (brackets can be omitted at the start of a line) + 1 2 (the same) (arguments can be indented, one per line) + 1 2 (also returns 3) (exception: with one expression alone, and no indented body, then brackets are needed if you want to call it) [fn] (this returns the fn without applying it) fn (the general rule is: if a line has multiple top-level items (including indented parts) it's wrapped in [], otherwise it isn't) (brackets are used to group function calls) + 1 [* 2 3] (evaluates to 7) + 1 * 2 3 (the same) (a pair is a key expression followed by a colon and a value expression) plus: + (in a body, pairs define variables) one: 1 two: 2 plus one two (evaluates to 3) (in special forms, pairs can have other meanings: see when and while later) (set variables with `set`) a: 1 print a (prints 1) set a 2 print a (prints 2) (functions can be defined using brackets [] around the key in a pair. think of it like a pattern match) (for example, this defines a function `fn` that takes a and b as arguments and adds them) [fn a b]: + a b [fn 1 2] (returns 3) (putting a . as the function name makes an anonymous function instead) [. a b]: + a b (if a colon is followed by a newline, then the indented region is treated like a body, and the expressions are evaluated one by one) three: one: 1 two: 2 + one two (the last item is returned) (putting a dot in front of a word makes it a symbol) .abc (methods / fields are used by putting a symbol after an expression) 'asdf'.length (this is sort of like calling 'asdf' with the symbol .length as an argument, except that symbols aren't passed as regular arguments) (symbols associate with the item on their left, so brackets may be omited more often) print 'asdf'.length (prints 4) (symbol applications at the start of a line can take arguments) 'asdf'.substring 1 3 (returns 'sd') (this calls 'asdf' with arguments .substring, 1, and 3) (when not starting a line, brackets are used to call a method with arguments) print 'asdf'.substring 1 3 (doesn't work, tries to print ['asdf'.substring], 1, and 3 as three separate values) print ['asdf'.substring 1 3] (prints 'sd') (arithmetic is implemented as method calls, so these two are the same) + 1 2 1 .+ 2 (the + function is implemented like this) [+ a b]: a .+ b (if always has two branches, each a single expression, and returns one of them, like a ternary expression) if [a .< 0] 'true expression' 'false expression' (and returns the first value if it's false, otherwise evaluates and returns the second value) (or returns the first value if it's not false, otherwise evaluates and returns the second value) and 1 2 (returns 2) or 1 2 (returns 1) and something-that-must-be-falsy [error 'it wasnt falsy!'] or something-that-must-be-truthy [error 'it wasnt truthy!'] (when uses condition: result pairs instead) when [a .< 1]: 'first then block goes here' [a .< 2]: 'another then block' (only the first matching one is executed) else: 'else catches other cases (optional)' 'like other bodies, can include multiple' 'indented expressions' (unlike `if`, `when` lets you: - have more than one condition, - omit the else case, - have bodies with multiple expressions) (when you only have one condition / body, putting the condition on the same line is more compact) when [a .< 1]: 'first expression' 'more expressions can go here,' 'they are all executed' (note that when pairs aren't the first thing on a line, they only accept one value on the right of the colon) when [a .< 1]: f 1 2 (not valid) when [a .< 1]: [f 1 2] (put brackets) when [a .< 1]: f 1 2 (indenting the body also works) (while takes a condition/result pair, repeats the result while the condition is true) a: 0 while [a .< 100]: print a set a [a .+ 1] (prints numbers from 0-99) (`#` creates a table, or object) obj: [#] (empty object) (i'm thinking of renaming this to `table`, or splitting it into `object` and `type`, which are the two things it's used for) (tables can have fields) tbl: # .field-a: 1 .field-b: 2 tbl.field-a (gets field-a) set tbl.field-a 3 (sets field-a) (including anonymous functions in tables lets you call the table with the corresponding number of arguments, this is how you overload a function depending on number of arguments) tbl2: # [.]: tbl2 1 2 [. a b]: + a b [tbl2] (returns 3) tbl2 2 3 (returns 5) (including a symbol as the first argument lets you call the table with that symbol) tbl3: # [. .something]: 1 (symbol-functions can have arguments) [. .something-else a b]: + a b tbl3.something (returns 1) tbl3.something-else 1 2 (returns 3) (tables can also be used as types, providing methods to other tables) some-type: # (methods are defined by putting a name before the symbol (usually but not always `self`)) [self.foo a]: + a self.field (methods are not called on the table itself, but w hen the table is used as the type for another table) (pass the type object `some-type` when making a table to allow the methods to be used on it) tbl4: # some-type .field: 1 tbl4.foo 2 (returns 3) (this is used to implement classes) (by convention, classes are named with words that start with `#`) (here's a two dimensional vector class) #vec2: # (invoking [#vec2 x y] calls this constructor, which makes a new table that inherits #vec2's methods) [. x y]: # #vec2 .x: x .y: y (this plus method will be available on instances of #vec2 (but not directly on the #vec2 class)) [a .+ b]: #vec2 [a.x .+ b.x] [a.y .+ b.y] [vec2 1 2] .+ [vec2 3 4] (returns [vec2 4 6]) + [vec2 1 2] [vec2 3 4] (the same) (note that only methods are inherited in this way, not other functions or fields) (todo: import, module exports, and, or) (import brings stuff from another file into scope) import 'prelude' (most files start with this) (a file that's imported in this way should end in a table literal with fields for each thing to be imported:) # .name: name (or) .exported-name: internal-name (imported fields are used without the leading .) (currently all imported names are visible globally, probably this will change to file scope later)