#!/usr/bin/ruby

#
## Boolean type
#
class SBool(Bool val) {

}

#
## String type + methods
#
class SString(String val) {
    method +(SString arg) {
        SString(val + arg.val);
    }

    method say() {
        self + SString("\n") -> print;
    }

    method print() {
        SBool(Sys.print(val));
    }
}

class Interpreter {

    #
    ## Expression executor
    #
    method execute_expr(statement) {
        statement.has_key(:self) || die "Invalid AST!";
        var self_obj = statement{:self};

        if (self_obj.is_a(Hash)) {
            self_obj = self.execute(self_obj);
        }

        if (statement.has_key(:call)) {
            statement{:call}.each { |call|

                var meth = call{:method};

                if (call.has_key(:arg)) {
                    var args = call{:arg}.map {|arg|
                        arg.is_a(Hash) ? self.execute_expr(arg) : arg
                    };
                    self_obj = self_obj.(meth)(args...);
                }
                else {
                    self_obj = self_obj.(meth);
                }

            }
        };

        return self_obj;
    }

    #
    ## Parse-tree executor
    #
    method execute(structure) {
        var results = [];
        structure.has_key(:main) || die "Invalid AST!";
        structure{:main}.each { |statement|
            results.append(self.execute_expr(statement));
        };
        results[-1];
    }
}

#
## The AST
#
var ast = Hash.new(
    :main => [
        Hash.new(
            :call => [Hash.new(:method => "print")],
            :self => Hash.new(
                :main => [
                    Hash.new(
                        :call => [Hash.new(:method => "+", :arg => [Hash.new(:self => SString("llo"))])],
                        :self => SString("he"),
                    )
                ],
            )
        ),
        Hash.new(
            :call => [Hash.new(:method => "say")],
            :self => SString(" world!");
        ),
    ]
);

#
## Begin execution
#
var intr = Interpreter();
intr.execute(ast);