From 51af0e73d505e3324d4a53d51271e4b3f2a4dc87 Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Wed, 6 Feb 2019 02:43:01 +0200 Subject: [PATCH 1/7] AST interpreter: implement handling of `switch` (and `case`) statements --- JSImpl/src/ASTInterpreter.cpp | 38 ++++++++++++++++++++++++++++++----- JSImpl/src/ASTInterpreter.h | 4 ++++ 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/JSImpl/src/ASTInterpreter.cpp b/JSImpl/src/ASTInterpreter.cpp index 25f5c021..ab061be8 100644 --- a/JSImpl/src/ASTInterpreter.cpp +++ b/JSImpl/src/ASTInterpreter.cpp @@ -70,6 +70,7 @@ struct StackScope }; ASTInterpreter::ASTInterpreter() + : m_Fallthrough(false) { // Allow globals EnterScope(); @@ -101,6 +102,15 @@ bool ASTInterpreter::EvalToBool(const ExpressionPtr& e) return result != 0.0; } +bool ASTInterpreter::EvalIsEqual(const ExpressionPtr& e) +{ + e->Accept(*this); + auto second = m_Evaluation.back(); + m_Evaluation.pop_back(); + auto first = m_Evaluation.back(); + return fabs(second - first) < 0.0001; +} + void ASTInterpreter::EnterScope() { m_Scopes.push(Scope{}); @@ -138,14 +148,14 @@ bool ASTInterpreter::HasVariable(const IPLString& name) } void ASTInterpreter::Visit(LiteralNull* e) { - NOT_IMPLEMENTED; + NOT_IMPLEMENTED; } void ASTInterpreter::Visit(LiteralUndefined* e) { - NOT_IMPLEMENTED; + NOT_IMPLEMENTED; } void ASTInterpreter::Visit(LiteralString* e) { - NOT_IMPLEMENTED; + NOT_IMPLEMENTED; } void ASTInterpreter::Visit(LiteralObject* e) @@ -278,8 +288,26 @@ void ASTInterpreter::Visit(IfStatement* e) { } } -void ASTInterpreter::Visit(SwitchStatement* e) { NOT_IMPLEMENTED;} -void ASTInterpreter::Visit(CaseStatement *e) { NOT_IMPLEMENTED;} +void ASTInterpreter::Visit(SwitchStatement* e) +{ + VariableScope scope(this); + e->GetCondition()->Accept(*this); + for (const auto& stmt : e->GetCases()) + stmt->Accept(*this); + RunExpression(e->GetDefaultCase()); + m_Evaluation.pop_back(); + + m_Fallthrough = false; +} + +void ASTInterpreter::Visit(CaseStatement *e) +{ + if (m_Fallthrough || EvalIsEqual(e->GetCondition())) + { + RunExpression(e->GetBody()); + m_Fallthrough = true; + } +} void ASTInterpreter::Visit(WhileStatement* e) { VariableScope scope(this); diff --git a/JSImpl/src/ASTInterpreter.h b/JSImpl/src/ASTInterpreter.h index db890130..cb19a950 100644 --- a/JSImpl/src/ASTInterpreter.h +++ b/JSImpl/src/ASTInterpreter.h @@ -52,6 +52,7 @@ class ASTInterpreter : public ExpressionVisitor private: void RunExpression(const ExpressionPtr& e); bool EvalToBool(const ExpressionPtr& e); + bool EvalIsEqual(const ExpressionPtr& e); void EnterScope(); void LeaveScope(); @@ -63,4 +64,7 @@ class ASTInterpreter : public ExpressionVisitor typedef IPLVector Scope; IPLStack m_Scopes; + + bool m_Fallthrough; + bool m_Break; }; From 9d2518786c41477f87334d242e258ce84066eb48 Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Wed, 6 Feb 2019 02:43:01 +0200 Subject: [PATCH 2/7] AST interpreter: implement handling of `break` statements --- JSImpl/src/ASTInterpreter.cpp | 20 ++++++++++++++++++++ JSImpl/src/ASTInterpreter.h | 2 ++ JSImpl/src/Expression.h | 2 +- JSImpl/src/ExpressionVisitor.h | 2 ++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/JSImpl/src/ASTInterpreter.cpp b/JSImpl/src/ASTInterpreter.cpp index ab061be8..374b5cc8 100644 --- a/JSImpl/src/ASTInterpreter.cpp +++ b/JSImpl/src/ASTInterpreter.cpp @@ -71,6 +71,7 @@ struct StackScope ASTInterpreter::ASTInterpreter() : m_Fallthrough(false) + , m_Break(false) { // Allow globals EnterScope(); @@ -147,6 +148,11 @@ bool ASTInterpreter::HasVariable(const IPLString& name) return m_Variables.find(name) != m_Variables.end(); } +bool ASTInterpreter::IsInSkipMode() +{ + return m_Break; +} + void ASTInterpreter::Visit(LiteralNull* e) { NOT_IMPLEMENTED; } @@ -226,6 +232,12 @@ void ASTInterpreter::Visit(BinaryExpression* e) { } void ASTInterpreter::Visit(UnaryExpression* e) { + if (e->GetOperator() == TokenType::Break) + { + m_Break = true; + return; + } + LValueExtractor extractor(this); auto lvalue = extractor.Run(e->GetExpr().get()); if (e->GetSuffix()) @@ -298,10 +310,14 @@ void ASTInterpreter::Visit(SwitchStatement* e) m_Evaluation.pop_back(); m_Fallthrough = false; + m_Break = false; } void ASTInterpreter::Visit(CaseStatement *e) { + if (m_Break) + return; + if (m_Fallthrough || EvalIsEqual(e->GetCondition())) { RunExpression(e->GetBody()); @@ -319,6 +335,8 @@ void ASTInterpreter::Visit(WhileStatement* e) { VariableScope bodyScope(this); RunExpression(e->GetBody()); } + + m_Break = false; } void ASTInterpreter::Visit(ForStatement* e) { @@ -339,6 +357,8 @@ void ASTInterpreter::Visit(ForStatement* e) { RunExpression(e->GetIteration()); } } + + m_Break = false; } void ASTInterpreter::Visit(FunctionDeclaration* e) { NOT_IMPLEMENTED;} diff --git a/JSImpl/src/ASTInterpreter.h b/JSImpl/src/ASTInterpreter.h index cb19a950..af6900a0 100644 --- a/JSImpl/src/ASTInterpreter.h +++ b/JSImpl/src/ASTInterpreter.h @@ -16,6 +16,8 @@ class ASTInterpreter : public ExpressionVisitor ValueStack Run(Expression* program); + virtual bool IsInSkipMode() override; + virtual void Visit(LiteralNull* e) override; virtual void Visit(LiteralUndefined* e) override; virtual void Visit(LiteralString* e) override; diff --git a/JSImpl/src/Expression.h b/JSImpl/src/Expression.h index 3fa7b90c..745f0d9a 100644 --- a/JSImpl/src/Expression.h +++ b/JSImpl/src/Expression.h @@ -40,7 +40,7 @@ class Expression : public IPLEnableShared : \ MEMBERS_ITERATOR(EXPAND_INITIALIZER_LIST) m_dummy(__dummy) {} \ virtual ~ClassName() {} \ - virtual void Accept(ExpressionVisitor& v) override { v.Visit(this); } \ + virtual void Accept(ExpressionVisitor& v) override { if (!v.IsInSkipMode()) v.Visit(this); } \ \ MEMBERS_ITERATOR(GENERATE_GETTERS) \ private: \ diff --git a/JSImpl/src/ExpressionVisitor.h b/JSImpl/src/ExpressionVisitor.h index f1f2fcd1..e82d534a 100644 --- a/JSImpl/src/ExpressionVisitor.h +++ b/JSImpl/src/ExpressionVisitor.h @@ -7,6 +7,8 @@ class ExpressionVisitor public: ~ExpressionVisitor() {} + virtual bool IsInSkipMode() { return false; } + virtual void Visit(LiteralNull* e) { (void)e; NOT_IMPLEMENTED; } virtual void Visit(LiteralUndefined* e) { (void)e; NOT_IMPLEMENTED; } virtual void Visit(LiteralString* e) { (void)e; NOT_IMPLEMENTED; } From 99ffc3f6305b878c896dc1157619d1793e4429fb Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Thu, 7 Feb 2019 09:29:42 +0200 Subject: [PATCH 3/7] AST interpreter: implement handling of `continue` statements --- JSImpl/src/ASTInterpreter.cpp | 14 ++++++++++++-- JSImpl/src/ASTInterpreter.h | 1 + 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/JSImpl/src/ASTInterpreter.cpp b/JSImpl/src/ASTInterpreter.cpp index 374b5cc8..ac5f87cd 100644 --- a/JSImpl/src/ASTInterpreter.cpp +++ b/JSImpl/src/ASTInterpreter.cpp @@ -72,6 +72,7 @@ struct StackScope ASTInterpreter::ASTInterpreter() : m_Fallthrough(false) , m_Break(false) + , m_Continue(false) { // Allow globals EnterScope(); @@ -150,7 +151,7 @@ bool ASTInterpreter::HasVariable(const IPLString& name) bool ASTInterpreter::IsInSkipMode() { - return m_Break; + return m_Break || m_Continue; } void ASTInterpreter::Visit(LiteralNull* e) { @@ -232,10 +233,16 @@ void ASTInterpreter::Visit(BinaryExpression* e) { } void ASTInterpreter::Visit(UnaryExpression* e) { - if (e->GetOperator() == TokenType::Break) + switch (e->GetOperator()) { + case TokenType::Break: m_Break = true; return; + case TokenType::Continue: + m_Continue = true; + return; + default: + ; } LValueExtractor extractor(this); @@ -330,10 +337,12 @@ void ASTInterpreter::Visit(WhileStatement* e) { if (e->GetDoWhile()) { VariableScope bodyScope(this); RunExpression(e->GetBody()); + m_Continue = false; } while (EvalToBool(e->GetCondition())) { VariableScope bodyScope(this); RunExpression(e->GetBody()); + m_Continue = false; } m_Break = false; @@ -351,6 +360,7 @@ void ASTInterpreter::Visit(ForStatement* e) { { VariableScope bodyScope(this); RunExpression(e->GetBody()); + m_Continue = false; } { StackScope stackScope(this); diff --git a/JSImpl/src/ASTInterpreter.h b/JSImpl/src/ASTInterpreter.h index af6900a0..c1c5658a 100644 --- a/JSImpl/src/ASTInterpreter.h +++ b/JSImpl/src/ASTInterpreter.h @@ -69,4 +69,5 @@ class ASTInterpreter : public ExpressionVisitor bool m_Fallthrough; bool m_Break; + bool m_Continue; }; From b08521ade82ae52795036caeaa992c325c56a846 Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Wed, 6 Feb 2019 03:15:38 +0200 Subject: [PATCH 4/7] AST interpreter: refactor: abstract out handling of equality of `double` values; use epsilon defined in instead of hardcoding it --- JSImpl/src/ASTInterpreter.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/JSImpl/src/ASTInterpreter.cpp b/JSImpl/src/ASTInterpreter.cpp index ac5f87cd..c0c40941 100644 --- a/JSImpl/src/ASTInterpreter.cpp +++ b/JSImpl/src/ASTInterpreter.cpp @@ -1,11 +1,14 @@ #include "ASTInterpreter.h" #include "Expression.h" #include +#include #include #undef NOT_IMPLEMENTED #define NOT_IMPLEMENTED (void)e; assert(0 && "not-implemented") +#define FLOAT_EQUALS(first, second) fabs(first - second) < std::numeric_limits::epsilon() + struct LValueExtractor : public ExpressionVisitor { public: @@ -110,7 +113,7 @@ bool ASTInterpreter::EvalIsEqual(const ExpressionPtr& e) auto second = m_Evaluation.back(); m_Evaluation.pop_back(); auto first = m_Evaluation.back(); - return fabs(second - first) < 0.0001; + return FLOAT_EQUALS(first, second); } void ASTInterpreter::EnterScope() @@ -215,10 +218,10 @@ void ASTInterpreter::Visit(BinaryExpression* e) { m_Evaluation.push_back(right); break; case TokenType::EqualEqual: - m_Evaluation.push_back(fabs(left - right) < 0.0001); + m_Evaluation.push_back(FLOAT_EQUALS(left, right)); break; case TokenType::BangEqual: - m_Evaluation.push_back(fabs(left - right) > 0.0001); + m_Evaluation.push_back(FLOAT_EQUALS(left, right)); break; case TokenType::Equal: { From 21f496b4a237801bcede46aeec7aebcb4ded2253 Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Thu, 7 Feb 2019 08:42:47 +0200 Subject: [PATCH 5/7] bytecode generator: implement handling of `switch` (and `case`) statements --- JSImpl/src/ByteCodeGenerator.cpp | 43 +++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/JSImpl/src/ByteCodeGenerator.cpp b/JSImpl/src/ByteCodeGenerator.cpp index fd853264..f66645d1 100644 --- a/JSImpl/src/ByteCodeGenerator.cpp +++ b/JSImpl/src/ByteCodeGenerator.cpp @@ -22,6 +22,8 @@ class ByteCodeGenerator : public ExpressionVisitor virtual void Visit(EmptyExpression* e) override { (void)e; } virtual void Visit(IfStatement* e) override; virtual void Visit(ForStatement* e) override; + virtual void Visit(SwitchStatement* e) override; + virtual void Visit(CaseStatement* e) override; virtual void Visit(UnaryExpression* e) override; IPLString GetCode(); @@ -106,6 +108,8 @@ class ByteCodeGenerator : public ExpressionVisitor IPLStack m_RegisterStack; IPLString m_OutputCode; ByteCodeGeneratorOptions m_Options; + + IPLVector> m_SwitchCases; }; size_t ByteCodeGenerator::PushInstruction(Instruction::Type opcode, const IPLString& arg0, const IPLString& arg1, const IPLString& arg2) @@ -345,6 +349,43 @@ void ByteCodeGenerator::Visit(ForStatement* e) m_Code[endAddress].Values.Address[0] = m_Code.size(); } +void ByteCodeGenerator::Visit(SwitchStatement* e) +{ + // unfortunately, we cannot use a jump table for the switch as JS allows non-integer values as conditions + e->GetCondition()->Accept(*this); + for (const auto& c : e->GetCases()) + { + c->Accept(*this); + } + + auto jumpToDefaultAddress = PushInstruction(Instruction::Type::JMP); + + for (const auto& c : m_SwitchCases) + { + auto jumpAddress = c.first; + m_Code[jumpAddress].Values.Address[0] = m_Code.size(); + c.second->Accept(*this); + } + + m_Code[jumpToDefaultAddress].Values.Address[0] = m_Code.size(); + e->GetDefaultCase()->Accept(*this); + + m_SwitchCases.clear(); + m_RegisterStack.pop(); +} + +void ByteCodeGenerator::Visit(CaseStatement* e) +{ + e->GetCondition()->Accept(*this); + auto caseCondition = m_RegisterStack.top(); + m_RegisterStack.pop(); + auto switchCondition = m_RegisterStack.top(); + auto result = CreateRegister(); + PushInstruction(Instruction::Type::EQ, result, switchCondition, caseCondition); + auto jumpAddress = PushInstruction(Instruction::Type::JMPT, result, (size_t)0); + m_SwitchCases.emplace_back(jumpAddress, e->GetBody()); +} + void ByteCodeGenerator::Visit(IdentifierExpression* e) { m_RegisterStack.push(e->GetName()); @@ -654,7 +695,7 @@ IPLString ByteCodeGenerator::GetCode() default: NOT_IMPLEMENTED; break; - + } ++programCounter; } From c12547c3d8d82cc35993557a9b0a695d2b5e196f Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Thu, 7 Feb 2019 06:54:06 +0200 Subject: [PATCH 6/7] bytecode generator: implement handling of `break` statements --- JSImpl/src/ByteCodeGenerator.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/JSImpl/src/ByteCodeGenerator.cpp b/JSImpl/src/ByteCodeGenerator.cpp index f66645d1..a0d1ea40 100644 --- a/JSImpl/src/ByteCodeGenerator.cpp +++ b/JSImpl/src/ByteCodeGenerator.cpp @@ -110,6 +110,7 @@ class ByteCodeGenerator : public ExpressionVisitor ByteCodeGeneratorOptions m_Options; IPLVector> m_SwitchCases; + IPLVector m_BreakInstructionAddresses; }; size_t ByteCodeGenerator::PushInstruction(Instruction::Type opcode, const IPLString& arg0, const IPLString& arg1, const IPLString& arg2) @@ -347,6 +348,10 @@ void ByteCodeGenerator::Visit(ForStatement* e) e->GetIteration()->Accept(*this); PushInstruction(Instruction::Type::JMP, compareAddress); m_Code[endAddress].Values.Address[0] = m_Code.size(); + + for (auto breakInstructionAddress : m_BreakInstructionAddresses) + m_Code[breakInstructionAddress].Values.Address[0] = m_Code.size(); + m_BreakInstructionAddresses.clear(); } void ByteCodeGenerator::Visit(SwitchStatement* e) @@ -372,6 +377,10 @@ void ByteCodeGenerator::Visit(SwitchStatement* e) m_SwitchCases.clear(); m_RegisterStack.pop(); + + for (auto breakInstructionAddress : m_BreakInstructionAddresses) + m_Code[breakInstructionAddress].Values.Address[0] = m_Code.size(); + m_BreakInstructionAddresses.clear(); } void ByteCodeGenerator::Visit(CaseStatement* e) @@ -432,6 +441,12 @@ void ByteCodeGenerator::Visit(UnaryExpression* e) PushInstruction(Instruction::Type::SUB, reg, reg, one); } return; + case TokenType::Break: + { + auto breakAddress = PushInstruction(Instruction::Type::JMP); + m_BreakInstructionAddresses.push_back(breakAddress); + } + return; default: NOT_IMPLEMENTED; break; From 78bf391f203001effa02702710854742159626f1 Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Thu, 7 Feb 2019 07:04:09 +0200 Subject: [PATCH 7/7] bytecode generator: implement handling of `continue` statements --- JSImpl/src/ByteCodeGenerator.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/JSImpl/src/ByteCodeGenerator.cpp b/JSImpl/src/ByteCodeGenerator.cpp index a0d1ea40..bcca27fe 100644 --- a/JSImpl/src/ByteCodeGenerator.cpp +++ b/JSImpl/src/ByteCodeGenerator.cpp @@ -111,6 +111,7 @@ class ByteCodeGenerator : public ExpressionVisitor IPLVector> m_SwitchCases; IPLVector m_BreakInstructionAddresses; + IPLVector m_ContinueInstructionAddresses; }; size_t ByteCodeGenerator::PushInstruction(Instruction::Type opcode, const IPLString& arg0, const IPLString& arg1, const IPLString& arg2) @@ -352,6 +353,10 @@ void ByteCodeGenerator::Visit(ForStatement* e) for (auto breakInstructionAddress : m_BreakInstructionAddresses) m_Code[breakInstructionAddress].Values.Address[0] = m_Code.size(); m_BreakInstructionAddresses.clear(); + + for (auto continueInstructionAddress : m_ContinueInstructionAddresses) + m_Code[continueInstructionAddress].Values.Address[0] = compareAddress; + m_ContinueInstructionAddresses.clear(); } void ByteCodeGenerator::Visit(SwitchStatement* e) @@ -447,6 +452,12 @@ void ByteCodeGenerator::Visit(UnaryExpression* e) m_BreakInstructionAddresses.push_back(breakAddress); } return; + case TokenType::Continue: + { + auto continueAddress = PushInstruction(Instruction::Type::JMP); + m_ContinueInstructionAddresses.push_back(continueAddress); + } + return; default: NOT_IMPLEMENTED; break;