diff --git a/doc/grammar.bnf b/doc/grammar.bnf index a5cbc9f..b34640f 100644 --- a/doc/grammar.bnf +++ b/doc/grammar.bnf @@ -3,4 +3,8 @@ EXPR ::= | int | float | bool | string | ident | CALL -CALL opar ident EXPR* cpar +| LAMBDA +CALL ::= opar EXPR EXPR* cpar +LAMBDA ::= opar rarrow opar PARAMS cpar BODY cpar +PARAMS ::= ident* +BODY ::= EXPR* diff --git a/examples/lambda.fk b/examples/lambda.fk new file mode 100644 index 0000000..e93dc5b --- /dev/null +++ b/examples/lambda.fk @@ -0,0 +1,33 @@ +;; CALL LAMBDA +;; =========== + +(assert= 2 ( + (-> (x y) x) + 2 3 +)) + +(assert= 3 ( + (-> (x y) y) + 2 3 +)) + +(assert= 7 ( + (-> (x) + ($ a 39) + x) + 7)) + +;; CALL IDENT +;; ========== + +($ a (-> (x y) y)) + +(assert= 9 (a 3 9)) + +;; RETURN LAMBDA +;; ============= + +($ b (-> () (-> () 32))) +(assert= 32 ((b))) + +(assert-static-fail ((b 7))) diff --git a/libstd/macro.cpp b/libstd/macro.cpp index 744a4a1..223aa57 100644 --- a/libstd/macro.cpp +++ b/libstd/macro.cpp @@ -1,4 +1,5 @@ #include "macro.hpp" +#include "src/Compiler.hpp" namespace fkstd { @@ -35,9 +36,10 @@ namespace fkstd compiler.compile_prog(rhs, program); - compiler.sym()->declare_local(ident, - addr, - node->loc()); + auto entry = compiler.sym()->declare_local(ident, + addr, + node->loc()) + .set_node(rhs); program->add(OP_STORE_LOCAL, addr); } @@ -59,7 +61,5 @@ namespace fkstd } compiler.sym()->leave_scope(); - - // std::cout << compiler.sym()->string() << std::endl; } } diff --git a/libstd/macro.hpp b/libstd/macro.hpp index 70a88be..9edb4da 100644 --- a/libstd/macro.hpp +++ b/libstd/macro.hpp @@ -15,7 +15,6 @@ namespace fkstd void block(Compiler& compiler, std::shared_ptr node, std::shared_ptr program); - } #endif diff --git a/meson.build b/meson.build index d91ea7c..d18e891 100644 --- a/meson.build +++ b/meson.build @@ -29,6 +29,7 @@ fakir_cpp = [ 'src/Parser.cpp', 'src/Constant.cpp', 'src/NativeFunction.cpp', + 'src/Lambda.cpp', 'src/NativeMacro.cpp', 'src/SymTable.cpp', 'src/SymEntry.cpp', @@ -53,6 +54,7 @@ fakir_hpp = [ 'src/Program.hpp', 'src/Module.hpp', 'src/NativeFunction.hpp', + 'src/Lambda.hpp', 'src/NativeMacro.hpp', 'src/opcodes.hpp', 'src/commons.hpp', diff --git a/src/Compiler.cpp b/src/Compiler.cpp index ad062c6..023e20d 100644 --- a/src/Compiler.cpp +++ b/src/Compiler.cpp @@ -1,4 +1,5 @@ #include "Compiler.hpp" +#include "Lambda.hpp" namespace fk { @@ -37,6 +38,48 @@ namespace fk } } break; + case NODE_BODY: { + for (size_t i=0; isize(); i++) + { + compile_prog(node->child(i), prog); + + if (i != node->size() - 1) + { + prog->add(OP_POP); + } + } + } break; + + case NODE_LAMBDA: { + auto params = node->child(0); + auto body = node->child(1); + + m_sym->enter_scope(node); + + for (size_t i=0; isize(); i++) + { + std::string ident = params->child(i)->repr(); + m_sym->declare_local(ident, i, node->loc()); + } + + Compiler compiler {m_sym}; + for (auto e: m_macros) + { + compiler.add_macro(e.first, e.second); + } + + auto program = compiler.compile(body); + program->add(OP_RET); + + m_sym->leave_scope(); + + auto constant = std::make_shared(TYPE_PROGRAM, + program, + node->loc()); + prog->load_const(constant); + prog->add(OP_MAKE_FUNCTION, params->size()); + } break; + case NODE_CALL: { std::string ident = node->child(0)->repr(); @@ -53,7 +96,37 @@ namespace fk compile_prog(node->child(i), prog); } - prog->add(OP_CALL_NATIVE, node->size() - 1); + if (node->child(0)->type() == NODE_LAMBDA + || node->child(0)->type() == NODE_CALL) + { + prog->add(OP_CALL_REF, node->size() - 1); + } + else if (node->child(0)->type() == NODE_IDENT) + { + if (auto entry = m_sym->find(node->child(0)->repr()); + entry && entry->is_global() == false) + { + size_t arity = entry->node()->child(0)->size(); + if (arity != node->size() - 1) + { + std::stringstream ss; + ss << "arity mismatch: " << node->child(0)->repr() + << " expect '" + << arity + << "' arguments, got '" + << node->size() - 1 + << "'"; + + node->loc().error(LOG_ERROR, ss.str()); + } + + prog->add(OP_CALL_REF, node->size() - 1); + } + else + { + prog->add(OP_CALL_NATIVE, node->size() - 1); + } + } } } break; @@ -116,5 +189,4 @@ namespace fk } break; } } - } diff --git a/src/Compiler.hpp b/src/Compiler.hpp index e443e94..7d69bda 100644 --- a/src/Compiler.hpp +++ b/src/Compiler.hpp @@ -11,6 +11,10 @@ namespace fk { FK_ERROR(compile_error); + struct DeclContext { + size_t arity; + }; + class Compiler { public: diff --git a/src/Constant.cpp b/src/Constant.cpp index 575acd9..d9a03eb 100644 --- a/src/Constant.cpp +++ b/src/Constant.cpp @@ -1,4 +1,5 @@ #include "Constant.hpp" +#include "Program.hpp" namespace fk { diff --git a/src/Constant.hpp b/src/Constant.hpp index 1546e42..0276a85 100644 --- a/src/Constant.hpp +++ b/src/Constant.hpp @@ -9,7 +9,15 @@ namespace fk { FK_ERROR(constant_error); - using constant_t = std::variant; + class Program; + + using constant_t = std::variant + >; class Constant { diff --git a/src/Lambda.cpp b/src/Lambda.cpp new file mode 100644 index 0000000..fa239bf --- /dev/null +++ b/src/Lambda.cpp @@ -0,0 +1,14 @@ +#include "Lambda.hpp" + +namespace fk +{ + /*explicit*/ Lambda::Lambda(std::shared_ptr program, size_t arity) + : m_program { program } + , m_arity { arity } + { + } + + /*virtual*/ Lambda::~Lambda() + { + } +} diff --git a/src/Lambda.hpp b/src/Lambda.hpp new file mode 100644 index 0000000..9d3036b --- /dev/null +++ b/src/Lambda.hpp @@ -0,0 +1,28 @@ +#ifndef fk_LAMBDA_HPP +#define fk_LAMBDA_HPP + +#include "commons.hpp" +#include "SymTable.hpp" +#include "Program.hpp" + +namespace fk +{ + class Lambda + { + public: + explicit Lambda(std::shared_ptr program, size_t arity); + virtual ~Lambda(); + + size_t arity() const { return m_arity; } + + std::shared_ptr program() const { return m_program; } + std::shared_ptr sym() const { return m_sym; } + + private: + std::shared_ptr m_program; + size_t m_arity; + std::shared_ptr m_sym = std::make_shared(); + }; +} + +#endif diff --git a/src/Lexer.cpp b/src/Lexer.cpp index d4990fc..a76ced5 100644 --- a/src/Lexer.cpp +++ b/src/Lexer.cpp @@ -7,6 +7,7 @@ namespace fk : m_loc { loc } { std::vector> text = { + {NODE_RARROW, "->", false}, {NODE_OPAR, "(", false}, {NODE_CPAR, ")", false}, {NODE_BOOL, "true", true}, diff --git a/src/Node.hpp b/src/Node.hpp index 72c02d1..54b6e20 100644 --- a/src/Node.hpp +++ b/src/Node.hpp @@ -6,7 +6,8 @@ #define NODE_TYPES(G) \ G(NODE_MODULE), G(NODE_INT), G(NODE_FLOAT), G(NODE_BOOL), G(NODE_STRING),\ - G(NODE_IDENT), G(NODE_OPAR), G(NODE_CPAR), G(NODE_CALL) + G(NODE_IDENT), G(NODE_OPAR), G(NODE_CPAR), G(NODE_CALL), G(NODE_LAMBDA),\ + G(NODE_RARROW), G(NODE_PARAMS), G(NODE_BODY) namespace fk { diff --git a/src/Parser.cpp b/src/Parser.cpp index 110ec2e..d5dde6f 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -59,7 +59,11 @@ namespace fk return consume(); } - if (type_is(NODE_OPAR)) + if (type_all({NODE_OPAR, NODE_RARROW})) + { + return parse_lambda(); + } + else if (type_is(NODE_OPAR)) { return parse_call(); } @@ -78,7 +82,7 @@ namespace fk { auto node = make_node(NODE_CALL); consume(NODE_OPAR); - node->add_child(consume(NODE_IDENT)); + node->add_child(parse_expr()); while (type_isnt(NODE_CPAR)) { @@ -90,6 +94,46 @@ namespace fk return node; } + std::shared_ptr Parser::parse_lambda() + { + auto node = make_node(NODE_LAMBDA); + consume(NODE_OPAR); + consume(NODE_RARROW); + + consume(NODE_OPAR); + node->add_child(parse_params()); + consume(NODE_CPAR); + + node->add_child(parse_body()); + consume(NODE_CPAR); + + return node; + } + + std::shared_ptr Parser::parse_params() + { + auto node = make_node(NODE_PARAMS); + + while (type_isnt(NODE_CPAR)) + { + node->add_child(consume(NODE_IDENT)); + } + + return node; + } + + std::shared_ptr Parser::parse_body() + { + auto node = make_node(NODE_BODY); + + while (type_isnt(NODE_CPAR)) + { + node->add_child(parse_expr()); + } + + return node; + } + std::shared_ptr Parser::make_node(NodeType type) { return std::make_shared(type, "", loc()); diff --git a/src/Parser.hpp b/src/Parser.hpp index b1d21e9..815243f 100644 --- a/src/Parser.hpp +++ b/src/Parser.hpp @@ -23,6 +23,9 @@ namespace fk std::shared_ptr parse_module(); std::shared_ptr parse_expr(); std::shared_ptr parse_call(); + std::shared_ptr parse_lambda(); + std::shared_ptr parse_params(); + std::shared_ptr parse_body(); std::shared_ptr make_node(NodeType type); Loc loc() const; diff --git a/src/SymEntry.cpp b/src/SymEntry.cpp index 3f6a69d..b8c352c 100644 --- a/src/SymEntry.cpp +++ b/src/SymEntry.cpp @@ -1,4 +1,5 @@ #include "SymEntry.hpp" +#include "Node.hpp" namespace fk { @@ -24,7 +25,9 @@ namespace fk std::string SymEntry::string() const { std::stringstream ss; - ss << m_name << "\t" << m_addr << "\t" << m_is_global << "\t" << m_scope; + ss << m_name << "\t" << m_addr << "\t" << m_is_global << "\t" << m_scope + << "\t" + << (m_parent ? NodeTypeStr[m_parent->type()] + strlen("NODE_") : ""); return ss.str(); } } diff --git a/src/SymEntry.hpp b/src/SymEntry.hpp index dd9ae38..d4845fe 100644 --- a/src/SymEntry.hpp +++ b/src/SymEntry.hpp @@ -24,10 +24,17 @@ namespace fk bool is_global() const { return m_is_global; } int scope() const { return m_scope; } std::shared_ptr parent() const { return m_parent; } + std::shared_ptr node() const { return m_node; } Loc loc() const { return m_loc; } + size_t arity() const { return m_arity; } - void set_global(bool global) { m_is_global = global; } - void set_addr(addr_t addr) { m_addr = addr; } + SymEntry& set_global(bool global) { m_is_global = global; return *this; } + SymEntry& set_addr(addr_t addr) { m_addr = addr; return *this; } + SymEntry& set_arity(size_t arity) { m_arity = arity; return *this; } + SymEntry& set_parent(std::shared_ptr parent) + { m_parent = parent; return *this; } + SymEntry& set_node(std::shared_ptr node) + { m_node = node; return *this; } std::string string() const; @@ -37,7 +44,9 @@ namespace fk bool m_is_global = false; int m_scope; std::shared_ptr m_parent; + std::shared_ptr m_node; Loc m_loc; + size_t m_arity = 0; }; } diff --git a/src/SymTable.cpp b/src/SymTable.cpp index 0f07fb4..f3f1ce3 100644 --- a/src/SymTable.cpp +++ b/src/SymTable.cpp @@ -1,5 +1,6 @@ #include "SymTable.hpp" #include "src/Loc.hpp" +#include "Node.hpp" namespace fk { @@ -11,13 +12,15 @@ namespace fk { } - void SymTable::declare_local(std::string const& name, - addr_t addr, - Loc const& loc) + SymEntry& SymTable::declare_local(std::string const& name, + addr_t addr, + Loc const& loc) { auto entry = find(name); - if (entry && entry->scope() == m_scope) + if (entry && entry->scope() == m_scope + && ((entry->parent() == nullptr && m_parents.empty()) + || entry->parent() == m_parents.back())) { std::stringstream ss; ss << "cannot declare existing variable '" @@ -27,15 +30,24 @@ namespace fk entry->loc().error(LOG_ERROR, ss.str()); } - m_entries.push_back(SymEntry {name, addr, false, m_scope, nullptr, loc}); + SymEntry e {name, + addr, + false, + m_scope, + m_parents.empty() ? + nullptr : m_parents.back(), + loc}; + + m_entries.push_back(e); + + return m_entries.back(); } - void SymTable::declare_global(std::string const& name, - addr_t addr, - Loc const& loc) + SymEntry& SymTable::declare_global(std::string const& name, + addr_t addr, + Loc const& loc) { - declare_local(name, addr, loc); - m_entries.back().set_global(true); + return declare_local(name, addr, loc).set_global(true); } std::optional SymTable::find(std::string const& name) @@ -46,6 +58,8 @@ namespace fk { if (entry.name() == name && entry.scope() <= m_scope + && ((entry.parent() == nullptr && m_parents.empty()) + || entry.parent() == m_parents.back()) && (result == std::nullopt || entry.scope() > result->scope())) { @@ -62,7 +76,7 @@ namespace fk ss << "======== SymTable ========\n"; ss << std::setw(8) << std::left; - ss << "name\taddr\tglobal\tscope" << std::endl; + ss << "name\taddr\tglobal\tscope\tfunction" << std::endl; for (auto const& entry: m_entries) { @@ -72,13 +86,15 @@ namespace fk return ss.str(); } - void SymTable::enter_scope() + void SymTable::enter_scope(std::shared_ptr parent) { m_scope++; + m_parents.push_back(parent); } void SymTable::leave_scope() { m_scope--; + m_parents.pop_back(); } } diff --git a/src/SymTable.hpp b/src/SymTable.hpp index 4fcfb7c..f487cc7 100644 --- a/src/SymTable.hpp +++ b/src/SymTable.hpp @@ -14,24 +14,25 @@ namespace fk explicit SymTable(); virtual ~SymTable(); - void declare_local(std::string const& name, - addr_t addr, - Loc const& loc); + SymEntry& declare_local(std::string const& name, + addr_t addr, + Loc const& loc); - void declare_global(std::string const& name, - addr_t addr, - Loc const& loc); + SymEntry& declare_global(std::string const& name, + addr_t addr, + Loc const& loc); std::optional find(std::string const& name); std::string string() const; - void enter_scope(); + void enter_scope(std::shared_ptr parent=nullptr); void leave_scope(); private: std::vector m_entries; int m_scope; + std::vector> m_parents; }; } diff --git a/src/VM.cpp b/src/VM.cpp index 51e8a3c..e65d4f2 100644 --- a/src/VM.cpp +++ b/src/VM.cpp @@ -1,4 +1,6 @@ #include "VM.hpp" +#include "src/opcodes.hpp" +#include namespace fk { @@ -25,6 +27,70 @@ namespace fk switch (instr.opcode) { + case OP_MAKE_FUNCTION: { + auto p = frame().program->get_const(pop()); + auto prog = std::get>(p->value()); + auto lambda = std::make_shared(prog, instr.param); + + addr_t addr = store_global(lambda); + auto ref = std::make_shared(TYPE_REF, addr, p->loc()); + + push(frame().program->add(ref)); + + m_pc++; + } break; + + case OP_RET: { + m_pc = m_frames.back().ret_addr; + size_t sz = m_frames.back().stack_sz; + std::optional ret; + + while (m_stack.size() > sz) + { + if (!ret) + { + ret = pop(); + } + else + { + pop(); + } + } + + auto value = m_frames.back().program->get_const(*ret); + m_frames.pop_back(); + push(frame().program->add(value)); + m_pc++; + } break; + + case OP_CALL_REF: { + std::vector> args; + + for (size_t i=0; iget_const(pop())); + } + + auto ref_val = frame().program->get_const(pop()); + addr_t ref = std::get(ref_val->value()); + + auto lambda = std::get>(load_global(ref)); + + Frame frame; + frame.program = lambda->program(); + frame.ret_addr = m_pc; + frame.stack_sz = m_stack.size(); + m_frames.push_back(frame); + + for (size_t i=0; i> args; diff --git a/src/VM.hpp b/src/VM.hpp index 15b9af3..d3b1fe5 100644 --- a/src/VM.hpp +++ b/src/VM.hpp @@ -4,13 +4,18 @@ #include "commons.hpp" #include "Program.hpp" #include "NativeFunction.hpp" +#include "Lambda.hpp" namespace fk { - using global_t = std::variant>; + using global_t = std::variant, + std::shared_ptr + >; struct Frame { std::shared_ptr program; + addr_t ret_addr; + size_t stack_sz; std::unordered_map> locals; }; diff --git a/src/opcodes.hpp b/src/opcodes.hpp index 1c29b02..4fca9fd 100644 --- a/src/opcodes.hpp +++ b/src/opcodes.hpp @@ -2,7 +2,8 @@ #define fk_OPCODES_HPP #define OPCODES(G) G(OP_LOAD_CONST), G(OP_POP), G(OP_CALL_NATIVE), \ - G(OP_LOAD_GLOBAL), G(OP_LOAD_LOCAL), G(OP_STORE_LOCAL) + G(OP_LOAD_GLOBAL), G(OP_LOAD_LOCAL), G(OP_STORE_LOCAL), \ + G(OP_MAKE_FUNCTION), G(OP_CALL_REF), G(OP_RET) #include "commons.hpp" diff --git a/src/types.hpp b/src/types.hpp index 7fb0ced..9fb9354 100644 --- a/src/types.hpp +++ b/src/types.hpp @@ -4,7 +4,7 @@ #include "commons.hpp" #define TYPES(G) G(TYPE_INT), G(TYPE_FLOAT), G(TYPE_BOOL), G(TYPE_STRING), \ - G(TYPE_REF) + G(TYPE_REF), G(TYPE_PROGRAM) namespace fk { diff --git a/tests/Lexer.cpp b/tests/Lexer.cpp index b56f6e1..dd98df2 100644 --- a/tests/Lexer.cpp +++ b/tests/Lexer.cpp @@ -88,3 +88,12 @@ TEST_CASE_METHOD(LexerTest, "Lexer_parenthesis") test_next("CPAR"); test_end(); } + +TEST_CASE_METHOD(LexerTest, "Lexer_lambda") +{ + m_lexer.scan(" (->) "); + test_next("OPAR"); + test_next("RARROW"); + test_next("CPAR"); + test_end(); +} diff --git a/tests/Parser.cpp b/tests/Parser.cpp index 909f4e9..f2fa06e 100644 --- a/tests/Parser.cpp +++ b/tests/Parser.cpp @@ -35,3 +35,15 @@ TEST_CASE_METHOD(ParserTest, "Parser_call") test_parse("MODULE(CALL(IDENT[bim],INT[2],STRING['3'],BOOL[false]))", " (bim 2 '3' false) "); } + +TEST_CASE_METHOD(ParserTest, "Parser_lambda") +{ + test_parse("MODULE(LAMBDA(PARAMS,BODY))", + " (-> ()) "); + + test_parse("MODULE(LAMBDA(PARAMS(IDENT[x]),BODY))", + " (-> (x)) "); + + test_parse("MODULE(LAMBDA(PARAMS(IDENT[x],IDENT[y]),BODY(INT[7])))", + " (-> (x y) 7) "); +}