aboutsummaryrefslogtreecommitdiffstats
path: root/alv/cell.moon
blob: 6e141aaf07371cdd9ba1a4af0f11a48cea62fe76 (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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
----
-- S-Expression Cell, implements the `AST` interface.
--
-- Consists of a head expression and any number of tail expressions (both `AST`
-- nodes), a `Tag`, and optionally the internal whitespace as parsed.
--
-- @classmod Cell
import T, Array from require 'alv.type'
import Constant from require 'alv.result'
import Dummy from require 'alv.dummy'
import Error from require 'alv.error'
import op_invoke, fn_invoke from require 'alv.invoke'
import Tag from require 'alv.tag'

parse_args = (tag, parts) ->
  if not parts
    parts, tag = tag, nil

  children, white = {}, { [0]: parts[1] }

  for i = 2,#parts,2
    children[i/2] = parts[i]
    white[i/2] = parts[i+1]

  tag, children, white

--- @type Cell
class Cell
--- members
-- @section members

  --- get the head of the cell.
  --
  -- @treturn AST
  head: => @children[1]

  --- get the tail of the cell.
  --
  -- @treturn {AST,...}
  tail: => [c for c in *@children[2,]]

  __tostring: => @stringify 2

  --- the parsed Tag.
  --
  -- @tfield Tag tag

  --- sequence of child AST Nodes
  --
  -- @tfield {AST,...} children

  --- optional sequence of whitespace segments.
  --
  -- If set, `whitespace[i]` is the whitespace between `children[i]` and
  -- `children[i+1]`, or the closing parenthesis of this Cell. `whitespace[0]`
  -- is the space between the opening parenthesis and `children[1]`.
  --
  -- @tfield ?{string,...} whitespace

--- AST interface
--
-- `Cell` implements the `AST` interface.
-- @section ast

  --- evaluate this Cell.
  --
  -- `AST:eval`uates the head of the expression, and finds the appropriate
  -- `Builtin` to invoke:
  --
  -- - if head is an `opdef`, use `invoke.op_invoke`
  -- - if head is a `fndef`, use `invoke.fn_invoke`
  -- - if head is a `builtin`, unwrap it
  --
  -- then calls `Builtin:eval_cell` on it.
  --
  -- @tparam Scope scope the scope to evaluate in
  -- @treturn RTNode the evaluation result
  eval: (scope) =>
    head = assert @head!, Error 'syntax', "cannot evaluate empty expr"
    head = (head\eval scope)\const!
    Builtin = switch head.type
      when T.opdef
        op_invoke
      when T.fndef
        fn_invoke
      when T.builtin
        head\unwrap!
      else
        error Error 'type', "#{head} is not an opdef, fndef or builtin"

    Builtin\eval_cell @, scope, head

  --- create a clone with its own identity.
  --
  -- creates a clone of this Cell with its own identity by prepending a `parent`
  -- to `tag` and cloning all child expressions recursively.
  --
  -- @tparam Tag parent
  -- @treturn Cell
  clone: (parent) =>
    tag = @tag\clone parent
    children = [child\clone parent for child in *@children]
    @@ tag, children, @white

  --- stringify this Cell.
  --
  -- if `depth` is passed, does not faithfully recreate the original string but
  -- rather create useful debug output.
  --
  -- @tparam[opt] int depth the maximum depth, defaults to infinite
  -- @treturn string the exact string this Cell was parsed from, unless `@tag`
  -- changed
  stringify: (depth=-1) =>
    nextdepth = if depth == -1 then -1 else depth - 1

    buf = ''
    buf ..= if depth > 0 then '' else @white[0]
    if depth == 0
      buf ..= '...'
    else
      for i, child in ipairs @children
        buf ..= child\stringify nextdepth
        buf ..= if depth > 0 then ' ' else @white[i]

      if depth > 0
        buf = buf\sub 1, #buf - 1

    tag = @tag\stringify nextdepth

    '(' .. tag .. buf .. ')'

--- static functions
-- @section static

  --- construct a new Cell.
  --
  -- @classmethod
  -- @tparam[opt] Tag tag
  -- @tparam {AST,...} children
  -- @tparam[opt] {string,...} white whitespace strings
  new: (@tag=Tag!, @children, @white) =>
    if not @white
      @white = [' ' for i=1,#@children]
      @white[0] = ''

    assert #@white == #@children, "mismatched whitespace length"

  --- parse a Cell (for parsing with Lpeg).
  --
  -- @tparam[opt] Tag tag
  -- @tparam table parts
  -- @treturn Cell
  @parse: (...) ->
    tag, children, white = parse_args ...
    Cell tag, children, white

--- @type RootCell
class RootCell extends Cell
  head: => Constant.sym 'do'
  tail: => @children

  new: (tag=(Tag "root"), ...) => super tag, ...

  stringify: =>
    buf = ''
    buf ..= @white[0]

    for i, child in ipairs @children
      buf ..= child\stringify!
      buf ..= @white[i]

    buf

  --- parse a root Cell (for parsing with Lpeg).
  --
  -- Root-Cells are at the root of an ALV document.
  -- They have an implicit head of 'do' and a `[0]` tag.
  --
  -- @tparam table parts
  -- @treturn Cell
  @parse: (parts) =>
    _, children, white = parse_args nil, parts
    @@ nil, children, white

--- @type ArrayCell
class ArrayCell extends RootCell
  head: => Constant.sym 'mkarray'
  tail: => @children
  stringify: => '[' .. super! .. ']'

  eval: (...) =>
    assert #@children > 0, Error 'syntax', "array literal can't be empty"
    super ...

  new: (tag=(Tag "array"), ...) => Cell.__init @, tag, ...

--- @type StructCell
class StructCell extends RootCell
  head: => Constant.sym 'mkstruct'
  tail: => @children
  stringify: => '{' .. super! .. '}'

  eval: (...) =>
    assert #@children > 0, Error 'syntax', "struct literal can't be empty"
    assert #@children % 2 == 0, Error 'syntax', "struct literal must have even number of values"
    super ...

  new: (tag=(Tag "struct"), ...) => Cell.__init @, tag, ...

--- @type TemplateString
class TemplateString extends Cell
--- AST interface
--
-- `TemplateString` partially implements the `AST` interface.
-- @section ast

  --- stringify this TemplateString.
  --
  -- if `depth` is passed, does not faithfully recreate the original string but
  -- rather create useful debug output.
  --
  -- @tparam[opt] int depth the maximum depth, defaults to infinite
  -- @treturn string the exact string this TemplateString was parsed from
  stringify: (depth=-1) =>
    nextdepth = if depth == -1 then -1 else depth - 1

    strings = [s\gsub '(["\\$])', '\\%1' for s in *@children[2].node.result!]
    children = ['$' .. child\stringify nextdepth for child in *@children[3,]]
    str = @@.subst strings, children

    if depth > 0 and #str > 19
      str = str\gsub '\n', '\\n'
      str = (str\sub 1, 20) .. ''

    tag = @tag\stringify nextdepth
    head = @children[1]\stringify nextdepth
    "$#{tag}#{head}\"#{str}\""

--- static functions
-- @section static

  --- apply substitutions to a template string.
  --
  -- Equivalent to `strings[1] .. children[1] .. strings[2] … strings[N+1]`
  --
  -- @tparam {string,...} strings the pieces of template string
  -- @tparam {any,...} children the pieces to substitute
  -- @treturn string
  @subst: (strings, children) ->
    assert #strings == #children + 1, "need one more string than child to substitute"

    elems = {}
    for i, string in ipairs strings
      table.insert elems, string
      table.insert elems, children[i]

    table.concat elems

  --- parse a TemplateString (for parsing with Lpeg).
  --
  -- @classmethod
  -- @tparam string tag
  -- @tparam string head
  -- @tparam {string|AST,...} pieces
  -- @treturn TemplateString
  @parse: (tag, head, pieces) =>
    if not pieces
      tag, head, pieces = nil, tag, head

    strings, children = {''}, {}
    for elem in *pieces
      if 'string' == type elem
        strings[#strings] ..= elem
      else
        table.insert children, elem
        table.insert strings, ''

    if #strings == #children
      table.insert strings, ''
    assert #strings == #children + 1

    strings = [s\gsub '\\(["\\$])', '%1' for s in *strings]

    table.insert children, 1, head
    table.insert children, 2, Dummy.literal (Array #strings, T.str), strings
    @@ tag, children

{
  :Cell
  :RootCell
  :ArrayCell
  :StructCell
  :TemplateString
}