diff options
Diffstat (limited to 'core/builtin.moon')
| -rw-r--r-- | core/builtin.moon | 487 |
1 files changed, 281 insertions, 206 deletions
diff --git a/core/builtin.moon b/core/builtin.moon index 49de80f..bd3183f 100644 --- a/core/builtin.moon +++ b/core/builtin.moon @@ -6,216 +6,282 @@ -- -- @module builtin import Action, Op, FnDef, Input, match from require 'core.base' -import Value from require 'core.value' +import Value, LiteralValue from require 'core.value' import Result from require 'core.result' import Cell from require 'core.cell' import Scope from require 'core.scope' import Tag from require 'core.tag' import op_invoke from require 'core.invoke' -class doc extends Action - @doc: "(doc sym) - print documentation in console - -prints the docstring for sym in the console" - - eval: (scope, tail) => - assert #tail == 1, "'doc' takes exactly one parameter" - - result = L\push tail[1]\eval, scope - with Result children: { def } - value = result\const! - L\print "(doc #{tail[1]}):\n#{value.doc}\n" - -class def extends Action - @doc: "(def sym1 val-expr1 - [sym2 val-expr2]...) - declare symbols in parent scope - -defines the symbols sym1, sym2, ... to resolve to the values of val-expr1, val-expr2, ... -updates all val-exprs." - - eval: (scope, tail) => - L\trace "evaling #{@}" - assert #tail > 1, "'def' requires at least 2 arguments" - assert #tail % 2 == 0, "'def' requires an even number of arguments" - - children = L\push -> - return for i=1,#tail,2 - name, val_expr = tail[i], tail[i+1] - name = (name\quote scope)\unwrap 'sym' - - with val_expr\eval scope - scope\set name, \make_ref! - - Result :children - -class use extends Action - @doc: "(use scope1 [scope2]...) - merge scopes into parent scope - -adds all symbols from scope1, scope2, ... to the parent scope. -all scopes have to be eval-time constants." - - eval: (scope, tail) => - L\trace "evaling #{@}" - for child in *tail - result = L\push child\eval, scope - value = result\const! - scope\use value\unwrap 'scope', "'use' only works on scopes" - - Result! - -class require_ extends Action - @doc: "(require name-str) - require a module - -returns the module's scope -name-str has to be an eval-time constant." - - eval: (scope, tail) => - L\trace "evaling #{@}" - assert #tail == 1, "'require' takes exactly one parameter" - - result = L\push tail[1]\eval, scope - name = result\const! - - L\trace @, "loading module #{name}" - scope = Value.wrap require "lib.#{name\unwrap 'str'}" - Result :value - -class import_ extends Action - @doc: "(import sym1 [sym2]...) - require and define modules - -requires modules sym1, sym2, ... and defines them as sym1, sym2, ... in the current scope" - - eval: (scope, tail) => - L\trace "evaling #{@}" - assert #tail > 0, "'import' requires at least one arguments" - - for child in *tail - name = (child\quote scope)\unwrap 'sym' - value = Value.wrap require "lib.#{name}" - scope\set name, Result :value -- (require "lib.#{name})\unwrap 'scope' - Result! - -class import_star extends Action - @doc: "(import* sym1 [sym2]...) - require and use modules - -requires modules sym1, sym2, ... and merges them into the current scope" - - eval: (scope, tail) => - L\trace "evaling #{@}" - assert #tail > 0, "'import' requires at least one arguments" - - - for child in *tail - name = (child\quote scope)\unwrap 'sym' - value = Value.wrap require "lib.#{name}" - scope\use value\unwrap 'scope' -- (require "lib.#{name}")\unwrap 'scope' - - Result! - -class fn extends Action - @doc: "(fn (p1 [p2]...) body-expr) - declare a (lambda) function - -the symbols p1, p2, ... will resolve to the arguments passed to the function." - - eval: (scope, tail) => - L\trace "evaling #{@}" - assert #tail == 2, "'fn' takes exactly two arguments" - { params, body } = tail - - assert params.__class == Cell, "'fn's first argument has to be an expression" - param_symbols = for param in *params.children - assert param.type == 'sym', "function parameter declaration has to be a symbol" - param\quote scope - - body = body\quote scope - Result value: Value.wrap FnDef param_symbols, body, scope - -class defn extends Action - @doc: "(defn name-sym (p1 [p2]...) body-expr) - define a function - -declares a lambda (see (doc fn)) and defines it in the current scope" - - eval: (scope, tail) => - L\trace "evaling #{@}" - assert #tail == 3, "'defn' takes exactly three arguments" - { name, params, body } = tail - - name = (name\quote scope)\unwrap 'sym' - assert params.__class == Cell, "'defn's second argument has to be an expression" - param_symbols = for param in *params.children - assert param.type == 'sym', "function parameter declaration has to be a symbol" - param\quote scope - - body = body\quote scope - fn = FnDef param_symbols, body, scope - - scope\set name, Result value: Value.wrap fn - Result! - -class do_expr extends Action - @doc: "(do expr1 [expr2]...) - update multiple expressions - -evaluates and continously updates expr1, expr2, ... -the last expression's value is returned." - - eval: (scope, tail) => - scope = Scope scope - Result children: [expr\eval scope for expr in *tail] - -class if_ extends Action - @doc: "(if bool then-expr [else-xpr]) - make an eval-time const choice - -bool has to be an eval-time constant. If it is truthy, this expression is equivalent -to then-expr, otherwise it is equivalent to else-xpr if given, or nil otherwise." - - eval: (scope, tail) => - L\trace "evaling #{@}" - assert #tail >= 2, "'if' needs at least two parameters" - assert #tail <= 3, "'if' needs at most three parameters" - - { xif, xthen, xelse } = tail - - xif = L\push xif\eval, scope - xif = xif\const!\unwrap! - - if xif - xthen\eval scope - elseif xelse - xelse\eval scope - -class trace_ extends Action - @doc: "(trace! expr) - print an eval-time constant to the console" - - eval: (scope, tail) => - L\trace "evaling #{@}" - assert #tail == 1, "'trace!' takes exactly one parameter" - - with result = L\push tail[1]\eval, scope - L\print "trace! #{tail[1]\stringify!}: #{result.value}" - -class trace extends Action - @doc: "(trace expr) - print values to the console - -prints expr's value whenever it changes." - - class traceOp extends Op - setup: (inputs) => - { prefix, value } = match 'str any', inputs - super - prefix: Input.cold prefix - value: Input.value value - - tick: => - L\print "trace #{@inputs.prefix!}: #{@inputs.value.stream}" - - eval: (scope, tail) => - L\trace "evaling #{@}" - assert #tail == 1, "'trace!' takes exactly one parameter" - - op = Value 'opdef', traceOp - tag = @tag\clone Tag.parse '-1' - prefix = Value.str tostring tail[1] - op_invoke\eval_cell scope, tag, op, { prefix, tail[1] } +doc = Value.meta + meta: + name: 'doc' + summary: "Print documentation in console." + examples: { '(doc sym)' } + description: "Print the documentation for `sym` to the console" + + value: class extends Action + format_meta = => + str = @summary + if @examples + for example in *@examples + str ..= '\n' .. example + if @description + str ..= '\n' .. @description\match '^\n*(.+)\n*$' + str + + eval: (scope, tail) => + assert #tail == 1, "'doc' takes exactly one parameter" + + result = L\push tail[1]\eval, scope + with Result children: { def } + meta = result.value.meta + L\print "(doc #{tail[1]}):\n#{format_meta meta}\n" + +def = Value.meta + meta: + name: 'def' + summary: "Declare symbols in current scope." + examples: { '(def sym1 val-expr1 [sym2 val-expr2…])' } + description: " +Define the symbols `sym1`, `sym2`, … to resolve to the values of `val-expr1`, +`val-expr2`, …." + + value: class extends Action + eval: (scope, tail) => + L\trace "evaling #{@}" + assert #tail > 1, "'def' requires at least 2 arguments" + assert #tail % 2 == 0, "'def' requires an even number of arguments" + + children = L\push -> + return for i=1,#tail,2 + name, val_expr = tail[i], tail[i+1] + name = (name\quote scope)\unwrap 'sym' + + with val_expr\eval scope + scope\set name, \make_ref! + + Result :children + +use = Value.meta + meta: + name: 'use' + summary: "Merge scopes into current scope." + examples: { '(use scope1 [scope2…])' } + description: " +Copy all symbol definitions from `scope1`, `scope2`, … to the current scope. +All arguments have to be evaltime constant." + + value: class extends Action + eval: (scope, tail) => + L\trace "evaling #{@}" + for child in *tail + result = L\push child\eval, scope + value = result\const! + scope\use value\unwrap 'scope', "'use' only works on scopes" + + Result! + +require_ = Value.meta + meta: + name: 'require' + summary: "Load a module." + examples: { '(require name)' } + description: "Load a module and return its scope." + + value: class extends Action + eval: (scope, tail) => + L\trace "evaling #{@}" + assert #tail == 1, "'require' takes exactly one parameter" + + result = L\push tail[1]\eval, scope + name = result\const! + + L\trace @, "loading module #{name}" + scope = Value.wrap require "lib.#{name\unwrap 'str'}" + Result :value + +import_ = Value.meta + meta: + name: 'import' + summary: "Require and define modules." + examples: { '(import sym1 [sym2…])' } + description: " +Requires modules `sym1`, `sym2`, … and define them as `sym1`, `sym2`, … in the +current scope." + + value: class extends Action + eval: (scope, tail) => + L\trace "evaling #{@}" + assert #tail > 0, "'import' requires at least one arguments" + + for child in *tail + name = (child\quote scope)\unwrap 'sym' + value = Value.wrap require "lib.#{name}" + scope\set name, Result :value -- (require "lib.#{name})\unwrap 'scope' + Result! + +import_star = Value.meta + meta: + name: 'import*' + summary: "Require and use modules." + examples: { '(import* sym1 [sym2…])' } + description: " +Requires modules `sym1`, `sym2`, … and merges them into the current scope." + + value: class extends Action + eval: (scope, tail) => + L\trace "evaling #{@}" + assert #tail > 0, "'import' requires at least one arguments" + + + for child in *tail + name = (child\quote scope)\unwrap 'sym' + value = Value.wrap require "lib.#{name}" + scope\use value\unwrap 'scope' -- (require "lib.#{name}")\unwrap 'scope' + + Result! + +fn = Value.meta + meta: + name: 'fn' + summary: "Declare a function." + examples: { '(fn (p1 [p2…]) body-expr)' } + description: " +The symbols `p1`, `p2`, ... will resolve to the arguments passed when the +function is invoked." + + value: class extends Action + eval: (scope, tail) => + L\trace "evaling #{@}" + assert #tail == 2, "'fn' takes exactly two arguments" + { params, body } = tail + + assert params.__class == Cell, "'fn's first argument has to be an expression" + param_symbols = for param in *params.children + assert param.type == 'sym', "function parameter declaration has to be a symbol" + param\quote scope + + body = body\quote scope + Result value: with Value.wrap FnDef param_symbols, body, scope + .meta = { + summary: "(user defined function)" + examples: { "(??? #{table.concat [p! for p in *param_symbols], ' '})" } + } + +defn = Value.meta + meta: + name: 'defn' + summary: "Define a function." + examples: { '(defn name-sym (p1 [p2…]) body-expr)' } + description: " +Declare a function and define it as `name-sym` in the current scope. +The symbols `p1`, `p2`, ... will resolve to the arguments passed when the +function is invoked." + + value: class extends Action + eval: (scope, tail) => + L\trace "evaling #{@}" + assert #tail == 3, "'defn' takes exactly three arguments" + { name, params, body } = tail + + name = (name\quote scope)\unwrap 'sym' + assert params.__class == Cell, "'defn's second argument has to be an expression" + param_symbols = for param in *params.children + assert param.type == 'sym', "function parameter declaration has to be a symbol" + param\quote scope + + body = body\quote scope + + value = with Value.wrap FnDef param_symbols, body, scope + .meta = + :name + summary: "(user defined function)" + examples: { "(#{name} #{table.concat [p! for p in *param_symbols], ' '})" } + + scope\set name, Result :value + Result! + +do_expr = Value.meta + meta: + name: 'do_expr' + summary: "Evaluate multiple expressions in a new scope." + examples: { '(do expr1 [expr2…])' } + description: " +Evaluate `expr1`, `expr2`, … and return the value of the last expression." + + value: class extends Action + eval: (scope, tail) => + scope = Scope scope + Result children: [expr\eval scope for expr in *tail] + +if_ = Value.meta + meta: + name: 'if' + summary: "Make an evaltime const choice." + examples: { '(if bool then-expr [else-expr])' } + description: " +`bool` has to be an evaltime constant. If it is truthy, this expression is equivalent +to `then-expr`, otherwise it is equivalent to `else-xpr` if given, or nil otherwise." + + value: class extends Action + eval: (scope, tail) => + L\trace "evaling #{@}" + assert #tail >= 2, "'if' needs at least two parameters" + assert #tail <= 3, "'if' needs at most three parameters" + + { xif, xthen, xelse } = tail + + xif = L\push xif\eval, scope + xif = xif\const!\unwrap! + + if xif + xthen\eval scope + elseif xelse + xelse\eval scope + +trace_ = Value.meta + meta: + name: 'trace!' + summary: "Trace an expression's value at evaltime." + examples: { '(trace! expr)' } + + value: class extends Action + eval: (scope, tail) => + L\trace "evaling #{@}" + assert #tail == 1, "'trace!' takes exactly one parameter" + + with result = L\push tail[1]\eval, scope + L\print "trace! #{tail[1]\stringify!}: #{result.value}" + +trace = Value.meta + meta: + name: 'trace' + summary: "Trace an expression's values at runtime." + examples: { '(trace expr)' } + + value: class extends Action + class traceOp extends Op + setup: (inputs) => + { prefix, value } = match 'str any', inputs + super + prefix: Input.cold prefix + value: Input.value value + + tick: => + L\print "trace #{@inputs.prefix!}: #{@inputs.value.stream}" + + eval: (scope, tail) => + L\trace "evaling #{@}" + assert #tail == 1, "'trace!' takes exactly one parameter" + + tag = @tag\clone Tag.parse '-1' + inner = Cell tag, { + LiteralValue 'opdef', traceOp, 'trace' + Value.str tostring tail[1] + tail[1] + } + inner\eval scope { :doc @@ -226,8 +292,17 @@ prints expr's value whenever it changes." import: import_ 'import*': import_star - true: Value.bool true - false: Value.bool false + true: Value.meta + meta: + name: 'true' + summary: "The boolean constant `true`." + value: Value.bool true + + false: Value.meta + meta: + name: 'false' + summary: "The boolean constant `false`." + value: Value.bool false :fn, :defn 'do': do_expr |
