code / nomsu

Lines6.6K Lua5.1K PEG1.3K make117
2 others 83
Markdown60 Bourne Again Shell23
(277 lines)
1 -- This file contains objects that are used to track code positions and incrementally
2 -- build up generated code, while keeping track of where it came from, and managing
3 -- indentation levels.
4 {:insert, :remove, :concat} = table
5 unpack or= table.unpack
6 local LuaCode, NomsuCode, Source
8 class Source
9 new: (@filename, @start, @stop)=>
11 @from_string: (str)=>
12 filename,start,stop = str\match("^@(.-)%[(%d+):(%d+)%]$")
13 unless filename
14 filename,start = str\match("^@(.-)%[(%d+)%]$")
15 return @(filename or str, tonumber(start or 1), tonumber(stop))
17 @is_instance: (x)=> type(x) == 'table' and x.__class == @
19 __tostring: => "@#{@filename}[#{@start}#{@stop and ':'..@stop or ''}]"
21 as_lua: => "Source(#{@filename\as_lua!}, #{@start}#{@stop and ', '..@stop or ''})"
23 __eq: (other)=>
24 getmetatable(@) == getmetatable(other) and @filename == other.filename and @start == other.start and @stop == other.stop
26 __lt: (other)=>
27 assert(@filename == other.filename, "Cannot compare sources from different files")
28 return if @start == other.start
29 (@stop or @start) < (other.stop or other.start)
30 else @start < other.start
32 __le: (other)=>
33 assert(@filename == other.filename, "Cannot compare sources from different files")
34 return if @start == other.start
35 (@stop or @start) <= (other.stop or other.start)
36 else @start <= other.start
38 __add: (offset)=>
39 if type(self) == 'number'
40 offset, self = self, offset
41 else if type(offset) != 'number' then error("Cannot add Source and #{type(offset)}")
42 return Source(@filename, @start+offset, @stop)
44 class Code
45 new: (...)=>
46 @bits = {}
47 @add(...)
49 @from: (source, ...)=>
50 inst = self(...)
51 if type(source) == 'string'
52 source = Source\from_string(source)
53 inst.source = source
54 return inst
56 @is_instance: (x)=> type(x) == 'table' and x.__class == @
58 text: =>
59 if @__str == nil
60 buff, indent = {}, 0
61 {:match, :gsub, :rep} = string
62 for i,b in ipairs @bits
63 if type(b) == 'string'
64 if spaces = match(b, "\n([ ]*)[^\n]*$")
65 indent = #spaces
66 else
67 b = b\text!
68 if indent > 0
69 b = gsub(b, "\n", "\n"..rep(" ", indent))
70 buff[#buff+1] = b
71 @__str = concat(buff, "")
72 return @__str
74 last: (n)=>
75 if @__str
76 return @__str\sub(-n, -1)
77 last = ""
78 for i=#@bits,1,-1
79 b = @bits[i]
80 last = (type(b) == 'string' and b\sub(-(n-#last)) or b\last(n-#last))..last
81 break if #last == n
82 return last
84 first: (n)=>
85 if @__str
86 return @__str\sub(1,n)
87 first = ""
88 for b in *@bits
89 first ..= type(b) == 'string' and b\sub(1,n-#first+1) or b\first(n-#first+1)
90 break if #first == n
91 return first
93 __tostring: => @text!
95 as_lua: =>
96 if @source
97 "#{@__class.__name}:from(#{concat {tostring(@source)\as_lua!, unpack([b\as_lua! for b in *@bits])}, ", "})"
98 else
99 "#{@__class.__name}(#{concat [b\as_lua! for b in *@bits], ", "})"
101 __len: =>
102 if @__str
103 return #@__str
104 len = 0
105 for b in *@bits
106 len += #b
107 return len
109 match: (...)=> @text!\match(...)
111 gmatch: (...)=> @text!\gmatch(...)
113 dirty: =>
114 @__str = nil
115 @_trailing_line_len = nil
116 -- Multi-line only goes from false->true, since there is no API for removing bits
117 @_is_multiline = nil if @_is_multiline == false
119 add: (...)=>
120 n = select("#",...)
121 match = string.match
122 bits = @bits
123 for i=1,n
124 b = select(i, ...)
125 assert(b, "code bit is nil")
126 assert(not Source\is_instance(b), "code bit is a Source")
127 if b == '' then continue
128 bits[#bits+1] = b
129 @dirty!
131 trailing_line_len: =>
132 if @_trailing_line_len == nil
133 @_trailing_line_len = #@text!\match("[^\n]*$")
134 return @_trailing_line_len
136 is_multiline: =>
137 if @_is_multiline == nil
138 match = string.match
139 @_is_multiline = false
140 for b in *@bits
141 if type(b) == 'string'
142 if match(b, '\n')
143 @_is_multiline = true
144 break
145 elseif b\is_multiline!
146 @_is_multiline = true
147 break
148 return @_is_multiline
150 concat_add: (values, joiner, wrapping_joiner)=>
151 wrapping_joiner or= joiner
152 match = string.match
153 bits = @bits
154 line_len = 0
155 for i=1,#values
156 b = values[i]
157 if i > 1
158 if line_len > 80
159 bits[#bits+1] = wrapping_joiner
160 line_len = 0
161 else
162 bits[#bits+1] = joiner
163 bits[#bits+1] = b
164 b.dirty = error if type(b) != 'string'
165 line = b\match("\n([^\n]*)$")
166 if line
167 line_len = #line
168 else
169 line_len += #b
170 @dirty!
172 prepend: (...)=>
173 n = select("#",...)
174 bits = @bits
175 for i=#bits+n,n+1,-1
176 bits[i] = bits[i-n]
177 for i=1,n
178 b = select(i, ...)
179 b.dirty = error if type(b) != 'string'
180 bits[i] = b
181 @dirty!
183 parenthesize: =>
184 @prepend "("
185 @add ")"
187 class LuaCode extends Code
188 __tostring: Code.__tostring
189 as_lua: Code.as_lua
190 __len: Code.__len
191 new: (...)=>
192 super ...
193 @free_vars = {}
195 add_free_vars: (vars)=>
196 return unless #vars > 0
197 seen = {[v]:true for v in *@free_vars}
198 for var in *vars
199 assert type(var) == 'string'
200 unless seen[var]
201 @free_vars[#@free_vars+1] = var
202 seen[var] = true
203 @dirty!
205 remove_free_vars: (vars=nil)=>
206 vars or= @get_free_vars!
207 return unless #vars > 0
208 removals = {}
209 for var in *vars
210 assert type(var) == 'string'
211 removals[var] = true
213 stack = {self}
214 while #stack > 0
215 lua, stack[#stack] = stack[#stack], nil
216 for i=#lua.free_vars,1,-1
217 free_var = lua.free_vars[i]
218 if removals[free_var]
219 remove lua.free_vars, i
220 for b in *lua.bits
221 if type(b) != 'string'
222 stack[#stack+1] = b
223 @dirty!
225 get_free_vars: =>
226 vars, seen = {}, {}
227 gather_from = =>
228 for var in *@free_vars
229 unless seen[var]
230 seen[var] = true
231 vars[#vars+1] = var
232 for bit in *@bits
233 unless type(bit) == 'string'
234 gather_from bit
235 gather_from self
236 return vars
238 declare_locals: (to_declare=nil)=>
239 to_declare or= @get_free_vars!
240 if #to_declare > 0
241 @remove_free_vars to_declare
242 @prepend "local #{concat to_declare, ", "};\n"
243 return to_declare
245 make_offset_table: =>
246 assert @source, "This code doesn't have a source"
247 -- Return a mapping from output (lua) character number to input (nomsu) character number
248 lua_to_nomsu, nomsu_to_lua = {}, {}
249 walk = (lua, pos)->
250 for b in *lua.bits
251 if type(b) == 'string'
252 if lua.source
253 lua_to_nomsu[pos] = lua.source.start
254 nomsu_to_lua[lua.source.start] = pos
255 else
256 walk b, pos
257 pos += #b
258 walk self, 1
259 return {
260 nomsu_filename:@source.filename
261 lua_filename:tostring(@source)..".lua", lua_file:@text!
262 :lua_to_nomsu, :nomsu_to_lua
265 parenthesize: =>
266 @prepend "("
267 @add ")"
269 class NomsuCode extends Code
270 __tostring: Code.__tostring
271 as_lua: Code.as_lua
272 __len: Code.__len
274 Code.__base.add_1_joined_with = assert Code.__base.concat_add
275 Code.__base.add = assert Code.__base.add
277 return {:Code, :NomsuCode, :LuaCode, :Source}