aboutsummaryrefslogtreecommitdiffstats
path: root/alv/cell.moon
blob: 36d5f43a08d0504c2d464f8152a38543e554716c (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
----
-- 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 from require 'alv.type'
import Constant from require 'alv.result'
import Error from require 'alv.error'
import op_invoke, fn_invoke from require 'alv.invoke'
import Tag from require 'alv.tag'

local RootCell

class Cell
--- members
-- @section members

  new: (@tag=Tag.blank!, @children, @white) =>
    if not @white
      @white = [' ' for i=1,#@children]
      @white[0] = ''

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

  --- 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]
    Cell 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) =>
    buf = ''
    buf ..= if depth > 0 then '' else @white[0]
    if depth == 0
      buf ..= '...'
    else
      for i, child in ipairs @children
        buf ..= child\stringify if depth == -1 then -1 else depth - 1
        buf ..= if depth > 0 then ' ' else @white[i]

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

    tag = if depth == -1 then @tag\stringify! else ''

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

--- static functions
-- @section static

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

  --- 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_root: (...) ->
    tag, children, white = parse_args (Tag.parse '0'), ...
    RootCell tag, children, white

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

  clone: (parent) =>
    tag = @tag\clone parent
    children = [child\clone parent for child in *@children]
    RootCell tag, children, @white

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

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

    buf

{
  :Cell
  :RootCell
}