code / nomsu

Lines6.6K Lua5.1K PEG1.3K make117
2 others 83
Markdown60 Bourne Again Shell23
(187 lines)
1 -- This file contains the datastructures used to represent parsed Nomsu syntax trees,
2 -- as well as the logic for converting them to Lua code.
3 {:insert, :remove, :concat} = table
4 {:Source} = require "code_obj"
5 {:List, :Dict} = require 'containers'
6 Files = require 'files'
7 unpack or= table.unpack
9 as_lua = =>
10 if type(@) == 'number'
11 return tostring(@)
12 if mt = getmetatable(@)
13 if _as_lua = mt.as_lua
14 return _as_lua(@)
15 return @as_lua! if @as_lua
16 error("Not supported: #{@}")
18 local SyntaxTree
19 class SyntaxTree
20 __tostring: =>
21 bits = [type(b) == 'string' and b\as_lua! or tostring(b) for b in *@]
22 for k,v in pairs(@)
23 unless bits[k] or k == 'type' or k == 'source'
24 table.insert(bits, "#{k}=#{type(v) == 'string' and v\as_lua! or v}")
25 return "#{@type}{#{table.concat(bits, ", ")}}"
27 __eq: (other)=>
28 return false if type(@) != type(other) or #@ != #other or getmetatable(@) != getmetatable(other)
29 return false if @type != other.type
30 for i=1,#@
31 return false if @[i] != other[i]
32 return true
34 as_lua: =>
35 bits = [as_lua(b) for b in *@]
36 for k,v in pairs(@)
37 unless bits[k]
38 table.insert(bits, "[ #{as_lua(k)}]=#{as_lua(v)}")
39 return "SyntaxTree{#{table.concat(bits, ", ")}}"
41 @source_code_for_tree: setmetatable({}, {
42 __index:(t)=>
43 s = t.source
44 f = Files.read(s.filename)
45 return f
46 __mode: "k"
47 })
48 get_source_file: => @@source_code_for_tree[@]
49 get_source_code: => @@source_code_for_tree[@]\sub(@source.start, @source.stop-1)
51 add: (...)=>
52 n = #@
53 for i=1,select('#', ...)
54 @[n+i] = select(i, ...)
55 @stub = nil
57 with: (fn)=>
58 if type(fn) == 'table'
59 replacements = fn
60 fn = (t)->
61 if t.type == "Var"
62 if r = replacements[t\as_var!]
63 return r
65 replacement = fn(@)
66 if replacement == false then return nil
67 if replacement
68 -- Clone the replacement, so we can give it a proper source/comments
69 if SyntaxTree\is_instance(replacement)
70 replacement = {k,v for k,v in pairs replacement}
71 replacement.source = @source
72 replacement.comments = {unpack(@comments)} if @comments
73 replacement = SyntaxTree(replacement)
74 else
75 replacement = {source:@source, comments:@comments and {unpack(@comments)}}
76 changes = false
77 for k,v in pairs(@)
78 replacement[k] = v
79 if SyntaxTree\is_instance(v)
80 r = v\with(fn)
81 continue if r == v or r == nil
82 changes = true
83 replacement[k] = r
84 return @ unless changes
85 replacement = SyntaxTree(replacement)
86 return replacement
88 contains: (subtree)=>
89 if subtree == @ then return true
90 for k,v in pairs(@)
91 if SyntaxTree\is_instance(v)
92 return true if v\contains(subtree)
93 return false
95 get_args: =>
96 assert(@type == "Action" or @type == "MethodCall", "Only actions and method calls have arguments")
97 args = {}
98 if @type == "MethodCall"
99 args[1] = @[1]
100 for i=2,#@
101 for tok in *@[i]
102 if type(tok) != 'string' then args[#args+1] = tok
103 else
104 for tok in *@
105 if type(tok) != 'string' then args[#args+1] = tok
106 return args
108 get_stub: =>
109 switch @type
110 when "Action"
111 stub_bits = {}
112 arg_i = 1
113 for a in *@
114 if type(a) == 'string'
115 stub_bits[#stub_bits+1] = a
116 else
117 stub_bits[#stub_bits+1] = arg_i
118 arg_i += 1
119 while type(stub_bits[#stub_bits]) == 'number'
120 stub_bits[#stub_bits] = nil
121 return concat stub_bits, " "
122 when "MethodCall"
123 return "0, "..table.concat([@[i]\get_stub! for i=2,#@], "; ")
124 else
125 error("#{@type}s do not have stubs")
127 as_var: =>
128 assert(@type == "Var")
129 if type(@[1]) == 'string'
130 return @[1]
131 else
132 return @[1]\get_stub!
134 matching: (patt)=>
135 if patt.type == "Var"
136 return {[patt\as_var!]:@}
137 return nil if patt.type != @type
138 return nil if patt.type == "Action" and patt\get_stub! != @get_stub!
139 -- TODO: support vararg matches like (\(say 1 2 3), matching \(say *$values))
140 return nil if #@ != #patt
141 match = {}
142 for i=1,#@
143 v = @[i]
144 pv = patt[i]
145 return nil if type(v) != type(pv)
146 if type(v) != 'table'
147 return nil unless v == pv
148 else
149 m = v\matching(pv)
150 return nil unless m
151 for mk,mv in pairs(m)
152 return nil if match[mk] and match[mk] != mv
153 match[mk] = mv
154 return Dict(match)
156 _breadth_first: =>
157 coroutine.yield @
158 for child in *@
159 if getmetatable(child) == SyntaxTree.__base
160 child\_breadth_first!
161 return
162 breadth_first: => coroutine.create(-> @_breadth_first!)
164 _depth_first: =>
165 coroutine.yield @
166 for child in *@
167 if getmetatable(child) == SyntaxTree.__base
168 child\_depth_first!
169 return
170 depth_first: => coroutine.create(-> @_depth_first!)
172 @is_instance: (t)=>
173 type(t) == 'table' and getmetatable(t) == @__base
175 SyntaxTree.__base.__type = "a Syntax Tree"
177 getmetatable(SyntaxTree).__call = (t, ...)=>
178 if type(t.source) == 'string'
179 t.source = Source\from_string(t.source)
180 setmetatable(t, @__base)
181 for i=1,select("#", ...)
182 t[i] = select(i, ...)
183 if t.type == 'Action'
184 t.stub = t\get_stub!
185 return t
187 return SyntaxTree