From ed156cd5a0480df465dedd93d26aa972633ef804 Mon Sep 17 00:00:00 2001 From: pajama-coder Date: Wed, 20 Nov 2024 14:41:34 +0800 Subject: [PATCH] [pjs] Add support of for-loop statements --- src/pjs/expr.hpp | 3 ++ src/pjs/parser.cpp | 29 +++++++++++++ src/pjs/stmt.cpp | 103 ++++++++++++++++++++++++++++++++++++++++++++- src/pjs/stmt.hpp | 31 ++++++++++++-- 4 files changed, 162 insertions(+), 4 deletions(-) diff --git a/src/pjs/expr.hpp b/src/pjs/expr.hpp index 89a85eca..3342331c 100644 --- a/src/pjs/expr.hpp +++ b/src/pjs/expr.hpp @@ -216,6 +216,9 @@ class Compound : public Expr { out = std::move(m_exprs); } + auto expression_count() const -> size_t { return m_exprs.size(); } + auto expression(size_t i) const -> Expr* { return m_exprs[i].get(); } + virtual bool is_argument_list() const override; virtual bool is_comma_ended() const override { return m_is_comma_ended; } virtual bool eval(Context &ctx, Value &result) override; diff --git a/src/pjs/parser.cpp b/src/pjs/parser.cpp index bcce6164..4632932a 100644 --- a/src/pjs/parser.cpp +++ b/src/pjs/parser.cpp @@ -1368,8 +1368,37 @@ Stmt* ScriptParser::statement() { } return locate(switch_case(cond.release(), std::move(cases)), l); } + case Token::ID("for"): { + read(l); + bool is_var = false; + std::unique_ptr init, cond, step; + if (!read(Token::ID("("), TokenExpected)) return nullptr; + if (read(Token::ID("var"))) is_var = true; + if (!read(Token::ID(";"))) { + auto e = expression(); + if (!e) return nullptr; + init.reset(e); + } + if (!read(Token::ID(";"), TokenExpected)) return nullptr; + if (!read(Token::ID(";"))) { + auto e = expression(); + if (!e) return nullptr; + cond.reset(e); + } + if (!read(Token::ID(";"), TokenExpected)) return nullptr; + if (!read(Token::ID(")"))) { + auto e = expression(); + if (!e) return nullptr; + step.reset(e); + } + if (!read(Token::ID(")"), TokenExpected)) return nullptr; + auto s = statement(); + if (!s) return nullptr; + return for_loop(is_var, init.release(), cond.release(), step.release(), s); + } case Token::ID("break"): { read(l); + if (peek_eol() || peek_end()) return locate(flow_break(), l); if (auto name = read_identifier()) { read_semicolons(); return locate(flow_break(name.release()), l); diff --git a/src/pjs/stmt.cpp b/src/pjs/stmt.cpp index f9675b38..36dccbd3 100644 --- a/src/pjs/stmt.cpp +++ b/src/pjs/stmt.cpp @@ -452,6 +452,107 @@ void Switch::dump(std::ostream &out, const std::string &indent) { } } +// +// For +// + +bool For::declare(Module *module, Scope &scope, Error &error, bool is_lval) { + if (m_is_var && m_init) { + std::vector> names; + if (auto assign = m_init->as()) { + auto lvalue = assign->lvalue(); + if (auto id = lvalue->as()) { + names.emplace_back(id->name()); + } else { + error.tree = lvalue; + error.message = "illegal left-value in assignment"; + return false; + } + } else if (auto comp = m_init->as()) { + for (size_t i = 0, n = comp->expression_count(); i < n; i++) { + auto expr = comp->expression(i); + if (auto assign = expr->as()) { + auto lvalue = assign->lvalue(); + if (auto id = lvalue->as()) { + names.emplace_back(id->name()); + } else { + error.tree = lvalue; + error.message = "illegal left-value in assignment"; + return false; + } + } + } + } + auto s = scope.parent(); + while (!s->is_root()) s = s->parent(); + for (const auto &name : names) { + if (Var::is_fiber(name->str())) { + if (!check_reserved(name->str(), error)) return false; + s->declare_fiber_var(name, module); + } else { + s->declare_var(name); + } + } + } + + Tree::Scope s(Tree::Scope::LOOP, &scope); + if (m_init && !m_init->declare(module, s, error, false)) return false; + if (m_cond && !m_cond->declare(module, s, error, false)) return false; + if (m_step && !m_step->declare(module, s, error, false)) return false; + if (m_body && !m_body->declare(module, s, error, false)) return false; + + return true; +} + +void For::resolve(Module *module, Context &ctx, int l, Tree::LegacyImports *imports) { + if (m_init) m_init->resolve(module, ctx, l, imports); + if (m_cond) m_cond->resolve(module, ctx, l, imports); + if (m_step) m_step->resolve(module, ctx, l, imports); + if (m_body) m_body->resolve(module, ctx, l, imports); +} + +void For::execute(Context &ctx, Result &result) { + Value val; + if (m_init && !m_init->eval(ctx, val)) return; + for (;;) { + if (m_cond) { + if (!m_cond->eval(ctx, val)) return; + if (!val.to_boolean()) break; + } + if (m_body) { + m_body->execute(ctx, result); + if (!ctx.ok()) return; + if (result.is_break()) { + if (result.label) return; + break; + } + if (result.is_continue()) { + if (m_step && !m_step->eval(ctx, val)) return; + continue; + } + if (!result.is_done()) return; + } + if (m_step && !m_step->eval(ctx, val)) return; + } + result.set_done(); +} + +bool For::check_reserved(const std::string &name, Error &error) { + if (!Var::is_reserved(name)) return true; + error.tree = this; + error.message = "reserved variable name '" + name + "'"; + return false; +} + +void For::dump(std::ostream &out, const std::string &indent) { + auto indent2 = indent + " "; + out << indent << "for" << std::endl; + out << indent << " init" << std::endl; if (m_init) m_init->dump(out, indent2); + out << indent << " cond" << std::endl; if (m_cond) m_cond->dump(out, indent2); + out << indent << " step" << std::endl; if (m_step) m_step->dump(out, indent2); + out << indent << " body" << std::endl; if (m_body) m_body->dump(out, indent2); +} + // // Break // @@ -463,7 +564,7 @@ bool Break::declare(Module *module, Scope &scope, Error &error, bool is_lval) { s = s->parent(); } } else { - while (s && s->kind() != Tree::Scope::SWITCH) { + while (s && s->kind() != Tree::Scope::SWITCH && s->kind() != Tree::Scope::LOOP) { s = s->parent(); } } diff --git a/src/pjs/stmt.hpp b/src/pjs/stmt.hpp index 1a688690..8a7c1a10 100644 --- a/src/pjs/stmt.hpp +++ b/src/pjs/stmt.hpp @@ -156,6 +156,9 @@ class Var : public Exportable { Var(std::vector> &&list) : m_list(std::move(list)) {} + static bool is_fiber(const std::string &name); + static bool is_reserved(const std::string &name); + virtual bool declare(Module *module, Scope &scope, Error &error, bool is_lval) override; virtual void resolve(Module *module, Context &ctx, int l, Tree::LegacyImports *imports) override; virtual void execute(Context &ctx, Result &result) override; @@ -167,9 +170,6 @@ class Var : public Exportable { std::vector m_assignments; bool check_reserved(const std::string &name, Error &error); - - static bool is_fiber(const std::string &name); - static bool is_reserved(const std::string &name); }; // @@ -232,6 +232,30 @@ class Switch : public Stmt { std::list, std::unique_ptr>> m_cases; }; +// +// For +// + +class For : public Stmt { +public: + For(bool is_var, Expr *init, Expr *cond, Expr *step, Stmt *body) + : m_is_var(is_var), m_init(init), m_cond(cond), m_step(step), m_body(body) {} + + virtual bool declare(Module *module, Scope &scope, Error &error, bool is_lval) override; + virtual void resolve(Module *module, Context &ctx, int l, Tree::LegacyImports *imports) override; + virtual void execute(Context &ctx, Result &result) override; + virtual void dump(std::ostream &out, const std::string &indent) override; + +private: + bool m_is_var; + std::unique_ptr m_init; + std::unique_ptr m_cond; + std::unique_ptr m_step; + std::unique_ptr m_body; + + bool check_reserved(const std::string &name, Error &error); +}; + // // Break // @@ -359,6 +383,7 @@ inline Stmt* var(std::vector> &&list) { return new stmt::V inline Stmt* function(expr::Identifier *name, Expr *expr) { return new stmt::Function(name, expr); } inline Stmt* if_else(Expr *cond, Stmt *then_clause, Stmt *else_clause = nullptr) { return new stmt::If(cond, then_clause, else_clause); } inline Stmt* switch_case(Expr *cond, std::list, std::unique_ptr>> &&cases) { return new stmt::Switch(cond, std::move(cases)); } +inline Stmt* for_loop(bool is_var, Expr *init, Expr *cond, Expr *step, Stmt *body) { return new stmt::For(is_var, init, cond, step, body); } inline Stmt* try_catch(Stmt *try_clause, Stmt *catch_clause, Stmt *finally_clause, Expr *exception_variable) { return new stmt::Try(try_clause, catch_clause, finally_clause, exception_variable); } inline Stmt* flow_break() { return new stmt::Break(); } inline Stmt* flow_break(expr::Identifier *label) { return new stmt::Break(label); }