diff --git a/grammar/culebra.peg b/grammar/culebra.peg index 3aa1c77..ca82824 100644 --- a/grammar/culebra.peg +++ b/grammar/culebra.peg @@ -1,12 +1,9 @@ PROGRAM <- _ STATEMENTS - STATEMENTS <- (EXPRESSION (';' _)?)* - EXPRESSION <- ASSIGNMENT / LOGICAL_OR - ASSIGNMENT <- MUTABLE IDENTIFIER '=' _ EXPRESSION - WHILE <- 'while' _ EXPRESSION BLOCK - IF <- 'if' _ EXPRESSION BLOCK ('else' _ 'if' _ EXPRESSION BLOCK)* ('else' _ BLOCK)? + + ASSIGNMENT <- MUTABLE PRIMARY (ARGUMENTS / INDEX / DOT)* '=' _ EXPRESSION LOGICAL_OR <- LOGICAL_AND ('||' _ LOGICAL_AND)* LOGICAL_AND <- CONDITION ('&&' _ CONDITION)* @@ -22,6 +19,9 @@ INDEX <- '[' _ EXPRESSION ']' _ DOT <- '.' _ IDENTIFIER + WHILE <- 'while' _ EXPRESSION BLOCK + IF <- 'if' _ EXPRESSION BLOCK ('else' _ 'if' _ EXPRESSION BLOCK)* ('else' _ BLOCK)? + PRIMARY <- WHILE / IF / FUNCTION / OBJECT / ARRAY / UNDEFINED / BOOLEAN / NUMBER / IDENTIFIER / STRING / INTERPOLATED_STRING / '(' _ EXPRESSION ')' _ FUNCTION <- 'fn' _ PARAMETERS BLOCK @@ -40,7 +40,7 @@ IDENTIFIER <- < [a-zA-Z_][a-zA-Z0-9_]* > _ OBJECT <- '{' _ (OBJECT_PROPERTY (',' _ OBJECT_PROPERTY)*)? '}' _ - OBJECT_PROPERTY <- IDENTIFIER ':' _ EXPRESSION + OBJECT_PROPERTY <- MUTABLE IDENTIFIER ':' _ EXPRESSION ARRAY <- '[' _ (EXPRESSION (',' _ EXPRESSION)*)? ']' _ diff --git a/language/culebra.h b/language/culebra.h index 7ab01f4..1dbd2be 100644 --- a/language/culebra.h +++ b/language/culebra.h @@ -8,13 +8,10 @@ namespace culebra { const auto grammar_ = R"( PROGRAM <- _ STATEMENTS - STATEMENTS <- (EXPRESSION (';' _)?)* - EXPRESSION <- ASSIGNMENT / LOGICAL_OR - ASSIGNMENT <- MUTABLE IDENTIFIER '=' _ EXPRESSION - WHILE <- 'while' _ EXPRESSION BLOCK - IF <- 'if' _ EXPRESSION BLOCK ('else' _ 'if' _ EXPRESSION BLOCK)* ('else' _ BLOCK)? + + ASSIGNMENT <- MUTABLE PRIMARY (ARGUMENTS / INDEX / DOT)* '=' _ EXPRESSION LOGICAL_OR <- LOGICAL_AND ('||' _ LOGICAL_AND)* LOGICAL_AND <- CONDITION ('&&' _ CONDITION)* @@ -30,6 +27,9 @@ const auto grammar_ = R"( INDEX <- '[' _ EXPRESSION ']' _ DOT <- '.' _ IDENTIFIER + WHILE <- 'while' _ EXPRESSION BLOCK + IF <- 'if' _ EXPRESSION BLOCK ('else' _ 'if' _ EXPRESSION BLOCK)* ('else' _ BLOCK)? + PRIMARY <- WHILE / IF / FUNCTION / OBJECT / ARRAY / UNDEFINED / BOOLEAN / NUMBER / IDENTIFIER / STRING / INTERPOLATED_STRING / '(' _ EXPRESSION ')' _ FUNCTION <- 'fn' _ PARAMETERS BLOCK @@ -48,7 +48,7 @@ const auto grammar_ = R"( IDENTIFIER <- < [a-zA-Z_][a-zA-Z0-9_]* > _ OBJECT <- '{' _ (OBJECT_PROPERTY (',' _ OBJECT_PROPERTY)*)? '}' _ - OBJECT_PROPERTY <- IDENTIFIER ':' _ EXPRESSION + OBJECT_PROPERTY <- MUTABLE IDENTIFIER ':' _ EXPRESSION ARRAY <- '[' _ (EXPRESSION (',' _ EXPRESSION)*)? ']' _ @@ -93,6 +93,7 @@ inline peglib::peg& get_parser() } struct Value; +struct Symbol; struct Environment; struct FunctionValue { @@ -112,12 +113,14 @@ struct FunctionValue { }; struct ObjectValue { - bool has_property(const std::string& name) const; - Value get_property(const std::string& name) const; + bool has(const std::string& name) const; + const Value& get(const std::string& name) const; + void assign(const std::string& name, const Value& val); + void initialize(const std::string& name, const Value& val, bool mut); virtual std::map& builtins(); - std::shared_ptr> properties = - std::make_shared>(); + std::shared_ptr> properties = + std::make_shared>(); }; struct ArrayValue : public ObjectValue { @@ -196,43 +199,30 @@ struct Value } } - ObjectValue to_object() const { + const ObjectValue& to_object() const { switch (type) { case Object: return v.get(); + case Array: return v.get(); default: throw std::runtime_error("type error."); } } - ArrayValue to_array() const { + ObjectValue& to_object() { + switch (type) { + case Object: return v.get(); + case Array: return v.get(); + default: throw std::runtime_error("type error."); + } + } + + const ArrayValue& to_array() const { switch (type) { case Array: return v.get(); default: throw std::runtime_error("type error."); } } - Value get_property(const std::string& name) const { - switch (type) { - case Object: return to_object().get_property(name); - case Array: return to_array().get_property(name); - default: throw std::runtime_error("type error."); - } - } - - std::string str_object() const { - const auto& properties = *to_object().properties; - std::string s = "{"; - auto it = properties.begin(); - for (; it != properties.end(); ++it) { - if (it != properties.begin()) { - s += ", "; - } - s += '"' + it->first + '"'; - s += ": "; - s += it->second.str(); - } - s += "}"; - return s; - } + std::string str_object() const; std::string str_array() const { const auto& values = *to_array().values; @@ -251,8 +241,8 @@ struct Value switch (type) { case Undefined: return "undefined"; case Bool: return to_bool() ? "true" : "false"; - case Long: return std::to_string(to_long()); break; - case String: return to_string(); + case Long: return std::to_string(to_long()); + case String: return "'" + to_string() + "'"; case Object: return str_object(); case Array: return str_array(); case Function: return "[function]"; @@ -334,6 +324,11 @@ struct Value peglib::any v; }; +struct Symbol { + Value val; + bool mut; +}; + inline std::ostream& operator<<(std::ostream& os, const Value& val) { return val.out(os); @@ -370,6 +365,7 @@ struct Environment } void assign(const std::string& s, const Value& val) { + assert(has(s)); if (dic_.find(s) != dic_.end()) { auto& sym = dic_[s]; if (!sym.mut) { @@ -380,30 +376,22 @@ struct Environment sym.val = val; return; } - if (outer && outer->has(s)) { - outer->assign(s, val); - return; - } - // NOTREACHED - throw std::logic_error("invalid internal condition."); + outer->assign(s, val); + return; } void initialize(const std::string& s, const Value& val, bool mut) { //std::cout << "Env::initialize: " << s << std::endl; - dic_[s] = Symbol{val, mut}; + dic_[s] = Symbol{ val, mut }; } std::shared_ptr outer; private: - struct Symbol { - Value val; - bool mut; - }; std::map dic_; }; -inline bool ObjectValue::has_property(const std::string& name) const { +inline bool ObjectValue::has(const std::string& name) const { if (properties->find(name) == properties->end()) { const auto& props = const_cast(this)->builtins(); return props.find(name) != props.end(); @@ -411,12 +399,27 @@ inline bool ObjectValue::has_property(const std::string& name) const { return true; } -inline Value ObjectValue::get_property(const std::string& name) const { +inline const Value& ObjectValue::get(const std::string& name) const { if (properties->find(name) == properties->end()) { const auto& props = const_cast(this)->builtins(); return props.at(name); } - return properties->at(name); + return properties->at(name).val; +} + +inline void ObjectValue::assign(const std::string& name, const Value& val) { + assert(has(name)); + auto& sym = properties->at(name); + if (!sym.mut) { + std::string msg = "immutable property '" + name + "'..."; + throw std::runtime_error(msg); + } + sym.val = val; + return; +} + +inline void ObjectValue::initialize(const std::string& name, const Value& val, bool mut) { + (*properties)[name] = Symbol{ val, mut }; } inline std::map& ObjectValue::builtins() { @@ -465,6 +468,27 @@ inline std::map& ArrayValue::builtins() { return props_; } +inline std::string Value::str_object() const { + const auto& properties = *to_object().properties; + std::string s = "{"; + auto it = properties.begin(); + for (; it != properties.end(); ++it) { + if (it != properties.begin()) { + s += ", "; + } + const auto& name = it->first; + const auto& sym = it->second; + if (sym.mut) { + s += "mut "; + } + s += name; + s += ": "; + s += sym.val.str(); + } + s += "}"; + return s; +} + inline void setup_built_in_functions(Environment& env) { env.initialize( "puts", @@ -595,63 +619,73 @@ private: )); }; + static Value eval_function_call(const peglib::Ast& ast, std::shared_ptr env, const Value& val) { + const auto& f = val.to_function(); + const auto& params = *f.params; + const auto& args = ast.nodes; + + if (params.size() <= args.size()) { + auto callEnv = std::make_shared(); + callEnv->initialize("self", val, false); + for (auto iprm = 0u; iprm < params.size(); iprm++) { + auto param = params[iprm]; + auto arg = args[iprm]; + auto val = eval(*arg, env); + callEnv->initialize(param.name, val, param.mut); + } + callEnv->initialize("__LINE__", Value((long)ast.line), false); + callEnv->initialize("__COLUMN__", Value((long)ast.column), false); + return f.eval(callEnv); + } + + std::string msg = "arguments error..."; + throw std::runtime_error(msg); + } + + static Value eval_array_reference(const peglib::Ast& ast, std::shared_ptr env, const Value& val) { + const auto& arr = val.to_array(); + auto idx = eval(ast, env).to_long(); + if (0 <= idx && idx < static_cast(arr.values->size())) { + return arr.values->at(idx); + } else { + // TODO: error? or 'undefined'? + } + return val; + } + + static Value eval_property(const peglib::Ast& ast, std::shared_ptr env, const Value& val) { + const auto& obj = val.to_object(); + auto name = ast.token; + if (!obj.has(name)) { + return Value(); + } + const auto& prop = obj.get(name); + if (prop.type == Value::Function) { + const auto& pf = prop.to_function(); + return Value(FunctionValue( + *pf.params, + [=](std::shared_ptr callEnv) { + callEnv->initialize("this", val, false); + return pf.eval(callEnv); + } + )); + } + return prop; + } + static Value eval_call(const peglib::Ast& ast, std::shared_ptr env) { using peglib::operator"" _; Value val = eval(*ast.nodes[0], env); for (auto i = 1u; i < ast.nodes.size(); i++) { - const auto& n = *ast.nodes[i]; - if (n.original_tag == "ARGUMENTS"_) { - // Function call - const auto& f = val.to_function(); - const auto& params = *f.params; - const auto& args = n.nodes; - if (params.size() <= args.size()) { - auto callEnv = std::make_shared(); + const auto& postfix = *ast.nodes[i]; - callEnv->initialize("self", val, false); - - for (auto iprm = 0u; iprm < params.size(); iprm++) { - auto param = params[iprm]; - auto arg = args[iprm]; - auto val = eval(*arg, env); - callEnv->initialize(param.name, val, param.mut); - } - - callEnv->initialize("__LINE__", Value((long)ast.line), false); - callEnv->initialize("__COLUMN__", Value((long)ast.column), false); - - val = f.eval(callEnv); - } else { - std::string msg = "arguments error..."; - throw std::runtime_error(msg); - } - } else if (n.original_tag == "INDEX"_) { - // Array reference - const auto& arr = val.to_array(); - auto idx = eval(n, env).to_long(); - if (0 <= idx && idx < static_cast(arr.values->size())) { - val = arr.values->at(idx); - } - } else if (n.original_tag == "DOT"_) { - // Property - auto name = n.token; - auto prop = val.get_property(name); - if (prop.type == Value::Function) { - const auto& pf = prop.to_function(); - val = Value(FunctionValue( - *pf.params, - [=](std::shared_ptr callEnv) { - callEnv->initialize("this", val, false); - return pf.eval(callEnv); - } - )); - } else { - val = prop; - } - } else { - throw std::logic_error("invalid internal condition."); + switch (postfix.original_tag) { + case "ARGUMENTS"_: val = eval_function_call(postfix, env, val); break; + case "INDEX"_: val = eval_array_reference(postfix, env, val); break; + case "DOT"_: val = eval_property(postfix, env, val); break; + default: throw std::logic_error("invalid internal condition."); } } @@ -733,15 +767,63 @@ private: } static Value eval_assignment(const peglib::Ast& ast, std::shared_ptr env) { - const auto& var = ast.nodes[1]->token; - auto val = eval(*ast.nodes[2], env); - if (env->has(var)) { - env->assign(var, val); + auto end = ast.nodes.size() - 1; + + auto mut = ast.nodes[0]->token == "mut"; + auto val = eval(*ast.nodes[end], env); + + if (ast.nodes.size() == 3) { + const auto& var = ast.nodes[1]->token; + if (env->has(var)) { + env->assign(var, val); + } else { + env->initialize(var, val, mut); + } + return std::move(val); } else { - const auto& mut = ast.nodes[0]->token; - env->initialize(var, val, mut == "mut"); + using peglib::operator"" _; + + Value lval = eval(*ast.nodes[1], env); + + auto end = ast.nodes.size() - 2; + for (auto i = 2; i < end; i++) { + const auto& postfix = *ast.nodes[i]; + + switch (postfix.original_tag) { + case "ARGUMENTS"_: lval = eval_function_call(postfix, env, lval); break; + case "INDEX"_: lval = eval_array_reference(postfix, env, lval); break; + case "DOT"_: lval = eval_property(postfix, env, lval); break; + default: throw std::logic_error("invalid internal condition."); + } + } + + const auto& postfix = *ast.nodes[end]; + + switch (postfix.original_tag) { + case "INDEX"_: { + const auto& arr = lval.to_array(); + auto idx = eval(postfix, env).to_long(); + if (0 <= idx && idx < static_cast(arr.values->size())) { + arr.values->at(idx) = val; + } else { + // TODO: error? or set 'undefined'? + } + return val; + } + case "DOT"_: { + auto& obj = lval.to_object(); + auto name = postfix.token; + if (obj.has(name)) { + obj.assign(name, val); + } else { + obj.initialize(name, val, mut); + } + return val; + } + default: + throw std::logic_error("invalid internal condition."); + } } - return std::move(val); }; static Value eval_identifier(const peglib::Ast& ast, std::shared_ptr env) { @@ -752,9 +834,10 @@ private: 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.properties->emplace(name, val); + auto mut = prop.nodes[0]->token == "mut"; + const auto& name = prop.nodes[1]->token; + auto val = eval(*prop.nodes[2], env); + obj.properties->emplace(name, Symbol{ val, mut }); } return Value(std::move(obj)); } @@ -785,7 +868,11 @@ private: std::string s; for (auto node: ast.nodes) { const auto& val = eval(*node, env); - s += val.str(); + if (val.type == Value::String) { + s += val.to_string(); + } else { + s += val.str(); + } } return Value(std::move(s)); }; diff --git a/language/samples/test.cul b/language/samples/test.cul index da95cf0..22872c2 100644 --- a/language/samples/test.cul +++ b/language/samples/test.cul @@ -79,6 +79,12 @@ test_object = fn () { assert(o.size() == 4) assert(o.f1(10) == 133) assert(o.f2(10) == 11) + + a = {} + a.b = 1 + assert(a.a == undefined) + assert(a.b == 1) + assert(a.size() == 1) } test_object_factory = fn () {