(277 lines)
1 -- This file contains objects that are used to track code positions and incrementally2 -- build up generated code, while keeping track of where it came from, and managing3 -- indentation levels.4 {:insert, :remove, :concat} = table5 unpack or= table.unpack6 local LuaCode, NomsuCode, Source8 class Source9 new: (@filename, @start, @stop)=>11 @from_string: (str)=>12 filename,start,stop = str\match("^@(.-)%[(%d+):(%d+)%]$")13 unless filename14 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.stop26 __lt: (other)=>27 assert(@filename == other.filename, "Cannot compare sources from different files")28 return if @start == other.start29 (@stop or @start) < (other.stop or other.start)30 else @start < other.start32 __le: (other)=>33 assert(@filename == other.filename, "Cannot compare sources from different files")34 return if @start == other.start35 (@stop or @start) <= (other.stop or other.start)36 else @start <= other.start38 __add: (offset)=>39 if type(self) == 'number'40 offset, self = self, offset41 else if type(offset) != 'number' then error("Cannot add Source and #{type(offset)}")42 return Source(@filename, @start+offset, @stop)44 class Code45 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 = source54 return inst56 @is_instance: (x)=> type(x) == 'table' and x.__class == @58 text: =>59 if @__str == nil60 buff, indent = {}, 061 {:match, :gsub, :rep} = string62 for i,b in ipairs @bits63 if type(b) == 'string'64 if spaces = match(b, "\n([ ]*)[^\n]*$")65 indent = #spaces66 else67 b = b\text!68 if indent > 069 b = gsub(b, "\n", "\n"..rep(" ", indent))70 buff[#buff+1] = b71 @__str = concat(buff, "")72 return @__str74 last: (n)=>75 if @__str76 return @__str\sub(-n, -1)77 last = ""78 for i=#@bits,1,-179 b = @bits[i]80 last = (type(b) == 'string' and b\sub(-(n-#last)) or b\last(n-#last))..last81 break if #last == n82 return last84 first: (n)=>85 if @__str86 return @__str\sub(1,n)87 first = ""88 for b in *@bits89 first ..= type(b) == 'string' and b\sub(1,n-#first+1) or b\first(n-#first+1)90 break if #first == n91 return first93 __tostring: => @text!95 as_lua: =>96 if @source97 "#{@__class.__name}:from(#{concat {tostring(@source)\as_lua!, unpack([b\as_lua! for b in *@bits])}, ", "})"98 else99 "#{@__class.__name}(#{concat [b\as_lua! for b in *@bits], ", "})"101 __len: =>102 if @__str103 return #@__str104 len = 0105 for b in *@bits106 len += #b107 return len109 match: (...)=> @text!\match(...)111 gmatch: (...)=> @text!\gmatch(...)113 dirty: =>114 @__str = nil115 @_trailing_line_len = nil116 -- Multi-line only goes from false->true, since there is no API for removing bits117 @_is_multiline = nil if @_is_multiline == false119 add: (...)=>120 n = select("#",...)121 match = string.match122 bits = @bits123 for i=1,n124 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 continue128 bits[#bits+1] = b129 @dirty!131 trailing_line_len: =>132 if @_trailing_line_len == nil133 @_trailing_line_len = #@text!\match("[^\n]*$")134 return @_trailing_line_len136 is_multiline: =>137 if @_is_multiline == nil138 match = string.match139 @_is_multiline = false140 for b in *@bits141 if type(b) == 'string'142 if match(b, '\n')143 @_is_multiline = true144 break145 elseif b\is_multiline!146 @_is_multiline = true147 break148 return @_is_multiline150 concat_add: (values, joiner, wrapping_joiner)=>151 wrapping_joiner or= joiner152 match = string.match153 bits = @bits154 line_len = 0155 for i=1,#values156 b = values[i]157 if i > 1158 if line_len > 80159 bits[#bits+1] = wrapping_joiner160 line_len = 0161 else162 bits[#bits+1] = joiner163 bits[#bits+1] = b164 b.dirty = error if type(b) != 'string'165 line = b\match("\n([^\n]*)$")166 if line167 line_len = #line168 else169 line_len += #b170 @dirty!172 prepend: (...)=>173 n = select("#",...)174 bits = @bits175 for i=#bits+n,n+1,-1176 bits[i] = bits[i-n]177 for i=1,n178 b = select(i, ...)179 b.dirty = error if type(b) != 'string'180 bits[i] = b181 @dirty!183 parenthesize: =>184 @prepend "("185 @add ")"187 class LuaCode extends Code188 __tostring: Code.__tostring189 as_lua: Code.as_lua190 __len: Code.__len191 new: (...)=>192 super ...193 @free_vars = {}195 add_free_vars: (vars)=>196 return unless #vars > 0197 seen = {[v]:true for v in *@free_vars}198 for var in *vars199 assert type(var) == 'string'200 unless seen[var]201 @free_vars[#@free_vars+1] = var202 seen[var] = true203 @dirty!205 remove_free_vars: (vars=nil)=>206 vars or= @get_free_vars!207 return unless #vars > 0208 removals = {}209 for var in *vars210 assert type(var) == 'string'211 removals[var] = true213 stack = {self}214 while #stack > 0215 lua, stack[#stack] = stack[#stack], nil216 for i=#lua.free_vars,1,-1217 free_var = lua.free_vars[i]218 if removals[free_var]219 remove lua.free_vars, i220 for b in *lua.bits221 if type(b) != 'string'222 stack[#stack+1] = b223 @dirty!225 get_free_vars: =>226 vars, seen = {}, {}227 gather_from = =>228 for var in *@free_vars229 unless seen[var]230 seen[var] = true231 vars[#vars+1] = var232 for bit in *@bits233 unless type(bit) == 'string'234 gather_from bit235 gather_from self236 return vars238 declare_locals: (to_declare=nil)=>239 to_declare or= @get_free_vars!240 if #to_declare > 0241 @remove_free_vars to_declare242 @prepend "local #{concat to_declare, ", "};\n"243 return to_declare245 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 number248 lua_to_nomsu, nomsu_to_lua = {}, {}249 walk = (lua, pos)->250 for b in *lua.bits251 if type(b) == 'string'252 if lua.source253 lua_to_nomsu[pos] = lua.source.start254 nomsu_to_lua[lua.source.start] = pos255 else256 walk b, pos257 pos += #b258 walk self, 1259 return {260 nomsu_filename:@source.filename261 lua_filename:tostring(@source)..".lua", lua_file:@text!262 :lua_to_nomsu, :nomsu_to_lua263 }265 parenthesize: =>266 @prepend "("267 @add ")"269 class NomsuCode extends Code270 __tostring: Code.__tostring271 as_lua: Code.as_lua272 __len: Code.__len274 Code.__base.add_1_joined_with = assert Code.__base.concat_add275 Code.__base.add = assert Code.__base.add277 return {:Code, :NomsuCode, :LuaCode, :Source}