From e83df1af2cdad8c2d61ba790a96875cd260eceaf Mon Sep 17 00:00:00 2001 From: s-ol Date: Sat, 21 Mar 2020 20:06:18 +0100 Subject: new meta/doc system --- core/base/action.moon | 33 ++-- core/builtin.moon | 487 +++++++++++++++++++++++++++++--------------------- core/cell.moon | 2 +- core/init.moon | 5 +- core/invoke.moon | 28 ++- core/result.moon | 2 - core/value.moon | 43 +++-- 7 files changed, 358 insertions(+), 242 deletions(-) (limited to 'core') diff --git a/core/base/action.moon b/core/base/action.moon index cfca919..7ada4ba 100644 --- a/core/base/action.moon +++ b/core/base/action.moon @@ -15,9 +15,11 @@ class Action --- create a new instance. -- + -- @tparam Cell cell the Cell to evaluate -- @tparam Value head the (`AST:eval`d) `head` of the Cell to evaluate - -- @tparam Tag tag the Tag of the expression to evaluate - new: (@head, @tag) => + new: (@cell, @head) => + @tag = @cell.tag + @tag\replace @ --- perform the actual evaluation. -- @@ -43,12 +45,13 @@ class Action -- @tparam ?Action prev the previous Action instance setup: (prev) => - --- the head of the `Cell` this Action was created for. - -- + --- the `Cell` this Action was created for. + -- @tfield Cell cell + + --- the evaluated head of `cell`. -- @tfield AST head - --- the identity of the `Cell` this Action was created for. - -- + --- the identity of `cell`. -- @tfield Tag tag --- static functions @@ -61,31 +64,29 @@ class Action -- it pass it to `setup`. Register the `Action` with `tag`, evaluate it -- and return the `Result`. -- + -- @tparam Cell cell the `Cell` being evaluated -- @tparam Scope scope the active scope - -- @tparam Tag tag the tag of the `Cell` being evaluated -- @tparam Value head the (`AST:eval`d) head of the `Cell` being evaluated - -- @tparam {AST,...} tail the raw AST parameters to the `Cell` being evaluated -- @treturn Result the result of evaluation - @eval_cell: (scope, tag, head, tail) => - last = tag\last! + @eval_cell: (cell, scope, head) => + last = cell.tag\last! compatible = last and (last.__class == @) and last.head == head L\trace if compatible - "reusing #{last} for #{tag} <#{@__name} #{head}>" + "reusing #{last} for #{cell.tag} <#{@__name} #{head}>" else if last - "replacing #{last} with new #{tag} <#{@__name} #{head}>" + "replacing #{last} with new #{cell.tag} <#{@__name} #{head}>" else - "initializing #{tag} <#{@__name} #{head}>" + "initializing #{cell.tag} <#{@__name} #{head}>" - action = @ head, tag + action = @ cell, head if compatible action\setup last else last\destroy! if last action\setup nil - tag\replace action - action\eval scope, tail + action\eval scope, cell\tail! __tostring: => "<#{@@__name} #{@head}>" __inherited: (cls) => cls.__base.__tostring = @__tostring 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 diff --git a/core/cell.moon b/core/cell.moon index 2109ade..b7f4a1d 100644 --- a/core/cell.moon +++ b/core/cell.moon @@ -83,7 +83,7 @@ class Cell else error "cannot evaluate expr with head #{head}" - Action\eval_cell scope, @tag, head, @tail! + Action\eval_cell @, scope, head --- quote this Cell, preserving its identity. -- diff --git a/core/init.moon b/core/init.moon index 00e3cd4..1aa0d2d 100644 --- a/core/init.moon +++ b/core/init.moon @@ -49,7 +49,10 @@ globals = Scope.from_table require 'core.builtin' :Registry, :SimpleRegistry, :Tag :globals - parse: program\match + + parse: (str) -> + assert (program\match str), Error 'syntax', "failed to parse" + eval: (str, inject) -> scope = Scope nil, globals scope\use inject if inject diff --git a/core/invoke.moon b/core/invoke.moon index 97d3949..654e436 100644 --- a/core/invoke.moon +++ b/core/invoke.moon @@ -8,6 +8,20 @@ import Action from require 'core.base' import Scope from require 'core.scope' import Error from require 'core.error' +get_name = (value, raw) -> + meta = if value.meta then value.meta.name + locl = if raw and raw.type == 'sym' then raw! + + if locl + if meta and meta != locl + "'#{meta}' (local '#{locl}')" + else + "'#{locl}'" + else if meta + "'#{meta}'" + else + "(unnamed)" + --- `Action` implementation that invokes an `Op`. -- -- @type op_invoke @@ -41,7 +55,9 @@ class op_invoke extends Action -- @treturn Result eval: (scope, tail) => children = [L\push expr\eval, scope for expr in *tail] - Error.wrap "invoking #{@op}#{@tag}", @op\setup, [result for result in *children], scope + + frame = "invoking op #{get_name @head, @cell\head!} at [#{@tag}]" + Error.wrap frame, @op\setup, [result for result in *children], scope any_dirty = false for input in @op\all_inputs! @@ -81,9 +97,13 @@ class fn_invoke extends Action -- @tparam {AST,...} tail the arguments to this expression -- @treturn Result the result of this evaluation eval: (outer_scope, tail) => - { :params, :body, :scope } = @head\unwrap 'fndef', "cant fn-invoke #{@head}" + name = get_name @head, @cell\head! + frame = "invoking function #{name} at [#{@tag}]" - assert #params == #tail, "argument count mismatch in #{@head}" + { :params, :body, :scope } = @head\unwrap 'fndef', "cant fn-invoke #{@head}" + if #params != #tail + error with Error 'argument', "expected #{#params} arguments, found #{#tail}" + \add_frame frame fn_scope = Scope scope, outer_scope @@ -93,7 +113,7 @@ class fn_invoke extends Action fn_scope\set name, \make_ref! clone = body\clone @tag - result = Error.wrap "invoking function #{body.tag} at #{@tag}", clone\eval, fn_scope + result = Error.wrap frame, clone\eval, fn_scope table.insert children, result Result :children, value: result.value diff --git a/core/result.moon b/core/result.moon index 357a868..52203da 100644 --- a/core/result.moon +++ b/core/result.moon @@ -61,9 +61,7 @@ class Result for stream in @op\all_inputs! if stream\dirty! self_dirty = true - break - -- L\trace "#{@op} is #{if self_dirty then 'dirty' else 'clean'}" return unless self_dirty @op\tick! diff --git a/core/value.moon b/core/value.moon index 7f5a93d..da7f6d0 100644 --- a/core/value.moon +++ b/core/value.moon @@ -72,23 +72,22 @@ class Value -- - `fndef` - `value` is a `FnDef` instance -- - `scope` - `value` is a `Scope` instance -- - -- @tfield string type the type name + -- @tfield string type --- the wrapped Lua value. + -- @tfield any value + + --- documentation metadata. -- - -- the following builtin typenames are used: + -- an optional table containing metadata for error messages and + -- documentation. The following keys are recognized: -- - -- - `str` - strings, `value` is a Lua string - -- - `sym` - symbols, `value` is a Lua string - -- - `num` - numbers, `value` is a Lua number - -- - `bool` - booleans, `value` is a Lua boolean - -- - `bang` - trigger signals, `value` is a Lua boolean - -- - `opdef` - `value` is an `Op` subclass - -- - `builtin` - `value` is an `Action` subclass - -- - `fndef` - `value` is a `FnDef` instance - -- - `scope` - `value` is a `Scope` instance + -- - `name`: optional name + -- - `summary`: single-line description (markdown) + -- - `examples`: optional list of single-line code examples + -- - `description`: optional full-text description (markdown) -- - -- @tfield any value the wrapped value + -- @tfield ?table meta --- AST interface -- @@ -139,6 +138,7 @@ class Value -- @tparam any value the Lua value to be accessed through `unwrap` -- @tparam string raw the raw string that resulted in this value. Used by `parsing`. new: (@type, @value, @raw) => + @meta = {} unescape = (str) -> str\gsub '\\([\'"\\])', '%1' --- create a capture-function (for parsing with Lpeg). @@ -157,6 +157,7 @@ class Value -- -- @tparam any val the value to wrap -- @tparam[opt] string name the name of this value (for error logging) + -- @treturn Value @wrap: (val, name='(unknown)') -> typ = switch type val when 'number' then 'num' @@ -188,21 +189,39 @@ class Value --- create a constant number. -- @tparam number num the number + -- @treturn Value @num: (num) -> Value 'num', num, tostring num --- create a constant string. -- @tparam string str the string + -- @treturn Value @str: (str) -> Value 'str', str, "'#{str}'" --- create a constant symbol. -- @tparam string sym the symbol + -- @treturn Value @sym: (sym) -> Value 'sym', sym, sym --- create a constant boolean. -- @tparam boolean bool the boolean + -- @treturn Value @bool: (bool) -> Value 'bool', bool, tostring bool + --- wrap and document a value. + -- + -- wraps `args.value` using `wrap`, then assigns `meta`. + -- + -- @tparam table args table with keys `value` and `meta` + -- @treturn Value + @meta: (args) -> + with Value.wrap args.value + .meta = args.meta if args.meta + +class LiteralValue extends Value + eval: => Result value: @ + { :Value + :LiteralValue :load_ } -- cgit v1.2.3