aboutsummaryrefslogtreecommitdiffstats
path: root/alv/invoke.moon
blob: 56e651106d2c156b064521e6db6aa940566b9d7a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
----
-- Builtins for invoking `Op`s and `FnDef`s.
--
-- @module invoke
import RTNode from require 'alv.rtnode'
import Builtin from require 'alv.base.builtin'
import Scope from require 'alv.scope'
import T from require 'alv.type'
import Error from require 'alv.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)"

--- `Builtin` implementation that invokes an `Op`.
--
-- @type op_invoke
class op_invoke extends Builtin
  --- `Builtin:setup` implementation.
  --
  -- `Op:fork`s the `prev`'s `Op` instance if given. Creates a new instance
  -- otherwise.
  setup: (prev) =>
    if prev
      @op = prev.op\fork!
      prev.forked = COPILOT.T
    else
      def = @head\unwrap T.opdef, "cant op-invoke #{@head}"
      @op = def!

  --- `Builtin:destroy` implementation.
  --
  -- calls `op`:@{Op:destroy|destroy}.
  destroy: =>
    if @op and @forked ~= COPILOT.T
      @op\destroy!

  --- perform an `Op` invocation.
  --
  -- `AST:eval`s the tail, and passes the result to `op`:@{Op:setup|setup}. Then
  -- checks if any of `op`:@{Op:all_inputs|all_inputs} are @{Input:dirty|dirty},
  -- and if so, calls `op`:@{Op:tick|tick}.
  --
  -- The `RTNode` contains `op`, `Op.value` and all the `RTNode`s from the tail.
  --
  -- @tparam Scope scope the active scope
  -- @tparam {AST,...} tail the arguments to this expression
  -- @treturn RTNode
  eval: (scope, tail) =>
    children = [L\push expr\eval, scope for expr in *tail]

    frame = "invoking op #{get_name @head, @cell\head!} at [#{@tag}]"
    Error.wrap frame, ->
      @op\setup [node for node in *children], scope

      any_dirty = false
      for input in @op\all_inputs!
        if input\dirty!
          any_dirty = true
          break

      if any_dirty
        @op\tick true

      for input in @op\all_inputs!
        input\finish_setup!

      super RTNode :children, result: @op.out, op: @op

  --- `Builtin:vis` implementation.
  --
  -- calls `op`:@{Op:vis|vis}.
  --
  -- @treturn table vis
  vis: => if @op then @op\vis!

  --- The `Op` instance.
  --
  -- @tfield Op op

--- `Builtin` implementation that invokes a `FnDef`.
--
-- @type fn_invoke
class fn_invoke extends Builtin
  --- evaluate a user-function invocation.
  --
  -- Creates a new `Scope` that inherits from `FnDef.scope` and has
  -- `caller_scope` as an additional parent for dynamic symbol resolution.
  -- Then `AST:eval`s the tail in `caller_scope`, and defines the results to the
  -- names in `FnDef.params` in the newly created scope. Lastly, `AST:clone`s
  -- `FnDef.body` with the prefix `Builtin.tag`, and `AST:eval`s it in the newly
  -- created `Scope`.
  --
  -- The `RTNode` contains the `Stream` from the cloned AST, and its children
  -- are all the `RTNode`s from evaluating the tail as well as the cloned
  -- `AST`s.
  --
  -- @tparam Scope caller_scope the active scope
  -- @tparam {AST,...} tail the arguments to this expression
  -- @treturn RTNode the result of this evaluation
  eval: (caller_scope, tail) =>
    name = get_name @head, @cell\head!
    frame = "invoking function #{name} at [#{@tag}]"

    fndef = @head\unwrap T.fndef, "cant fn-invoke #{@head}"
    { :params, :body } = fndef
    if #params != #tail
      err = Error 'argument', "expected #{#params} arguments, found #{#tail}"
      err\add_frame frame
      error err

    fn_scope = Scope fndef.scope, caller_scope

    children = for i=1,#params
      name = params[i]\unwrap T.sym
      with L\push tail[i]\eval, caller_scope
        fn_scope\set name, \make_ref!

    clone = body\clone @tag
    node = Error.wrap frame, clone\eval, fn_scope

    table.insert children, node
    super RTNode :children, result: node.result

{
  :op_invoke, :fn_invoke
}