188 lines
6.1 KiB
Plaintext
188 lines
6.1 KiB
Plaintext
-- This file contains the datastructures used to represent parsed Nomsu syntax trees,
|
|
-- as well as the logic for converting them to Lua code.
|
|
{:insert, :remove, :concat} = table
|
|
{:Source} = require "code_obj"
|
|
{:List, :Dict} = require 'containers'
|
|
Files = require 'files'
|
|
unpack or= table.unpack
|
|
|
|
as_lua = =>
|
|
if type(@) == 'number'
|
|
return tostring(@)
|
|
if mt = getmetatable(@)
|
|
if _as_lua = mt.as_lua
|
|
return _as_lua(@)
|
|
return @as_lua! if @as_lua
|
|
error("Not supported: #{@}")
|
|
|
|
local SyntaxTree
|
|
class SyntaxTree
|
|
__tostring: =>
|
|
bits = [type(b) == 'string' and b\as_lua! or tostring(b) for b in *@]
|
|
for k,v in pairs(@)
|
|
unless bits[k] or k == 'type' or k == 'source'
|
|
table.insert(bits, "#{k}=#{type(v) == 'string' and v\as_lua! or v}")
|
|
return "#{@type}{#{table.concat(bits, ", ")}}"
|
|
|
|
__eq: (other)=>
|
|
return false if type(@) != type(other) or #@ != #other or getmetatable(@) != getmetatable(other)
|
|
return false if @type != other.type
|
|
for i=1,#@
|
|
return false if @[i] != other[i]
|
|
return true
|
|
|
|
as_lua: =>
|
|
bits = [as_lua(b) for b in *@]
|
|
for k,v in pairs(@)
|
|
unless bits[k]
|
|
table.insert(bits, "[ #{as_lua(k)}]=#{as_lua(v)}")
|
|
return "SyntaxTree{#{table.concat(bits, ", ")}}"
|
|
|
|
@source_code_for_tree: setmetatable({}, {
|
|
__index:(t)=>
|
|
s = t.source
|
|
f = Files.read(s.filename)
|
|
return f
|
|
__mode: "k"
|
|
})
|
|
get_source_file: => @@source_code_for_tree[@]
|
|
get_source_code: => @@source_code_for_tree[@]\sub(@source.start, @source.stop-1)
|
|
|
|
add: (...)=>
|
|
n = #@
|
|
for i=1,select('#', ...)
|
|
@[n+i] = select(i, ...)
|
|
@stub = nil
|
|
|
|
with: (fn)=>
|
|
if type(fn) == 'table'
|
|
replacements = fn
|
|
fn = (t)->
|
|
if t.type == "Var"
|
|
if r = replacements[t\as_var!]
|
|
return r
|
|
|
|
replacement = fn(@)
|
|
if replacement == false then return nil
|
|
if replacement
|
|
-- Clone the replacement, so we can give it a proper source/comments
|
|
if SyntaxTree\is_instance(replacement)
|
|
replacement = {k,v for k,v in pairs replacement}
|
|
replacement.source = @source
|
|
replacement.comments = {unpack(@comments)} if @comments
|
|
replacement = SyntaxTree(replacement)
|
|
else
|
|
replacement = {source:@source, comments:@comments and {unpack(@comments)}}
|
|
changes = false
|
|
for k,v in pairs(@)
|
|
replacement[k] = v
|
|
if SyntaxTree\is_instance(v)
|
|
r = v\with(fn)
|
|
continue if r == v or r == nil
|
|
changes = true
|
|
replacement[k] = r
|
|
return @ unless changes
|
|
replacement = SyntaxTree(replacement)
|
|
return replacement
|
|
|
|
contains: (subtree)=>
|
|
if subtree == @ then return true
|
|
for k,v in pairs(@)
|
|
if SyntaxTree\is_instance(v)
|
|
return true if v\contains(subtree)
|
|
return false
|
|
|
|
get_args: =>
|
|
assert(@type == "Action" or @type == "MethodCall", "Only actions and method calls have arguments")
|
|
args = {}
|
|
if @type == "MethodCall"
|
|
args[1] = @[1]
|
|
for i=2,#@
|
|
for tok in *@[i]
|
|
if type(tok) != 'string' then args[#args+1] = tok
|
|
else
|
|
for tok in *@
|
|
if type(tok) != 'string' then args[#args+1] = tok
|
|
return args
|
|
|
|
get_stub: =>
|
|
switch @type
|
|
when "Action"
|
|
stub_bits = {}
|
|
arg_i = 1
|
|
for a in *@
|
|
if type(a) == 'string'
|
|
stub_bits[#stub_bits+1] = a
|
|
else
|
|
stub_bits[#stub_bits+1] = arg_i
|
|
arg_i += 1
|
|
while type(stub_bits[#stub_bits]) == 'number'
|
|
stub_bits[#stub_bits] = nil
|
|
return concat stub_bits, " "
|
|
when "MethodCall"
|
|
return "0, "..table.concat([@[i]\get_stub! for i=2,#@], "; ")
|
|
else
|
|
error("#{@type}s do not have stubs")
|
|
|
|
as_var: =>
|
|
assert(@type == "Var")
|
|
if type(@[1]) == 'string'
|
|
return @[1]
|
|
else
|
|
return @[1]\get_stub!
|
|
|
|
matching: (patt)=>
|
|
if patt.type == "Var"
|
|
return {[patt\as_var!]:@}
|
|
return nil if patt.type != @type
|
|
return nil if patt.type == "Action" and patt\get_stub! != @get_stub!
|
|
-- TODO: support vararg matches like (\(say 1 2 3), matching \(say *$values))
|
|
return nil if #@ != #patt
|
|
match = {}
|
|
for i=1,#@
|
|
v = @[i]
|
|
pv = patt[i]
|
|
return nil if type(v) != type(pv)
|
|
if type(v) != 'table'
|
|
return nil unless v == pv
|
|
else
|
|
m = v\matching(pv)
|
|
return nil unless m
|
|
for mk,mv in pairs(m)
|
|
return nil if match[mk] and match[mk] != mv
|
|
match[mk] = mv
|
|
return Dict(match)
|
|
|
|
_breadth_first: =>
|
|
coroutine.yield @
|
|
for child in *@
|
|
if getmetatable(child) == SyntaxTree.__base
|
|
child\_breadth_first!
|
|
return
|
|
breadth_first: => coroutine.create(-> @_breadth_first!)
|
|
|
|
_depth_first: =>
|
|
coroutine.yield @
|
|
for child in *@
|
|
if getmetatable(child) == SyntaxTree.__base
|
|
child\_depth_first!
|
|
return
|
|
depth_first: => coroutine.create(-> @_depth_first!)
|
|
|
|
@is_instance: (t)=>
|
|
type(t) == 'table' and getmetatable(t) == @__base
|
|
|
|
SyntaxTree.__base.__type = "a Syntax Tree"
|
|
|
|
getmetatable(SyntaxTree).__call = (t, ...)=>
|
|
if type(t.source) == 'string'
|
|
t.source = Source\from_string(t.source)
|
|
setmetatable(t, @__base)
|
|
for i=1,select("#", ...)
|
|
t[i] = select(i, ...)
|
|
if t.type == 'Action'
|
|
t.stub = t\get_stub!
|
|
return t
|
|
|
|
return SyntaxTree
|