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
}
|