(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} = table4 {:Source} = require "code_obj"5 {:List, :Dict} = require 'containers'6 Files = require 'files'7 unpack or= table.unpack9 as_lua = =>10 if type(@) == 'number'11 return tostring(@)12 if mt = getmetatable(@)13 if _as_lua = mt.as_lua14 return _as_lua(@)15 return @as_lua! if @as_lua16 error("Not supported: #{@}")18 local SyntaxTree19 class SyntaxTree20 __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.type30 for i=1,#@31 return false if @[i] != other[i]32 return true34 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.source44 f = Files.read(s.filename)45 return f46 __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 = nil57 with: (fn)=>58 if type(fn) == 'table'59 replacements = fn60 fn = (t)->61 if t.type == "Var"62 if r = replacements[t\as_var!]63 return r65 replacement = fn(@)66 if replacement == false then return nil67 if replacement68 -- Clone the replacement, so we can give it a proper source/comments69 if SyntaxTree\is_instance(replacement)70 replacement = {k,v for k,v in pairs replacement}71 replacement.source = @source72 replacement.comments = {unpack(@comments)} if @comments73 replacement = SyntaxTree(replacement)74 else75 replacement = {source:@source, comments:@comments and {unpack(@comments)}}76 changes = false77 for k,v in pairs(@)78 replacement[k] = v79 if SyntaxTree\is_instance(v)80 r = v\with(fn)81 continue if r == v or r == nil82 changes = true83 replacement[k] = r84 return @ unless changes85 replacement = SyntaxTree(replacement)86 return replacement88 contains: (subtree)=>89 if subtree == @ then return true90 for k,v in pairs(@)91 if SyntaxTree\is_instance(v)92 return true if v\contains(subtree)93 return false95 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] = tok103 else104 for tok in *@105 if type(tok) != 'string' then args[#args+1] = tok106 return args108 get_stub: =>109 switch @type110 when "Action"111 stub_bits = {}112 arg_i = 1113 for a in *@114 if type(a) == 'string'115 stub_bits[#stub_bits+1] = a116 else117 stub_bits[#stub_bits+1] = arg_i118 arg_i += 1119 while type(stub_bits[#stub_bits]) == 'number'120 stub_bits[#stub_bits] = nil121 return concat stub_bits, " "122 when "MethodCall"123 return "0, "..table.concat([@[i]\get_stub! for i=2,#@], "; ")124 else125 error("#{@type}s do not have stubs")127 as_var: =>128 assert(@type == "Var")129 if type(@[1]) == 'string'130 return @[1]131 else132 return @[1]\get_stub!134 matching: (patt)=>135 if patt.type == "Var"136 return {[patt\as_var!]:@}137 return nil if patt.type != @type138 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 #@ != #patt141 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 == pv148 else149 m = v\matching(pv)150 return nil unless m151 for mk,mv in pairs(m)152 return nil if match[mk] and match[mk] != mv153 match[mk] = mv154 return Dict(match)156 _breadth_first: =>157 coroutine.yield @158 for child in *@159 if getmetatable(child) == SyntaxTree.__base160 child\_breadth_first!161 return162 breadth_first: => coroutine.create(-> @_breadth_first!)164 _depth_first: =>165 coroutine.yield @166 for child in *@167 if getmetatable(child) == SyntaxTree.__base168 child\_depth_first!169 return170 depth_first: => coroutine.create(-> @_depth_first!)172 @is_instance: (t)=>173 type(t) == 'table' and getmetatable(t) == @__base175 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 t187 return SyntaxTree