aboutsummaryrefslogtreecommitdiff
path: root/syntax_tree.moon
blob: 1800fcbd4b0b6837147a25e11c2224ae5dce8b57 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
-- 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"
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: #{@}")

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)->
                for k,v in pairs(replacements)
                    if k == t then return v

        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: =>
        if @type == "MethodCall"
            return "0, "..table.concat([@[i]\get_stub! for i=2,#@], "; ")
        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, " "

    as_var: =>
        assert(@type == "Var")
        if type(@[1]) == 'string'
            return @[1]
        else
            return @[1]\get_stub!

    @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