Compare commits
6 Commits
211a837468
...
9c5a3397d9
| Author | SHA1 | Date | |
|---|---|---|---|
| 9c5a3397d9 | |||
| df84159ed8 | |||
| 896459bdfa | |||
| 68ba6f9293 | |||
| 06612e2824 | |||
| 0248f47218 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -23,6 +23,7 @@ Makefile
|
|||||||
# Generated files
|
# Generated files
|
||||||
*.log
|
*.log
|
||||||
*.out
|
*.out
|
||||||
|
.cache/
|
||||||
|
|
||||||
# macOS
|
# macOS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
@@ -1,56 +1,78 @@
|
|||||||
cmake_minimum_required(VERSION 3.30)
|
cmake_minimum_required(VERSION 3.30)
|
||||||
project(camellya)
|
project(camellya)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 23)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
|
option(CAMELLYA_BUILD_TESTS "Build tests" ON)
|
||||||
|
option(CAMELLYA_BUILD_STATIC "Build static library" ON)
|
||||||
|
option(CAMELLYA_BUILD_CLI "Build command line interface" ON)
|
||||||
|
|
||||||
# Library sources
|
# Library sources
|
||||||
set(LIB_SOURCES
|
set(LIB_SOURCES
|
||||||
library.cpp
|
src/lexer.cpp
|
||||||
lexer.cpp
|
src/parser.cpp
|
||||||
parser.cpp
|
src/value.cpp
|
||||||
value.cpp
|
src/interpreter.cpp
|
||||||
interpreter.cpp
|
src/state.cpp
|
||||||
state.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Library headers
|
||||||
set(LIB_HEADERS
|
set(LIB_HEADERS
|
||||||
library.h
|
src/camellya.h
|
||||||
lexer.h
|
src/lexer.h
|
||||||
parser.h
|
src/parser.h
|
||||||
ast.h
|
src/ast.h
|
||||||
value.h
|
src/value.h
|
||||||
interpreter.h
|
src/interpreter.h
|
||||||
state.h
|
src/state.h
|
||||||
)
|
)
|
||||||
|
|
||||||
# Build static library
|
if(CAMELLYA_BUILD_STATIC)
|
||||||
add_library(camellya STATIC ${LIB_SOURCES} ${LIB_HEADERS})
|
add_library(libcamellya STATIC ${LIB_SOURCES} ${LIB_HEADERS})
|
||||||
|
elseif()
|
||||||
|
add_library(libcamellya SHARED ${LIB_SOURCES} ${LIB_HEADERS})
|
||||||
|
endif()
|
||||||
|
|
||||||
include(FetchContent)
|
target_include_directories(libcamellya PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||||
|
|
||||||
FetchContent_Declare(
|
if(CAMELLYA_BUILD_CLI)
|
||||||
|
add_executable(camellya
|
||||||
|
cli/main.cpp
|
||||||
|
)
|
||||||
|
target_link_libraries(camellya
|
||||||
|
PRIVATE
|
||||||
|
libcamellya
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(CAMELLYA_BUILD_TESTS)
|
||||||
|
include(FetchContent)
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
Catch2
|
Catch2
|
||||||
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
|
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
|
||||||
GIT_TAG v3.6.0
|
GIT_TAG v3.12.0
|
||||||
)
|
)
|
||||||
|
|
||||||
FetchContent_MakeAvailable(Catch2)
|
FetchContent_MakeAvailable(Catch2)
|
||||||
|
|
||||||
enable_testing()
|
enable_testing()
|
||||||
|
|
||||||
add_executable(camellya_tests
|
add_executable(camellya_tests
|
||||||
tests/test_basic.cpp
|
tests/test_basic.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(camellya_tests
|
target_include_directories(camellya_tests
|
||||||
PRIVATE
|
PRIVATE
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(camellya_tests
|
target_link_libraries(camellya_tests
|
||||||
PRIVATE
|
PRIVATE
|
||||||
camellya
|
libcamellya
|
||||||
Catch2::Catch2WithMain
|
Catch2::Catch2WithMain
|
||||||
)
|
)
|
||||||
|
|
||||||
add_test(NAME camellya_tests COMMAND camellya_tests)
|
add_test(NAME camellya_tests COMMAND camellya_tests)
|
||||||
|
endif()
|
||||||
|
|||||||
22
cli/main.cpp
Normal file
22
cli/main.cpp
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#include "camellya.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <format>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
if(argc < 2){
|
||||||
|
std::cout << std::format("Usage: camellya <script> \n") << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now();
|
||||||
|
camellya::State state;
|
||||||
|
bool success = state.do_file(argv[1]);
|
||||||
|
if (!success) {
|
||||||
|
std::cerr << "Error: " << state.get_error() << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
std::chrono::high_resolution_clock::time_point end = std::chrono::high_resolution_clock::now();
|
||||||
|
std::chrono::duration<double> duration = end - start;
|
||||||
|
std::cout << std::format("Execution completed in {} seconds. \n", duration.count()) << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -34,6 +34,14 @@ print("List:", numbers);
|
|||||||
print("First element (index 0):", numbers[0]);
|
print("First element (index 0):", numbers[0]);
|
||||||
print("Third element (index 2):", numbers[2]);
|
print("Third element (index 2):", numbers[2]);
|
||||||
|
|
||||||
|
for(number i = 0; i < len(numbers); i = i + 1) {
|
||||||
|
print("List element", numbers[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
while(true) {
|
||||||
|
print("test break");
|
||||||
|
break;
|
||||||
|
}
|
||||||
// Test maps
|
// Test maps
|
||||||
print("\n=== Map Demo ===");
|
print("\n=== Map Demo ===");
|
||||||
map config = {"host": "localhost", "port": "8080"};
|
map config = {"host": "localhost", "port": "8080"};
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
#include "library.h"
|
|
||||||
|
|
||||||
// Implementation file for Camellya scripting language
|
|
||||||
// All implementation is in separate source files
|
|
||||||
@@ -183,6 +183,14 @@ struct ReturnStmt : public Stmt {
|
|||||||
explicit ReturnStmt(ExprPtr value = nullptr) : value(std::move(value)) {}
|
explicit ReturnStmt(ExprPtr value = nullptr) : value(std::move(value)) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct BreakStmt : public Stmt {
|
||||||
|
BreakStmt() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ContinueStmt : public Stmt {
|
||||||
|
ContinueStmt() = default;
|
||||||
|
};
|
||||||
|
|
||||||
struct FunctionDecl : public Stmt {
|
struct FunctionDecl : public Stmt {
|
||||||
std::string name;
|
std::string name;
|
||||||
std::vector<std::pair<std::string, std::string>> parameters; // (type, name)
|
std::vector<std::pair<std::string, std::string>> parameters; // (type, name)
|
||||||
@@ -106,6 +106,10 @@ void Interpreter::execute_statement(const Stmt& stmt) {
|
|||||||
exec_for(*for_stmt);
|
exec_for(*for_stmt);
|
||||||
} else if (auto* return_stmt = dynamic_cast<const ReturnStmt*>(&stmt)) {
|
} else if (auto* return_stmt = dynamic_cast<const ReturnStmt*>(&stmt)) {
|
||||||
exec_return(*return_stmt);
|
exec_return(*return_stmt);
|
||||||
|
} else if (auto* break_stmt = dynamic_cast<const BreakStmt*>(&stmt)) {
|
||||||
|
exec_break(*break_stmt);
|
||||||
|
} else if (auto* continue_stmt = dynamic_cast<const ContinueStmt*>(&stmt)) {
|
||||||
|
exec_continue(*continue_stmt);
|
||||||
} else if (auto* func_decl = dynamic_cast<const FunctionDecl*>(&stmt)) {
|
} else if (auto* func_decl = dynamic_cast<const FunctionDecl*>(&stmt)) {
|
||||||
exec_function_decl(*func_decl);
|
exec_function_decl(*func_decl);
|
||||||
} else if (auto* class_decl = dynamic_cast<const ClassDecl*>(&stmt)) {
|
} else if (auto* class_decl = dynamic_cast<const ClassDecl*>(&stmt)) {
|
||||||
@@ -397,8 +401,17 @@ void Interpreter::exec_if(const IfStmt& stmt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Interpreter::exec_while(const WhileStmt& stmt) {
|
void Interpreter::exec_while(const WhileStmt& stmt) {
|
||||||
|
try {
|
||||||
while (is_truthy(evaluate(*stmt.condition))) {
|
while (is_truthy(evaluate(*stmt.condition))) {
|
||||||
|
try {
|
||||||
execute_statement(*stmt.body);
|
execute_statement(*stmt.body);
|
||||||
|
} catch (const ContinueException&) {
|
||||||
|
// Continue: just proceed to the next iteration (check condition again)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (const BreakException&) {
|
||||||
|
// Break: exit the loop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,12 +425,18 @@ void Interpreter::exec_for(const ForStmt& stmt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
while (!stmt.condition || is_truthy(evaluate(*stmt.condition))) {
|
while (!stmt.condition || is_truthy(evaluate(*stmt.condition))) {
|
||||||
|
try {
|
||||||
execute_statement(*stmt.body);
|
execute_statement(*stmt.body);
|
||||||
|
} catch (const ContinueException&) {
|
||||||
|
// Continue: proceed to increment
|
||||||
|
}
|
||||||
|
|
||||||
if (stmt.increment) {
|
if (stmt.increment) {
|
||||||
evaluate(*stmt.increment);
|
evaluate(*stmt.increment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (const BreakException&) {
|
||||||
|
// Break: exit the loop
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
environment = previous;
|
environment = previous;
|
||||||
throw;
|
throw;
|
||||||
@@ -431,6 +450,14 @@ void Interpreter::exec_return(const ReturnStmt& stmt) {
|
|||||||
throw ReturnException(value);
|
throw ReturnException(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Interpreter::exec_break(const BreakStmt& stmt) {
|
||||||
|
throw BreakException();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::exec_continue(const ContinueStmt& stmt) {
|
||||||
|
throw ContinueException();
|
||||||
|
}
|
||||||
|
|
||||||
void Interpreter::exec_function_decl(const FunctionDecl& stmt) {
|
void Interpreter::exec_function_decl(const FunctionDecl& stmt) {
|
||||||
auto func_decl = std::make_shared<FunctionDecl>(stmt);
|
auto func_decl = std::make_shared<FunctionDecl>(stmt);
|
||||||
auto func = std::make_shared<FunctionValue>(stmt.name, func_decl);
|
auto func = std::make_shared<FunctionValue>(stmt.name, func_decl);
|
||||||
@@ -20,6 +20,9 @@ public:
|
|||||||
explicit ReturnException(ValuePtr value) : value(std::move(value)) {}
|
explicit ReturnException(ValuePtr value) : value(std::move(value)) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class BreakException : public std::exception {};
|
||||||
|
class ContinueException : public std::exception {};
|
||||||
|
|
||||||
class Environment {
|
class Environment {
|
||||||
public:
|
public:
|
||||||
std::shared_ptr<Environment> parent;
|
std::shared_ptr<Environment> parent;
|
||||||
@@ -138,6 +141,8 @@ private:
|
|||||||
void exec_while(const WhileStmt& stmt);
|
void exec_while(const WhileStmt& stmt);
|
||||||
void exec_for(const ForStmt& stmt);
|
void exec_for(const ForStmt& stmt);
|
||||||
void exec_return(const ReturnStmt& stmt);
|
void exec_return(const ReturnStmt& stmt);
|
||||||
|
void exec_break(const BreakStmt& stmt);
|
||||||
|
void exec_continue(const ContinueStmt& stmt);
|
||||||
void exec_function_decl(const FunctionDecl& stmt);
|
void exec_function_decl(const FunctionDecl& stmt);
|
||||||
void exec_class_decl(const ClassDecl& stmt);
|
void exec_class_decl(const ClassDecl& stmt);
|
||||||
|
|
||||||
@@ -61,7 +61,8 @@ void Lexer::skip_whitespace() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Lexer::skip_comment() {
|
void Lexer::skip_comment() {
|
||||||
if (peek() == '/' && peek_next() == '/') {
|
if (peek() == '/') {
|
||||||
|
advance();
|
||||||
while (peek() != '\n' && !is_at_end()) {
|
while (peek() != '\n' && !is_at_end()) {
|
||||||
advance();
|
advance();
|
||||||
}
|
}
|
||||||
@@ -223,6 +224,8 @@ TokenType Lexer::get_keyword_type(const std::string& text) const {
|
|||||||
{"and", TokenType::AND},
|
{"and", TokenType::AND},
|
||||||
{"or", TokenType::OR},
|
{"or", TokenType::OR},
|
||||||
{"this", TokenType::THIS},
|
{"this", TokenType::THIS},
|
||||||
|
{"continue", TokenType::CONTINUE},
|
||||||
|
{"break", TokenType::BREAK},
|
||||||
};
|
};
|
||||||
|
|
||||||
auto it = keywords.find(text);
|
auto it = keywords.find(text);
|
||||||
@@ -12,7 +12,7 @@ enum class TokenType {
|
|||||||
// Keywords
|
// Keywords
|
||||||
CLASS, FUNC, NUMBER, STRING, BOOL, LIST, MAP,
|
CLASS, FUNC, NUMBER, STRING, BOOL, LIST, MAP,
|
||||||
IF, ELSE, WHILE, FOR, RETURN, VAR,
|
IF, ELSE, WHILE, FOR, RETURN, VAR,
|
||||||
TRUE, FALSE, NIL, THIS,
|
TRUE, FALSE, NIL, THIS, CONTINUE, BREAK,
|
||||||
|
|
||||||
// Operators
|
// Operators
|
||||||
PLUS, MINUS, STAR, SLASH, PERCENT,
|
PLUS, MINUS, STAR, SLASH, PERCENT,
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "parser.h"
|
#include "parser.h"
|
||||||
#include <format>
|
#include <format>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
namespace camellya {
|
namespace camellya {
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ Program Parser::parse() {
|
|||||||
try {
|
try {
|
||||||
statements.push_back(declaration());
|
statements.push_back(declaration());
|
||||||
} catch (const ParseError& error) {
|
} catch (const ParseError& error) {
|
||||||
|
std::cerr << error.what() << std::endl;
|
||||||
synchronize();
|
synchronize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,7 +120,6 @@ StmtPtr Parser::class_declaration() {
|
|||||||
members.push_back(function_declaration());
|
members.push_back(function_declaration());
|
||||||
} else {
|
} else {
|
||||||
members.push_back(var_declaration());
|
members.push_back(var_declaration());
|
||||||
consume(TokenType::SEMICOLON, "Expected ';' after field declaration.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,6 +169,7 @@ StmtPtr Parser::var_declaration() {
|
|||||||
if (match({TokenType::EQUAL})) {
|
if (match({TokenType::EQUAL})) {
|
||||||
initializer = expression();
|
initializer = expression();
|
||||||
}
|
}
|
||||||
|
consume(TokenType::SEMICOLON, "Expected ';' after variable declaration.");
|
||||||
|
|
||||||
return std::make_unique<VarDecl>(type_name, name.lexeme, std::move(initializer));
|
return std::make_unique<VarDecl>(type_name, name.lexeme, std::move(initializer));
|
||||||
}
|
}
|
||||||
@@ -177,6 +179,8 @@ StmtPtr Parser::statement() {
|
|||||||
if (match({TokenType::WHILE})) return while_statement();
|
if (match({TokenType::WHILE})) return while_statement();
|
||||||
if (match({TokenType::FOR})) return for_statement();
|
if (match({TokenType::FOR})) return for_statement();
|
||||||
if (match({TokenType::RETURN})) return return_statement();
|
if (match({TokenType::RETURN})) return return_statement();
|
||||||
|
if (match({TokenType::BREAK})) return break_statement();
|
||||||
|
if (match({TokenType::CONTINUE})) return continue_statement();
|
||||||
if (match({TokenType::LEFT_BRACE})) return block_statement();
|
if (match({TokenType::LEFT_BRACE})) return block_statement();
|
||||||
|
|
||||||
return expression_statement();
|
return expression_statement();
|
||||||
@@ -214,7 +218,6 @@ StmtPtr Parser::for_statement() {
|
|||||||
StmtPtr initializer = nullptr;
|
StmtPtr initializer = nullptr;
|
||||||
if (!match({TokenType::SEMICOLON})) {
|
if (!match({TokenType::SEMICOLON})) {
|
||||||
initializer = declaration();
|
initializer = declaration();
|
||||||
consume(TokenType::SEMICOLON, "Expected ';' after for initializer.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ExprPtr condition = nullptr;
|
ExprPtr condition = nullptr;
|
||||||
@@ -246,16 +249,21 @@ StmtPtr Parser::return_statement() {
|
|||||||
return std::make_unique<ReturnStmt>(std::move(value));
|
return std::make_unique<ReturnStmt>(std::move(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StmtPtr Parser::break_statement() {
|
||||||
|
consume(TokenType::SEMICOLON, "Expected ';' after 'break'.");
|
||||||
|
return std::make_unique<BreakStmt>();
|
||||||
|
}
|
||||||
|
|
||||||
|
StmtPtr Parser::continue_statement() {
|
||||||
|
consume(TokenType::SEMICOLON, "Expected ';' after 'continue'.");
|
||||||
|
return std::make_unique<ContinueStmt>();
|
||||||
|
}
|
||||||
|
|
||||||
StmtPtr Parser::block_statement() {
|
StmtPtr Parser::block_statement() {
|
||||||
std::vector<StmtPtr> statements;
|
std::vector<StmtPtr> statements;
|
||||||
|
|
||||||
while (!check(TokenType::RIGHT_BRACE) && !is_at_end()) {
|
while (!check(TokenType::RIGHT_BRACE) && !is_at_end()) {
|
||||||
auto stmt = declaration();
|
statements.push_back(declaration());
|
||||||
// If declaration returned a VarDecl (not a class/function/statement), consume semicolon
|
|
||||||
if (dynamic_cast<VarDecl*>(stmt.get())) {
|
|
||||||
consume(TokenType::SEMICOLON, "Expected ';' after variable declaration.");
|
|
||||||
}
|
|
||||||
statements.push_back(std::move(stmt));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
consume(TokenType::RIGHT_BRACE, "Expected '}' after block.");
|
consume(TokenType::RIGHT_BRACE, "Expected '}' after block.");
|
||||||
@@ -44,6 +44,8 @@ private:
|
|||||||
StmtPtr while_statement();
|
StmtPtr while_statement();
|
||||||
StmtPtr for_statement();
|
StmtPtr for_statement();
|
||||||
StmtPtr return_statement();
|
StmtPtr return_statement();
|
||||||
|
StmtPtr break_statement();
|
||||||
|
StmtPtr continue_statement();
|
||||||
StmtPtr block_statement();
|
StmtPtr block_statement();
|
||||||
StmtPtr expression_statement();
|
StmtPtr expression_statement();
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
#include <catch2/benchmark/catch_benchmark.hpp>
|
#include <catch2/benchmark/catch_benchmark.hpp>
|
||||||
#include "library.h"
|
#include "camellya.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
@@ -148,3 +148,72 @@ TEST_CASE("interpreter performance: simple loop", "[perf][script]") {
|
|||||||
REQUIRE(r_num->value == 499500.0);
|
REQUIRE(r_num->value == 499500.0);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("loop break", "[script][loop]") {
|
||||||
|
State state;
|
||||||
|
const char* script = R"(
|
||||||
|
number sum = 0;
|
||||||
|
for (number i = 0; i < 10; i = i + 1) {
|
||||||
|
if (i == 5) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sum = sum + i;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
REQUIRE(state.do_string(script));
|
||||||
|
auto sum_val = state.get_global("sum");
|
||||||
|
REQUIRE(sum_val);
|
||||||
|
auto sum_num = std::dynamic_pointer_cast<NumberValue>(sum_val);
|
||||||
|
REQUIRE(sum_num->value == 10.0); // 0+1+2+3+4 = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("loop continue", "[script][loop]") {
|
||||||
|
State state;
|
||||||
|
const char* script = R"(
|
||||||
|
number sum = 0;
|
||||||
|
for (number i = 0; i < 5; i = i + 1) {
|
||||||
|
if (i == 2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
sum = sum + i;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
REQUIRE(state.do_string(script));
|
||||||
|
auto sum_val = state.get_global("sum");
|
||||||
|
REQUIRE(sum_val);
|
||||||
|
auto sum_num = std::dynamic_pointer_cast<NumberValue>(sum_val);
|
||||||
|
REQUIRE(sum_num->value == 8.0); // 0+1+3+4 = 8
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("while break and continue", "[script][loop]") {
|
||||||
|
State state;
|
||||||
|
const char* script = R"(
|
||||||
|
number i = 0;
|
||||||
|
number sum = 0;
|
||||||
|
while (i < 10) {
|
||||||
|
i = i + 1;
|
||||||
|
if (i == 3) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (i == 6) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sum = sum + i;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
REQUIRE(state.do_string(script));
|
||||||
|
auto sum_val = state.get_global("sum");
|
||||||
|
REQUIRE(sum_val);
|
||||||
|
auto sum_num = std::dynamic_pointer_cast<NumberValue>(sum_val);
|
||||||
|
REQUIRE(sum_num->value == 12.0);
|
||||||
|
// 1st iter: i=1, sum=1
|
||||||
|
// 2nd iter: i=2, sum=1+2=3
|
||||||
|
// 3rd iter: i=3, continue
|
||||||
|
// 4th iter: i=4, sum=3+4=7
|
||||||
|
// 5th iter: i=5, sum=7+5=12
|
||||||
|
// 6th iter: i=6, break
|
||||||
|
// Result should be 12.0
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user