aboutsummaryrefslogtreecommitdiff
path: root/lib/class.nom
blob: 7099d4841e14694c3f3cb902f82cc7e31ed1994e (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
use "lib/core.nom"

compile [@%var] to:
    lua> ".."
        local key_lua = repr(\%var.value);
        local key_attr = (key_lua:match("'([a-zA-Z][a-zA-Z0-9]*)'")
                       or key_lua:match('"([a-zA-Z][a-zA-Z0-9]*)"'));
        if key_attr then
            return "_me."..key_attr;
        elseif key_lua:sub(1,1) == "[" then
            key_lua = " "..key_lua.." ";
        end
        return "_me["..key_lua.."]";

compile [set @%var = %val] to code:
    lua> ".."
        local val_lua = \(%val as lua);
        local key_lua = repr(\%var.value);
        local key_attr = (key_lua:match("'([a-zA-Z][a-zA-Z0-9]*)'")
                       or key_lua:match('"([a-zA-Z][a-zA-Z0-9]*)"'));
        if key_attr then
            return "_me."..key_attr.." = "..val_lua..";";
        elseif key_lua:sub(1,1) == "[" then
            key_lua = " "..key_lua.." ";
        end
        return "_me["..key_lua.."] = "..val_lua..";";

compile [object %classname %class_body] to:
    local [%methods]
    set %methods = []
    for %line in (%class_body's "value"):
        if ((%line's "type") == "Comment"): do next %line
        assume (((%line's "type") == "FunctionCall") and ((%line's "stub") == "action % %"))
        ..or barf "Only action definitions are supported inside 'object % %', not \(%line's "src")"
        lua> ".."
            assert(\%line.value[3].type == "Block",
                "Invalid type for action definition body. Expected Block, but got: "..tostring(\%line.value[3].type));
            local names, args = nomsu:parse_spec(\%line.value[2]);
            local stub = nomsu:get_stub(names[1]);
            local body_lua = nomsu:tree_to_lua(\%line.value[3]);
            body_lua = body_lua.statements or ("return "..body_lua.expr..";");
            table.insert(\%methods, ([[
            {   aliases=%s,
                        line=%s,
                        source=%s,
                        action=function(%s)
                            %s
                        end,
                        compile_to=function(%s)
                            return {expr="("..nomsu:tree_to_lua(_me).expr..")[ "..%s.."]("..nomsu:tree_to_lua(%s).expr..")"};
                        end,
                    }]]):format(repr(names), repr(\%line:get_line_no()), repr(nomsu:dedent(\%line.src)),
                        table.concat(args, ", "), body_lua,
                        table.concat(args, ", "), repr(repr(stub)), table.concat(args, ").expr..nomsu:tree_to_lua(")));
    
    local %class_identifier
    set %class_identifier = (=lua "nomsu:var_to_lua_identifier(\(%classname as value)):sub(2,-1)")
    if (%class_identifier == ""):
        set %class_identifier = "class"

    return ".."
        do -- \%class_identifier
            -- Create the class object:
            local \%class_identifier = setmetatable({
                name=\(%classname as lua), instances=setmetatable({}, {__mode="k"}),
            }, {
                __tostring=function(c) return c.name; end,
                __call=function(cls, inst)
                    inst = inst or {};
                    inst.id = tostring(inst):match('table: (.*)');
                    setmetatable(inst, cls.instance_metatable);
                    cls.instances[inst] = true;
                    if inst['set % up'] then
                        inst['set % up'](inst);
                    end
                    return inst;
                end,
            });
            \%class_identifier.class = \%class_identifier;

            -- Define the methods:
            \%class_identifier.methods = {
                \(join %methods with ",\n        ")
            }

            -- Define nomsu compile time actions that rewrite "%foo baz %x" to %foo['% baz %'](%foo, %x)
            -- so classes can have overlapping method names:
            for i,method in ipairs(\%class_identifier.methods) do
                for a_i, alias in ipairs(method.aliases) do
                    \%class_identifier[nomsu:get_stub(alias)] = method.action;
                end
                ACTION_METADATA[method.action] = {
                    fn=method.action, src=method.source, line_no=method.line, aliases=method.aliases,
                };
                nomsu:define_compile_action(method.aliases, method.line, method.compile_to, method.source);
            end

            -- Define class methods for instantiating and accessing instances:
            \%class_identifier.instance_metatable = {
                __index=\%class_identifier,
                __tostring=\%class_identifier['% as text'] or function(inst)
                    return "<"..inst.class.name..": "..inst.id..">";
                end,
            };
            nomsu:define_action("instances of "..\%class_identifier.name, "lib/class.nom", function()
                return utils.keys(\%class_identifier.instances);
            end, "");
            nomsu:define_action("new "..\%class_identifier.name.." %instance", "lib/class.nom", function(_instance)
                return \%class_identifier(_instance);
            end, "");
            nomsu:define_action("new "..\%class_identifier.name, "lib/class.nom", function()
                return \%class_identifier({});
            end, "");
        end -- End of definition of \%class_identifier
        \("\n\n")