From 672f7740d8765314e8698a5f3ee86b4fedf9c9c4 Mon Sep 17 00:00:00 2001 From: yhirose Date: Wed, 22 Jul 2015 21:14:55 -0400 Subject: [PATCH] Added Object support. --- grammar/culebra.peg | 10 ++-- language/interpreter.cc | 53 ++++++++++++++++----- language/interpreter.hpp | 97 ++++++++++++++++++++++++++------------- language/main.cc | 2 +- language/parser.cc | 68 ++++++++++++++------------- language/parser.hpp | 34 ++++++++++++-- language/samples/test.cul | 84 ++++++++++++++++++++++++++++----- peglib.h | 26 +++++++++-- 8 files changed, 273 insertions(+), 101 deletions(-) diff --git a/grammar/culebra.peg b/grammar/culebra.peg index 7d929f5..db927f1 100644 --- a/grammar/culebra.peg +++ b/grammar/culebra.peg @@ -17,12 +17,12 @@ UNARY_NOT <- UNARY_NOT_OPERATOR? MULTIPLICATIVE MULTIPLICATIVE <- CALL (MULTIPLICATIVE_OPERATOR CALL)* - CALL <- PRIMARY (ARGUMENTS / INDEX / METHOD)* + CALL <- PRIMARY (ARGUMENTS / INDEX / DOT)* ARGUMENTS <- '(' _ (EXPRESSION (',' _ EXPRESSION)*)? ')' _ INDEX <- '[' _ EXPRESSION ']' _ - METHOD <- '.' _ IDENTIFIER ARGUMENTS + DOT <- '.' _ IDENTIFIER - PRIMARY <- WHILE / IF / FUNCTION / IDENTIFIER / ARRAY / NUMBER / BOOLEAN / STRING / INTERPOLATED_STRING / '(' _ EXPRESSION ')' _ + PRIMARY <- WHILE / IF / FUNCTION / IDENTIFIER / OBJECT / ARRAY / NUMBER / BOOLEAN / STRING / INTERPOLATED_STRING / '(' _ EXPRESSION ')' _ FUNCTION <- 'fn' _ PARAMETERS BLOCK PARAMETERS <- '(' _ (PARAMETER (',' _ PARAMETER)*)? ')' _ @@ -39,7 +39,11 @@ IDENTIFIER <- < [a-zA-Z_][a-zA-Z0-9_]* > _ + OBJECT <- '{' _ (OBJECT_PROPERTY (',' _ OBJECT_PROPERTY)*)? '}' _ + OBJECT_PROPERTY <- IDENTIFIER ':' _ EXPRESSION + ARRAY <- '[' _ (EXPRESSION (',' _ EXPRESSION)*)? ']' _ + NUMBER <- < [0-9]+ > _ BOOLEAN <- < ('true' / 'false') > _ STRING <- ['] < (!['] .)* > ['] _ diff --git a/language/interpreter.cc b/language/interpreter.cc index 4ab27c7..a7255f0 100644 --- a/language/interpreter.cc +++ b/language/interpreter.cc @@ -23,6 +23,7 @@ struct Eval case UnaryNot: return eval_unary_not(ast, env); case BinExpresion: return eval_bin_expression(ast, env); case Identifier: return eval_identifier(ast, env); + case Object: return eval_object(ast, env); case Array: return eval_array(ast, env); case Number: return eval_number(ast, env); case Boolean: return eval_bool(ast, env); @@ -93,7 +94,7 @@ private: auto f = Value::FunctionValue( params, [=](shared_ptr callEnv) { - callEnv->set_outer(env); + callEnv->append_outer(env); return eval(*body, callEnv); } ); @@ -106,7 +107,7 @@ private: for (auto i = 1u; i < ast.nodes.size(); i++) { const auto& n = *ast.nodes[i]; - if (n.tag == AstTag::Arguments) { + if (n.original_tag == AstTag::Arguments) { // Function call const auto& f = val.to_function(); const auto& params = f.data->params; @@ -131,16 +132,16 @@ private: string msg = "arguments error..."; throw runtime_error(msg); } - } else if (n.tag == AstTag::Index) { + } else if (n.original_tag == AstTag::Index) { // Array reference const auto& arr = val.to_array(); - auto idx = eval(*n.nodes[0], env).to_long(); + auto idx = eval(n, env).to_long(); if (0 <= idx && idx < static_cast(arr.data->values.size())) { val = arr.data->values.at(idx); } - } else { // n.tag == AstTag::Property + } else if (n.original_tag == AstTag::Dot) { // Property - auto name = n.nodes[0]->token; + auto name = n.token; auto prop = val.get_property(name); if (prop.get_type() == Value::Function) { @@ -149,11 +150,10 @@ private: auto f = Value::FunctionValue( pf.data->params, [=](shared_ptr callEnv) { - auto thisEnv = make_shared(); - thisEnv->set_outer(env); - thisEnv->initialize("this", val, false); - - callEnv->set_outer(thisEnv); + callEnv->initialize("this", val, false); + if (val.get_type() == Value::Object) { + callEnv->set_object(val.to_object()); + } return pf.data->eval(callEnv); } ); @@ -162,6 +162,8 @@ private: } else { val = prop; } + } else { + throw std::logic_error("invalid internal condition."); } } @@ -277,6 +279,19 @@ private: return env->get(var); }; + static Value eval_object(const Ast& ast, shared_ptr env) { + Value::ObjectValue obj; + + for (auto i = 0u; i < ast.nodes.size(); i++) { + const auto& prop = *ast.nodes[i]; + const auto& name = prop.nodes[0]->token; + auto val = eval(*prop.nodes[1], env); + obj.data->props.emplace(name, val); + } + + return Value(std::move(obj)); + } + static Value eval_array(const Ast& ast, shared_ptr env) { Value::ArrayValue arr; @@ -307,6 +322,20 @@ private: }; }; +std::map Value::ObjectValue::prototypes = { + { + "size", + Value(FunctionValue( + {}, + [](shared_ptr callEnv) { + const auto& val = callEnv->get("this"); + long n = val.to_object().data->props.size(); + return Value(n); + } + )) + } +}; + std::map Value::ArrayValue::prototypes = { { "size", @@ -330,7 +359,7 @@ std::map Value::ArrayValue::prototypes = { return Value(); } }) - }, + } }; bool run( diff --git a/language/interpreter.hpp b/language/interpreter.hpp index fdd63a2..5f9bae4 100644 --- a/language/interpreter.hpp +++ b/language/interpreter.hpp @@ -32,7 +32,17 @@ struct Value }; struct ObjectValue { + bool has_property(const std::string& name) const { + if (data->props.find(name) == data->props.end()) { + return prototypes.find(name) != prototypes.end(); + } + return true; + } + Value get_property(const std::string& name) const { + if (data->props.find(name) == data->props.end()) { + return prototypes.at(name); + } return data->props.at(name); } @@ -40,6 +50,8 @@ struct Value std::map props; }; std::shared_ptr data = std::make_shared(); + + static std::map prototypes; }; struct ArrayValue { @@ -90,7 +102,7 @@ struct Value explicit Value(bool b) : type(Bool), v(b) {} explicit Value(long l) : type(Long), v(l) {} explicit Value(std::string&& s) : type(String), v(s) {} - explicit Value(ObjectValue&& o) : type(Object), v(0) {} + explicit Value(ObjectValue&& o) : type(Object), v(o) {} explicit Value(ArrayValue&& a) : type(Array), v(a) {} explicit Value(FunctionValue&& f) : type(Function), v(f) {} @@ -287,21 +299,39 @@ struct Environment { Environment() = default; + void set_object(const Value::ObjectValue& obj) { + obj_ = obj; + } + void set_outer(std::shared_ptr outer) { outer_ = outer; } + void append_outer(std::shared_ptr outer) { + if (outer_) { + outer_->append_outer(outer); + } else { + outer_ = outer; + } + } + bool has(const std::string& s) const { if (dic_.find(s) != dic_.end()) { return true; } + if (obj_.has_property(s)) { + return true; + } return outer_ && outer_->has(s); } - const Value& get(const std::string& s) const { + Value get(const std::string& s) const { if (dic_.find(s) != dic_.end()) { return dic_.at(s).val; } + if (obj_.has_property(s)) { + return obj_.get_property(s); + } if (outer_) { return outer_->get(s); } @@ -333,46 +363,47 @@ struct Environment dic_[s] = Symbol{val, mut}; } - void setup_built_in_functions() { - { - auto f = Value::FunctionValue( - { {"arg", true} }, - [](std::shared_ptr env) { - std::cout << env->get("arg").str() << std::endl; - return Value(); - } - ); - initialize("puts", Value(std::move(f)), false); - } - - { - auto f = Value::FunctionValue( - { {"arg", true} }, - [](std::shared_ptr env) { - auto cond = env->get("arg").to_bool(); - if (!cond) { - auto line = env->get("__LINE__").to_long(); - auto column = env->get("__COLUMN__").to_long(); - std::string msg = "assert failed at " + std::to_string(line) + ":" + std::to_string(column) + "."; - throw std::runtime_error(msg); - } - return Value(); - } - ); - initialize("assert", Value(std::move(f)), false); - } - } - private: struct Symbol { Value val; bool mut; }; - std::shared_ptr outer_; + std::shared_ptr outer_; std::map dic_; + Value::ObjectValue obj_; }; +inline void setup_built_in_functions(Environment& env) { + { + auto f = Value::FunctionValue( + { {"arg", true} }, + [](std::shared_ptr env) { + std::cout << env->get("arg").str() << std::endl; + return Value(); + } + ); + env.initialize("puts", Value(std::move(f)), false); + } + + { + auto f = Value::FunctionValue( + { {"arg", true} }, + [](std::shared_ptr env) { + auto cond = env->get("arg").to_bool(); + if (!cond) { + auto line = env->get("__LINE__").to_long(); + auto column = env->get("__COLUMN__").to_long(); + std::string msg = "assert failed at " + std::to_string(line) + ":" + std::to_string(column) + "."; + throw std::runtime_error(msg); + } + return Value(); + } + ); + env.initialize("assert", Value(std::move(f)), false); + } +} + bool run( const std::string& path, std::shared_ptr env, diff --git a/language/main.cc b/language/main.cc index 9dd10d4..b661ec4 100644 --- a/language/main.cc +++ b/language/main.cc @@ -46,7 +46,7 @@ int main(int argc, const char** argv) try { auto env = make_shared(); - env->setup_built_in_functions(); + setup_built_in_functions(*env); for (auto path: path_list) { vector buff; diff --git a/language/parser.cc b/language/parser.cc index d55821d..4a669d2 100644 --- a/language/parser.cc +++ b/language/parser.cc @@ -23,12 +23,12 @@ static auto g_grammar = R"( UNARY_NOT <- UNARY_NOT_OPERATOR? MULTIPLICATIVE MULTIPLICATIVE <- CALL (MULTIPLICATIVE_OPERATOR CALL)* - CALL <- PRIMARY (ARGUMENTS / INDEX / PROPERTY)* + CALL <- PRIMARY (ARGUMENTS / INDEX / DOT)* ARGUMENTS <- '(' _ (EXPRESSION (',' _ EXPRESSION)*)? ')' _ INDEX <- '[' _ EXPRESSION ']' _ - PROPERTY <- '.' _ IDENTIFIER + DOT <- '.' _ IDENTIFIER - PRIMARY <- WHILE / IF / FUNCTION / IDENTIFIER / ARRAY / NUMBER / BOOLEAN / STRING / INTERPOLATED_STRING / '(' _ EXPRESSION ')' _ + PRIMARY <- WHILE / IF / FUNCTION / IDENTIFIER / OBJECT / ARRAY / NUMBER / BOOLEAN / STRING / INTERPOLATED_STRING / '(' _ EXPRESSION ')' _ FUNCTION <- 'fn' _ PARAMETERS BLOCK PARAMETERS <- '(' _ (PARAMETER (',' _ PARAMETER)*)? ')' _ @@ -45,7 +45,11 @@ static auto g_grammar = R"( IDENTIFIER <- < [a-zA-Z_][a-zA-Z0-9_]* > _ + OBJECT <- '{' _ (OBJECT_PROPERTY (',' _ OBJECT_PROPERTY)*)? '}' _ + OBJECT_PROPERTY <- IDENTIFIER ':' _ EXPRESSION + ARRAY <- '[' _ (EXPRESSION (',' _ EXPRESSION)*)? ']' _ + NUMBER <- < [0-9]+ > _ BOOLEAN <- < ('true' / 'false') > _ STRING <- ['] < (!['] .)* > ['] _ @@ -79,34 +83,36 @@ peg& get_parser() throw logic_error("invalid peg grammar"); } - parser.enable_ast(true, { - /* - Definition, Tag Optimize - ---------------------- ------------------ ---------- */ - { "STATEMENTS", Statements, false }, - { "WHILE", While, false }, - { "ASSIGNMENT", Assignment, false }, - { "IF", If, false }, - { "FUNCTION", Function, false }, - { "PARAMETERS", Default, false }, - { "CALL", Call, true }, - { "ARGUMENTS", Arguments, false }, - { "INDEX", Index, false }, - { "PROPERTY", Property, false }, - { "LOGICAL_OR", LogicalOr, true }, - { "LOGICAL_AND", LogicalAnd, true }, - { "CONDITION", Condition, true }, - { "ADDITIVE", BinExpresion, true }, - { "UNARY_PLUS", UnaryPlus, true }, - { "UNARY_MINUS", UnaryMinus, true }, - { "UNARY_NOT", UnaryNot, true }, - { "MULTIPLICATIVE", BinExpresion, true }, - { "ARRAY", Array, false }, - { "NUMBER", Number, false }, - { "BOOLEAN", Boolean, false }, - { "IDENTIFIER", Identifier, false }, - { "INTERPOLATED_STRING", InterpolatedString, false }, - }); + parser.enable_ast( + true, // Optimize AST nodes + { + /* Definition Tag Optimize + ----------------------- ------------------ ---------- */ + { "STATEMENTS", Statements, true }, + { "WHILE", While, true }, + { "ASSIGNMENT", Assignment, true }, + { "IF", If, true }, + { "FUNCTION", Function, true }, + { "PARAMETERS", Default, false }, + { "CALL", Call, true }, + { "ARGUMENTS", Arguments, false }, + { "INDEX", Index, true }, + { "DOT", Dot, true }, + { "LOGICAL_OR", LogicalOr, true }, + { "LOGICAL_AND", LogicalAnd, true }, + { "CONDITION", Condition, true }, + { "ADDITIVE", BinExpresion, true }, + { "UNARY_PLUS", UnaryPlus, true }, + { "UNARY_MINUS", UnaryMinus, true }, + { "UNARY_NOT", UnaryNot, true }, + { "MULTIPLICATIVE", BinExpresion, true }, + { "OBJECT", Object, true }, + { "ARRAY", Array, true }, + { "NUMBER", Number, true }, + { "BOOLEAN", Boolean, true }, + { "IDENTIFIER", Identifier, true }, + { "INTERPOLATED_STRING", InterpolatedString, true }, + }); } return parser; diff --git a/language/parser.hpp b/language/parser.hpp index 7fd41bf..1dfbb11 100644 --- a/language/parser.hpp +++ b/language/parser.hpp @@ -3,11 +3,35 @@ enum AstTag { Default = peglib::AstDefaultTag, - Statements, While, If, Call, Assignment, - Arguments, Index, Property, - LogicalOr, LogicalAnd, Condition, UnaryPlus, UnaryMinus, UnaryNot, BinExpresion, - Identifier, InterpolatedString, - Number, Boolean, Function, Array + + Statements, + While, + If, + Call, + Assignment, + + Arguments, + Index, + Dot, + + LogicalOr, + LogicalAnd, + Condition, + UnaryPlus, + UnaryMinus, + UnaryNot, + BinExpresion, + + Identifier, + + Object, + Array, + Function, + + InterpolatedString, + + Number, + Boolean, }; peglib::peg& get_parser(); diff --git a/language/samples/test.cul b/language/samples/test.cul index 287ae9d..083c587 100644 --- a/language/samples/test.cul +++ b/language/samples/test.cul @@ -21,17 +21,6 @@ test_closure = fn () { assert(ret == 319) } -test_sum = fn () { - mut i = 1 - mut ret = 0 - while i <= 10 { - ret = ret + i - i = i + 1 - } - - assert(ret == 55) -} - test_array = fn () { a = [1,2,3] assert(a.size() == 3) @@ -43,6 +32,68 @@ test_array = fn () { assert(b.size() == 0) } +g_ = 1 + +test_function = fn () { + a = 1 + make = fn () { + b = 1 + fn (c) { + g_ + a + b + c + } + } + f = make() + assert(f(1) == 4) +} + +test_object = fn () { + n = 1 + o = { + n: 123, + s: 'str', + f1: fn (x) { x + this.n }, + f2: fn (x) { x + n } + } + assert(o.size() == 4) + assert(o.f1(10) == 133) + assert(o.f2(10) == 133) +} + +test_object_factory = fn () { + ctor = fn (init) { + mut n = init + + { + add: fn (x) { + n = n + x + }, + sub: fn (x) { + n = n - x + }, + val: fn () { + n + } + } + } + + calc = ctor(10) + + assert(calc.val() == 10) + assert(calc.add(1) == 11) + assert(calc.sub(1) == 10) +} + +test_sum = fn () { + mut i = 1 + mut ret = 0 + while i <= 10 { + ret = ret + i + i = i + 1 + } + + assert(ret == 55) +} + test_fib = fn () { fib = fn (x) { if x < 2 { @@ -57,8 +108,19 @@ test_fib = fn () { assert(ret == 610) } +test_interpolated_string = fn () { + hello = "Hello" + world = "World!" + ret = "{hello} {world}" + assert(ret == 'Hello World!') +} + test_call() test_closure() test_array() +test_function() +test_object() +test_object_factory() test_sum() test_fib() +test_interpolated_string() diff --git a/peglib.h b/peglib.h index cc7887f..1e776d1 100644 --- a/peglib.h +++ b/peglib.h @@ -1944,10 +1944,15 @@ const int AstDefaultTag = -1; struct Ast { Ast(size_t _line, size_t _column, const char* _name, int _tag, const std::vector>& _nodes) - : line(_line), column(_column), name(_name), tag(_tag), is_token(false), nodes(_nodes) {} + : line(_line), column(_column), name(_name), tag(_tag), original_tag(_tag), is_token(false), nodes(_nodes) {} Ast(size_t _line, size_t _column, const char* _name, int _tag, const std::string& _token) - : line(_line), column(_column), name(_name), tag(_tag), is_token(true), token(_token) {} + : line(_line), column(_column), name(_name), tag(_tag), original_tag(_tag), is_token(true), token(_token) {} + + Ast(const Ast& ast, int original_tag) + : line(ast.line), column(ast.column), name(ast.name), tag(ast.tag), original_tag(original_tag), is_token(ast.is_token), token(ast.token), nodes(ast.nodes) {} + + const Ast& get_smallest_ancestor() const; void print() const; @@ -1955,6 +1960,7 @@ struct Ast const size_t column; const std::string name; const int tag; + const int original_tag; const bool is_token; const std::string token; const std::vector> nodes; @@ -1980,6 +1986,16 @@ private: int level_; }; +inline const Ast& Ast::get_smallest_ancestor() const { + assert(nodes.size() <= 1); + + if (nodes.empty()) { + return *this; + } + + return nodes[0]->get_smallest_ancestor(); +} + inline void Ast::print() const { AstPrint().print(*this); } @@ -2162,7 +2178,7 @@ public: private: void output_log(const char* s, size_t n, Log log, const Definition::Result& r) const { if (log) { - if (!r.ret) { + if (!r.ret) { auto line = line_info(s, r.error_pos); log(line.first, line.second, r.message.empty() ? "syntax error" : r.message); } else if (r.len != n) { @@ -2181,7 +2197,7 @@ private: return std::make_shared(line.first, line.second, info.name, info.tag, std::string(sv.s, sv.n)); } if (info.optimize_nodes && sv.size() == 1) { - std::shared_ptr ast = sv[0].get>(); + auto ast = std::make_shared(*sv[0].get>(), info.tag); return ast; } auto line = line_info(sv.ss, sv.s); @@ -2201,7 +2217,7 @@ private: return std::make_shared(line.first, line.second, name.c_str(), AstDefaultTag, std::string(sv.s, sv.n)); } if (optimize_nodes && sv.size() == 1) { - std::shared_ptr ast = sv[0].get>(); + auto ast = std::make_shared(*sv[0].get>(), AstDefaultTag); return ast; } auto line = line_info(sv.ss, sv.s);