diff --git a/.gitmodules b/.gitmodules index 4d082dc4..d7eb753b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,7 +3,9 @@ url = ../../tree-sitter/tree-sitter.git [submodule "extern/tree-sitter-lua"] path = extern/tree-sitter-lua - url = ../../Azganoth/tree-sitter-lua.git + url = ../../CFU9/tree-sitter-lua.git + update = rebase + branch = adjustments [submodule "extern/catch2"] path = extern/catch2 url = ../../catchorg/Catch2.git diff --git a/extern/tree-sitter b/extern/tree-sitter index 12341dbb..1dc127e5 160000 --- a/extern/tree-sitter +++ b/extern/tree-sitter @@ -1 +1 @@ -Subproject commit 12341dbbc03075e0b3bdcbf05191efbac78731fe +Subproject commit 1dc127e5dae7550a58a09886a19fdea19196bcf1 diff --git a/extern/tree-sitter-lua b/extern/tree-sitter-lua index d835774e..d1da08cf 160000 --- a/extern/tree-sitter-lua +++ b/extern/tree-sitter-lua @@ -1 +1 @@ -Subproject commit d835774e14fd7073567358cdb8900c6012ea5eef +Subproject commit d1da08cf00ecfa157fce5d020509d790abe067a9 diff --git a/include/tree_sitter/tree_sitter.hpp b/include/tree_sitter/tree_sitter.hpp index cda95056..b64d3a28 100644 --- a/include/tree_sitter/tree_sitter.hpp +++ b/include/tree_sitter/tree_sitter.hpp @@ -306,7 +306,9 @@ const TypeId NODE_VARIABLE_DECLARATION = LUA_LANGUAGE.node_type_id("variable_dec const TypeId NODE_LOCAL_VARIABLE_DECLARATION = LUA_LANGUAGE.node_type_id("local_variable_declaration", true); const TypeId NODE_FIELD_EXPRESSION = LUA_LANGUAGE.node_type_id("field_expression", true); +const TypeId NODE_TABLE_INDEX = LUA_LANGUAGE.node_type_id("table_index", true); const TypeId NODE_VARIABLE_DECLARATOR = LUA_LANGUAGE.node_type_id("variable_declarator", true); +const TypeId NODE_LOCAL_VARIABLE_DECLARATOR = LUA_LANGUAGE.node_type_id("local_variable_declarator", true); const TypeId NODE_DO_STATEMENT = LUA_LANGUAGE.node_type_id("do_statement", true); const TypeId NODE_IF_STATEMENT = LUA_LANGUAGE.node_type_id("if_statement", true); const TypeId NODE_ELSEIF = LUA_LANGUAGE.node_type_id("elseif", true); @@ -382,7 +384,6 @@ class Node { struct unsafe_t {}; // used as a token for the unsafe constructor constexpr static unsafe_t unsafe{}; - /** * Creates a new node from the given tree-sitter node and the tree. * diff --git a/src/details/ast.cpp b/src/details/ast.cpp new file mode 100644 index 00000000..79b6cd81 --- /dev/null +++ b/src/details/ast.cpp @@ -0,0 +1,884 @@ +#include "ast.hpp" +#include +#include +#include +#include +using namespace std::string_literals; +namespace minilua::details { +// Body +Body::Body(std::vector node_vec) : nodes(std::move(node_vec)) {} +auto Body::ret() -> std::optional { + if (nodes.empty() || nodes[nodes.size() - 1].type_id() != ts::NODE_RETURN_STATEMENT) { + return {}; + } else { + return Return(nodes[nodes.size() - 1]); + } +} +auto Body::statements() -> std::vector { + + std::vector::iterator end; + std::vector res; + if (nodes.empty()) { + return res; + } + if ((nodes.end() - 1)->type_id() == ts::NODE_RETURN_STATEMENT) { + res.reserve(nodes.size() - 1); + end = nodes.end() - 1; + } else { + res.reserve(nodes.size()); + end = nodes.end(); + } + std::transform( + nodes.begin(), end, std::back_inserter(res), [](ts::Node node) { return Statement(node); }); + return res; +} +// Identifier +Identifier::Identifier(ts::Node node) : id(node) { + if (node.type_id() != ts::NODE_IDENTIFIER && node.type_id() != ts::NODE_METHOD && + node.type_id() != ts::NODE_PROPERTY_IDENTIFIER && + node.type_id() != ts::NODE_FUNCTION_NAME_FIELD) { + throw std::runtime_error("not an identifier node" + to_string(node.type_id())); + } +} +auto Identifier::str() -> std::string { return id.text(); } +// Program +Program::Program(ts::Node node) : program(node) { + if (node.type_id() != ts::NODE_PROGRAM) { + throw std::runtime_error("not a program node"); + } +} +auto Program::body() -> Body { return Body(program.named_children()); } +// BinOpEnum +/*static std::ostream& operator<<(std::ostream& os, BinOpEnum& binOpEnum) { + switch (binOpEnum) { + case BinOpEnum::ADD: + return os << '+'; + case BinOpEnum::SUB: + return os << '-'; + case BinOpEnum::MUL: + return os << '*'; + case BinOpEnum::DIV: + return os << '/'; + case BinOpEnum::POW: + return os << '^'; + case BinOpEnum::MOD: + return os << '%'; + case BinOpEnum::LEQ: + return os << "<="; + case BinOpEnum::GEQ: + return os << ">="; + case BinOpEnum::EQ: + return os << "=="; + case BinOpEnum::LT: + return os << '<'; + case BinOpEnum::GT: + return os << '>'; + case BinOpEnum::NEQ: + return os << "~="; + case BinOpEnum::AND: + return os << "and"; + case BinOpEnum::OR: + return os << "or"; + case BinOpEnum::CONCAT: + return os << ".."; + case BinOpEnum::BSL: + return os << "<<"; + case BinOpEnum::BSR: + return os << ">>"; + case BinOpEnum::BWNOT: + return os << "~"; + case BinOpEnum::BWAND: + return os << "&"; + case BinOpEnum::BWOR: + return os << "|"; + case BinOpEnum::INTDIV: + return os << "//"; + } + throw std::runtime_error("invalid binary operator"); +};*/ +// BinaryOperation +BinaryOperation::BinaryOperation(ts::Node node) : bin_op(node) { + if (node.type_id() != ts::NODE_BINARY_OPERATION) { + throw std::runtime_error("not a binary_operation node"); + } + assert(node.child_count() == 3); +} +auto BinaryOperation::left() -> Expression { return Expression(bin_op.child(0).value()); } +auto BinaryOperation::right() -> Expression { return Expression(bin_op.child(2).value()); } +auto BinaryOperation::op() -> BinOpEnum { + std::string op_str = bin_op.child(1)->text(); + if (op_str == "+"s) { + return BinOpEnum::ADD; + } else if (op_str == "-"s) { + return BinOpEnum::SUB; + } else if (op_str == "/"s) { + return BinOpEnum::DIV; + } else if (op_str == "*"s) { + return BinOpEnum::MUL; + } else if (op_str == "%"s) { + return BinOpEnum::MOD; + } else if (op_str == "^"s) { + return BinOpEnum::POW; + } else if (op_str == "<"s) { + return BinOpEnum::LT; + } else if (op_str == ">"s) { + return BinOpEnum::GT; + } else if (op_str == "<="s) { + return BinOpEnum::LEQ; + } else if (op_str == ">="s) { + return BinOpEnum::GEQ; + } else if (op_str == "==") { + return BinOpEnum::EQ; + } else if (op_str == "~="s) { + return BinOpEnum::NEQ; + } else if (op_str == ".."s) { + return BinOpEnum::CONCAT; + } else if (op_str == "and"s) { + return BinOpEnum::AND; + } else if (op_str == "or"s) { + return BinOpEnum::OR; + } else if (op_str == "<<"s) { + return BinOpEnum::BSL; + } else if (op_str == ">>"s) { + return BinOpEnum::BSR; + } else if (op_str == "//"s) { + return BinOpEnum::INTDIV; + } else if (op_str == "|"s) { + return BinOpEnum::BWOR; + } else if (op_str == "&"s) { + return BinOpEnum::BWAND; + } else if (op_str == "~"s) { + return BinOpEnum::BWNOT; + } else { + throw std::runtime_error("Unknown Binary Operator: " + op_str); + } +} +// UnaryOperation +UnaryOperation::UnaryOperation(ts::Node node) : un_op(node) { + if (node.type_id() != ts::NODE_UNARY_OPERATION) { + throw std::runtime_error("not an unary_operation node"); + } + assert(node.child_count() == 2); +} + +auto UnaryOperation::op() -> UnOpEnum { + if (this->un_op.child(0)->text() == "not"s) { + return UnOpEnum::NOT; + } else if (this->un_op.child(0)->text() == "-"s) { + return UnOpEnum::NEG; + } else if (this->un_op.child(0)->text() == "~"s) { + return UnOpEnum::BWNOT; + } else if (this->un_op.child(0)->text() == "#"s) { + return UnOpEnum::LEN; + } else { + throw std::runtime_error("unknown Unary Operator: " + this->un_op.child(0)->text()); + } +} + +auto UnaryOperation::exp() -> Expression { return Expression(un_op.child(0).value()); } + +/*std::ostream& operator<<(std::ostream& os, UnaryOperation& unaryOperation){ + return os << (int)unaryOperation.op() << unaryOperation.exp().type(); +}*/ + +// ForStatement +ForStatement::ForStatement(ts::Node node) : for_statement(node) { + if (node.type_id() != ts::NODE_FOR_STATEMENT) { + throw std::runtime_error("not a for_statement node"); + } + assert(node.named_child(0).has_value() && + node.named_child(0).value().type_id() == ts::NODE_LOOP_EXPRESSION); +} +auto ForStatement::body() -> Body { + std::vector body = for_statement.named_children(); + body.erase(body.begin()); + return Body(body); +} + +auto ForStatement::loop_exp() -> LoopExpression { + return LoopExpression(for_statement.named_child(0).value()); +} + +LoopExpression::LoopExpression(ts::Node node) : loop_exp(node) { + if (node.type_id() != ts::NODE_LOOP_EXPRESSION) { + throw std::runtime_error("not a loop_expression node"); + } + assert(node.named_child_count() == 3 || node.named_child_count() == 4); +} +auto LoopExpression::variable() -> Identifier { + return Identifier(loop_exp.named_child(0).value()); +} +auto LoopExpression::end() -> Expression { + if (loop_exp.named_child_count() == 4) { + return Expression(loop_exp.named_child(3).value()); + } else { + return Expression(loop_exp.named_child(2).value()); + } +} +auto LoopExpression::start() -> Expression { return Expression(loop_exp.named_child(1).value()); } +auto LoopExpression::step() -> std::optional { + if (loop_exp.named_child_count() == 4) { + return Expression(loop_exp.named_child(2).value()); + } else { + return {}; + } +} +InLoopExpression::InLoopExpression(ts::Node node) : loop_exp(node) { + if (node.type_id() != ts::NODE_LOOP_EXPRESSION) { + throw std::runtime_error("not a in_loop_expression node"); + } + assert(node.named_child_count()==2); +} +auto InLoopExpression::loop_exps() -> std::vector { + std::vector children = loop_exp.named_child(1).value().named_children(); + std::vector exps; + exps.reserve(children.size()); + std::transform(children.begin(), children.end(),std::back_inserter(exps),[](ts::Node node){return Expression(node);}); + return exps; +} +auto InLoopExpression::loop_vars() -> std::vector { + std::vector children = loop_exp.named_child(0).value().named_children(); + std::vector loop_vars; + loop_vars.reserve(children.size()); + std::transform( + children.begin(), children.end(),std::back_inserter(loop_vars),[](ts::Node node){return Identifier(node);}); + return loop_vars; +} +// ForInStatement +ForInStatement::ForInStatement(ts::Node node) : for_in(node) { + if (node.type_id() != ts::NODE_FOR_IN_STATEMENT) { + throw std::runtime_error("not a for_in_statement node"); + } + assert(node.named_child(0).has_value() && + node.named_child(0)->type_id() == ts::NODE_LOOP_EXPRESSION); +} +auto ForInStatement::loop_exp() -> InLoopExpression { + return InLoopExpression(for_in.named_child(0).value()); +} +auto ForInStatement::body() -> Body { + std::vector children = for_in.named_children(); + children.erase(children.begin()); + return Body(children); +} +// WhileStatement +WhileStatement::WhileStatement(ts::Node node) : while_statement(node) { + if (node.type_id() != ts::NODE_WHILE_STATEMENT) { + throw std::runtime_error("not a while_statement node"); + } + assert(node.named_child(0).has_value() && + node.named_child(0).value().type_id() == ts::NODE_CONDITION_EXPRESSION); +} + +auto WhileStatement::body() -> Body { + std::vector body = while_statement.named_children(); + body.erase(body.begin()); + return Body(body); +} +auto WhileStatement::exit_cond() -> Expression { + return Expression(while_statement.named_child(0).value().named_child(0).value()); +} +RepeatStatement::RepeatStatement(ts::Node node) : repeat_statement(node) { + if (node.type_id() != ts::NODE_REPEAT_STATEMENT) + { + throw std::runtime_error("not a repeat_statement node"); + } + assert(node.named_child_count()>=1 && (node.named_children().end() - 1)->type_id() == ts::NODE_CONDITION_EXPRESSION); +} +auto RepeatStatement::body() -> Body { + std::vector body = repeat_statement.named_children(); + body.pop_back(); + return Body(body); +} +auto RepeatStatement::until_cond() -> Expression { + return Expression(repeat_statement.named_child(repeat_statement.named_child_count() - 1) + .value() + .named_child(0) + .value()); +} +// If +IfStatement::IfStatement(ts::Node node) : if_statement(node) { + if (node.type_id() != ts::NODE_IF_STATEMENT) { + throw std::runtime_error("not an if_statement node"); + } + assert(node.named_child(0).has_value() && + node.named_child(0).value().type_id() == ts::NODE_CONDITION_EXPRESSION); +} +auto IfStatement::cond() -> Expression { + return Expression(if_statement.named_child(0).value().named_child(0).value()); +} +auto IfStatement::else_() -> std::optional { + if (if_statement.named_child(if_statement.named_child_count() - 1).has_value() && + if_statement.named_child(if_statement.named_child_count() - 1)->type_id() == + ts::NODE_ELSE) { + return Else(if_statement.named_child(if_statement.named_child_count() - 1).value()); + } else { + return {}; + } +} +auto IfStatement::elseifs() -> std::vector { + if (if_statement.named_child_count() == 1) { + return std::vector(); + } + std::vector if_children = if_statement.named_children(); + std::vector::iterator end; + std::vector res; + if ((if_children.end() - 1)->type_id() == ts::NODE_ELSE) { + end = if_children.end() - 1; + } else { + end = if_children.end(); + } + auto begin = end; + while ((begin - 1)->type_id() == ts::NODE_ELSEIF && begin - 1 != if_children.begin()) { + begin--; + } + if (begin == end) { + return std::vector(); + } + std::transform(begin, end, std::back_inserter(res), [](ts::Node node) { return ElseIf(node); }); + return res; +} +auto IfStatement::body() -> Body { + if (if_statement.named_child_count() == 1 || + (if_statement.named_child(1).has_value() && + if_statement.named_child(1)->type_id() == ts::NODE_ELSEIF)) { + return Body(std::vector()); + } + std::vector if_children = if_statement.named_children(); + std::vector::iterator end; + + if ((if_children.end() - 1)->type_id() == ts::NODE_ELSE) { + end = if_children.end() - 1; + } else { + end = if_children.end(); + } + while ((end - 1)->type_id() == ts::NODE_ELSEIF) { + end--; + } + return Body(std::vector(if_children.begin() + 1, end)); +} +// Else +Else::Else(ts::Node node) : else_statement(node) { + if (node.type_id() != ts::NODE_ELSE) { + throw std::runtime_error("Not an else_statement node"); + } +} +auto Else::body() -> Body { return Body(else_statement.named_children()); } +// ElseIf +ElseIf::ElseIf(ts::Node node) : else_if(node) { + if (node.type_id() != ts::NODE_ELSEIF) { + throw std::runtime_error("not a else_if node"); + } + assert(node.named_child(0).has_value() && + node.named_child(0).value().type_id() == ts::NODE_CONDITION_EXPRESSION); +} +auto ElseIf::body() -> Body { + std::vector body = else_if.named_children(); + body.erase(body.begin()); + return Body(body); +} +auto ElseIf::cond() -> Expression { + return Expression(else_if.named_child(0).value().named_child(0).value()); +} +// Return +Return::Return(ts::Node node) : expressions(node) {} +auto Return::explist() -> std::vector { + std::vector exps = expressions.named_children(); + std::vector::iterator end; + std::vector res; + if (exps.size() > 1 && exps[exps.size() - 1].text() == ";"s) { + end = exps.end() - 1; + res.reserve(exps.size() - 1); + } else { + end = exps.end(); + res.reserve(exps.size()); + } + std::transform( + exps.begin(), end, std::back_inserter(res), [](ts::Node node) { return Expression(node); }); + return res; +} +// VariableDeclaration +VariableDeclaration::VariableDeclaration(ts::Node node) : var_dec(node) { + if (node.type_id() != ts::NODE_VARIABLE_DECLARATION && node.type_id() != ts::NODE_LOCAL_VARIABLE_DECLARATION) { + throw std::runtime_error("not a variable_declaration node"); + }else{ + local_dec = node.type_id() == ts::NODE_LOCAL_VARIABLE_DECLARATION; + } +} +auto VariableDeclaration::declarations() -> std::vector { + std::vector res; + if(local_dec){ + std::vector nodes = var_dec.named_children(); + res.reserve(nodes.size() - 1); + std::transform(nodes.begin() + 1, nodes.end(), std::back_inserter(res), [](ts::Node node) { + return Expression(node); + }); + return res; + }else { + std::vector nodes = var_dec.named_children(); + auto it = nodes.begin(); + while (it->type_id() == ts::NODE_VARIABLE_DECLARATOR) { + it++; + } + res.reserve(nodes.size() - (it - nodes.begin())); + std::transform(it, nodes.end(), std::back_inserter(res), [](ts::Node node) { + return Expression(node); + }); + return res; + } +} +auto VariableDeclaration::declarators() -> std::vector { + if(local_dec){ + std::vector nodes = var_dec.named_child(0).value().named_children(); + std::vector res; + res.reserve(nodes.size()); + std::transform(nodes.begin(), nodes.end(), std::back_inserter(res), [](ts::Node node) { + return VariableDeclarator(node); + }); + return res; + }else { + std::vector nodes = var_dec.named_children(); + std::vector res; + auto it = nodes.begin(); + while (it->type_id() == ts::NODE_VARIABLE_DECLARATOR) { + it++; + } + res.reserve(it - nodes.begin()); + std::transform(nodes.begin(), it, std::back_inserter(res), [](ts::Node node) { + return VariableDeclarator(node); + }); + return res; + } +} +auto VariableDeclaration::local() -> bool { + return local_dec; +} +// VariableDeclarator +VariableDeclarator::VariableDeclarator(ts::Node node) : dec(node) { + if (node.type_id() != ts::NODE_VARIABLE_DECLARATOR && node.type_id() != ts::NODE_IDENTIFIER && + node.type_id() != ts::NODE_FIELD_EXPRESSION && + node.type_id() != ts::NODE_TABLE_INDEX) { + throw std::runtime_error("not a variable declarator"); + } +} +auto VariableDeclarator::var() -> std::variant { + if (dec.type_id() == ts::NODE_IDENTIFIER) { + return Identifier(dec); + } else if (dec.type_id() == ts::NODE_FIELD_EXPRESSION) { + return FieldExpression(dec); + } else if(dec.type_id() == ts::NODE_TABLE_INDEX){ + return TableIndex(dec); + } + if (dec.named_child(0).has_value()) { + if (dec.named_child(0)->type_id() == ts::NODE_IDENTIFIER) { + return Identifier(dec.named_child(0).value()); + } else if (dec.named_child(0)->type_id() == ts::NODE_FIELD_EXPRESSION) { + return FieldExpression(dec.named_child(0).value()); + } else if (dec.named_child(0)->type_id() == ts::NODE_TABLE_INDEX) { + return TableIndex(dec.named_child(0).value()); + } else { + throw std::runtime_error("invalid variable declarator"); + } + }else{ + throw std::runtime_error("invalid variable declarator"); + } +} +//TableIndex +TableIndex::TableIndex(ts::Node node) : table_index(node) { + if (node.type_id() != ts::NODE_TABLE_INDEX) { + throw std::runtime_error("not a table_index node"); + } + assert(node.named_child_count()==2); +} +auto TableIndex::table() -> Prefix { + return Prefix(table_index.named_child(0).value()); +} +auto TableIndex::index() -> Expression { + return Expression(table_index.named_child(1).value()); +} +// DoStatement +DoStatement::DoStatement(ts::Node node) : do_statement(node) { + if (node.type_id() != ts::NODE_DO_STATEMENT) { + throw std::runtime_error("not a do_statement node"); + } +} +auto DoStatement::body() -> Body { return Body(do_statement.named_children()); } +// FieldExpression +FieldExpression::FieldExpression(ts::Node node) : exp(node) { + if (node.type_id() != ts::NODE_FIELD_EXPRESSION) { + throw std::runtime_error("not a field_expression node"); + } + assert(node.named_child_count() == 2); +} +auto FieldExpression::table_id() -> Prefix { return Prefix(exp.named_child(0).value()); } +auto FieldExpression::property_id() -> Identifier { return Identifier(exp.named_child(1).value()); } +// Label +Label::Label(ts::Node node) : label(node) { + if (node.type_id() != ts::NODE_LABEL_STATEMENT) { + throw std::runtime_error("not a label node"); + } + assert(node.named_child_count()==1); +} +auto Label::id() -> Identifier { return Identifier(label.named_child(0).value()); } +// GoTo +GoTo::GoTo(ts::Node node) : go_to(node) { + if (node.type_id() != ts::NODE_GOTO_STATEMENT) { + throw std::runtime_error("not a go_to node"); + } + assert(node.named_child_count()==1); +} +auto GoTo::label() -> Identifier { return Identifier(go_to.named_child(0).value()); } +// Parameters +Parameters::Parameters(ts::Node node) : parameters(node) { + if (node.type_id() != ts::NODE_PARAMETERS) { + throw std::runtime_error("not a parameters node"); + } +} +auto Parameters::leading_self() -> bool { + if (parameters.named_child_count() < 1) { + return false; + } else { + return parameters.named_child(0)->type_id() == ts::NODE_SELF; + } +} +auto Parameters::spread() -> SpreadPos { + if (parameters.named_child_count() < 1) { + return SpreadPos::NO_SPREAD; + } else if (parameters.named_child(0)->type_id() == ts::NODE_SPREAD) { + return SpreadPos::BEGIN; + } else if ( + parameters.named_child(parameters.named_child_count() - 1)->type_id() == ts::NODE_SPREAD) { + return SpreadPos::END; + } else { + return SpreadPos::NO_SPREAD; + } +} +auto Parameters::params() -> std::vector { + SpreadPos sp = spread(); + std::vector res; + std::vector children = parameters.named_children(); + if (leading_self()) { + if (sp == NO_SPREAD) { + if (parameters.named_child_count() < 1) { + return res; + } + res.reserve(parameters.named_child_count() - 1); + std::transform( + children.begin() + 1, children.end(), std::back_inserter(res), + [](ts::Node node) { return Identifier(node); }); + } else { + if (parameters.named_child_count() < 2) { + return res; + } + res.reserve(parameters.named_child_count() - 2); + std::transform( + children.begin() + 1, children.end() - 1, std::back_inserter(res), + [](ts::Node node) { return Identifier(node); }); + } + } else { + switch (sp) { + case NO_SPREAD: + res.reserve(parameters.named_child_count()); + std::transform( + children.begin(), children.end(), std::back_inserter(res), + [](ts::Node node) { return Identifier(node); }); + break; + case BEGIN: + if (parameters.named_child_count() < 1) { + return res; + } + res.reserve(parameters.named_child_count() - 1); + std::transform( + children.begin() + 1, children.end(), std::back_inserter(res), + [](ts::Node node) { return Identifier(node); }); + break; + case END: + if (parameters.named_child_count() < 1) { + return res; + } + res.reserve(parameters.named_child_count() - 1); + std::transform( + children.begin(), children.end() - 1, std::back_inserter(res), + [](ts::Node node) { return Identifier(node); }); + break; + } + } + return res; +} +// FunctionName +FunctionName::FunctionName(ts::Node node) : func_name(node) { + if (node.type_id() != ts::NODE_FUNCTION_NAME) { + throw std::runtime_error("Not a function_name node"); + } +} +auto FunctionName::method() -> std::optional { + if (func_name.named_child(func_name.named_child_count() - 1)->type_id() == ts::NODE_METHOD) { + return Identifier(func_name.named_child(func_name.named_child_count() - 1).value()); + } else { + return {}; + } +} +auto FunctionName::identifier() -> std::vector { + if (func_name.named_child(0)->type_id() == ts::NODE_IDENTIFIER) { + return std::vector{Identifier(func_name.named_child(0).value())}; + } else { + std::vector res; + std::vector children = func_name.named_child(0).value().named_children(); + res.reserve(children.size()); + std::transform( + children.begin(), children.end(), std::back_inserter(res), + [](ts::Node node) { return Identifier(node); }); + return res; + } +} +// FunctionDefinition +FunctionDefinition::FunctionDefinition(ts::Node node) : func_def(node) { + if (node.type_id() != ts::NODE_FUNCTION_DEFINITION) { + throw std::runtime_error("not a function_definition node"); + } +} +auto FunctionDefinition::parameters() -> Parameters { + return Parameters(func_def.named_child(0).value()); +} +auto FunctionDefinition::body() -> Body { + std::vector children = func_def.named_children(); + return Body(std::vector(children.begin() + 1, children.end())); +} +// FunctionStatement +FunctionStatement::FunctionStatement(ts::Node node) : func_stat(node) { + if (node.type_id() != ts::NODE_FUNCTION) { + throw std::runtime_error("not a function(_statement) node"); + } +} +auto FunctionStatement::body() -> Body { + std::vector children = func_stat.named_children(); + return Body(std::vector(children.begin() + 2, children.end())); +} +auto FunctionStatement::name() -> FunctionName { + return FunctionName(func_stat.named_child(0).value()); +} +auto FunctionStatement::parameters() -> Parameters { + return Parameters(func_stat.named_child(1).value()); +} +// LocalFunctionStatement +LocalFunctionStatement::LocalFunctionStatement(ts::Node node) : func_stat(node) { + if (node.type_id() != ts::NODE_LOCAL_FUNCTION) { + throw std::runtime_error("not a function(_statement) node"); + } +} +auto LocalFunctionStatement::parameters() -> Parameters { + return Parameters(func_stat.named_child(1).value()); +} +auto LocalFunctionStatement::name() -> Identifier { + return Identifier(func_stat.named_child(0).value()); +} +auto LocalFunctionStatement::body() -> Body { + std::vector children = func_stat.named_children(); + return Body(std::vector(children.begin() + 2, children.end())); +} +// FunctionCall +FunctionCall::FunctionCall(ts::Node node) : func_call(node) { + if (node.type_id() != ts::NODE_FUNCTION_CALL) { + throw runtime_error("not a function_call node"); + } + assert(node.named_child_count()==2 || node.named_child_count()==3); +} +auto FunctionCall::method() -> std::optional { + if (func_call.named_child_count() == 3) { + return Identifier(func_call.named_child(1).value()); + } else { + return {}; + } +} +auto FunctionCall::id() -> Prefix { return Prefix(func_call.named_child(0).value()); } +auto FunctionCall::args() -> std::vector { + std::vector arg_nodes = + func_call.named_child(func_call.named_child_count() - 1).value().named_children(); + std::vector args; + args.reserve(arg_nodes.size()); + std::transform(arg_nodes.begin(), arg_nodes.end(), std::back_inserter(args), [](ts::Node node) { + return Expression(node); + }); + return args; +} +// GlobalVariable +GlobalVariable::GlobalVariable(ts::Node node) : g_var(node) { + if (node.type_id() != ts::NODE_GLOBAL_VARIABLE || + !(node.text() == "_G" || node.text() == "_VERSION")) { + throw runtime_error("not a global variable node"); + } +} +auto GlobalVariable::type() -> GV { + if (g_var.text() == "_G") { + return GV::_G; + } else { + return GV::_VERSION; + } +} +// Table +Table::Table(ts::Node node) : table(node) { + if (node.type_id() != ts::NODE_TABLE) { + throw runtime_error("not a table node"); + } +} +auto Table::fields() -> std::vector { + if (table.named_child_count() < 1) { + return std::vector(); + } + std::vector fields; + fields.reserve(table.named_child_count()); + std::vector nodes = table.named_children(); + std::transform(nodes.begin(), nodes.end(), std::back_inserter(fields), [](ts::Node node) { + return Field(node); + }); + return fields; +} +// Field +Field::Field(ts::Node node) : field(node) { + if (node.type_id() != ts::NODE_FIELD) { + throw runtime_error("not a field node"); + } + assert(node.named_child_count()==1 || node.named_child_count()==2); +} +auto Field::content() -> std::variant< + std::pair, std::pair, Expression> { + if (field.named_child_count() < 2) { + return Expression(field.named_child(0).value()); + } else if (field.child(0)->text() == "[") { + return std::pair( + Expression(field.named_child(0).value()), Expression(field.named_child(1).value())); + } else { + return std::pair( + Identifier(field.named_child(0).value()), Expression(field.named_child(1).value())); + } +} +// Prefix +Prefix::Prefix(ts::Node node) : prefix(node) { + if (!(node.type_id() == ts::NODE_SELF || node.type_id() == ts::NODE_GLOBAL_VARIABLE || + node.type_id() == ts::NODE_FUNCTION_CALL || node.type_id() == ts::NODE_IDENTIFIER || + node.type_id() == ts::NODE_FIELD_EXPRESSION || node.type_id() == ts::NODE_TABLE_INDEX)) { + throw std::runtime_error("Not a prefix-node"); + } +} +auto Prefix::options() + -> std::variant { + if (prefix.type_id() == ts::NODE_SELF) { + return Self(); + } else if (prefix.type_id() == ts::NODE_GLOBAL_VARIABLE) { + return GlobalVariable(prefix); + } else if (prefix.type_id() == ts::NODE_FUNCTION_CALL) { + return FunctionCall(prefix); + } else if (prefix.child_count() > 0 && prefix.child(0)->text() == "(") { + return Expression(prefix.child(1).value()); + } else if ( + prefix.type_id() == ts::NODE_IDENTIFIER || + prefix.type_id() == ts::NODE_FIELD_EXPRESSION || prefix.type_id() == ts::NODE_TABLE_INDEX) { + return VariableDeclarator(prefix); + } else { + throw std::runtime_error("Not a prefix-node"); + } +} +// Expression +Expression::Expression(ts::Node node) : exp(node) { + if (!(node.type_id() == ts::NODE_SPREAD || node.type_id() == ts::NODE_NEXT || + node.type_id() == ts::NODE_FUNCTION_DEFINITION || node.type_id() == ts::NODE_TABLE || + node.type_id() == ts::NODE_BINARY_OPERATION || + node.type_id() == ts::NODE_UNARY_OPERATION || node.type_id() == ts::NODE_STRING || + node.type_id() == ts::NODE_NUMBER || node.type_id() == ts::NODE_NIL || + node.type_id() == ts::NODE_FALSE || node.type_id() == ts::NODE_TRUE || + node.type_id() == ts::NODE_IDENTIFIER || + (node.type_id() == ts::NODE_SELF || node.type_id() == ts::NODE_GLOBAL_VARIABLE || + node.type_id() == ts::NODE_FUNCTION_CALL || node.child(0)->text() == "("))) { + throw std::runtime_error("Not an expression-node"); + } +} +auto Expression::options() -> std::variant< + Spread, Prefix, Next, FunctionDefinition, Table, BinaryOperation, UnaryOperation, + minilua::Value, Identifier> { + if (exp.type_id() == ts::NODE_SPREAD) { + return Spread(); + } else if (exp.type_id() == ts::NODE_NEXT) { + return Next(); + } else if (exp.type_id() == ts::NODE_FUNCTION_DEFINITION) { + return FunctionDefinition(exp); + } else if (exp.type_id() == ts::NODE_TABLE) { + return Table(exp); + } else if (exp.type_id() == ts::NODE_BINARY_OPERATION) { + return BinaryOperation(exp); + } else if (exp.type_id() == ts::NODE_UNARY_OPERATION) { + return UnaryOperation(exp); + } else if (exp.type_id() == ts::NODE_STRING) { + std::string str = exp.text(); + return minilua::Value(String(std::string(str.begin() + 1, str.end() - 1))); + } else if (exp.type_id() == ts::NODE_NUMBER) { + return parse_number_literal(exp.text()); + } else if (exp.type_id() == ts::NODE_NIL) { + return minilua::Value(Value::Type(Nil())); + } else if (exp.type_id() == ts::NODE_TRUE) { + return minilua::Value(true); + } else if (exp.type_id() == ts::NODE_FALSE) { + return minilua::Value(false); + } else if (exp.type_id() == ts::NODE_IDENTIFIER) { + return Identifier(exp); + } else if ( + exp.type_id() == ts::NODE_SELF || exp.type_id() == ts::NODE_FUNCTION_CALL || + exp.type_id() == ts::NODE_GLOBAL_VARIABLE || exp.type_id() == ts::NODE_FIELD_EXPRESSION || + exp.type_id() == ts::NODE_TABLE_INDEX || + (exp.child(0).has_value() && exp.child(0)->text() == "(")) { + return Prefix(exp); + } else { + throw std::runtime_error("Not an expression-node"); + } +} +// Statement +Statement::Statement(ts::Node node) : statement(node) { + if (!(node.type_id() == ts::NODE_EXPRESSION || + node.type_id() == ts::NODE_VARIABLE_DECLARATION || + node.type_id() == ts::NODE_LOCAL_VARIABLE_DECLARATION || + node.type_id() == ts::NODE_DO_STATEMENT || node.type_id() == ts::NODE_IF_STATEMENT || + node.type_id() == ts::NODE_WHILE_STATEMENT || + node.type_id() == ts::NODE_REPEAT_STATEMENT || node.type_id() == ts::NODE_FOR_STATEMENT || + node.type_id() == ts::NODE_FOR_IN_STATEMENT || + node.type_id() == ts::NODE_GOTO_STATEMENT || node.type_id() == ts::NODE_BREAK_STATEMENT || + node.type_id() == ts::NODE_LABEL_STATEMENT || node.type_id() == ts::NODE_FUNCTION || + node.type_id() == ts::NODE_LOCAL_FUNCTION || node.type_id() == ts::NODE_FUNCTION_CALL || + (node.child(0).has_value() && node.child(0)->text() == ";"))) { + throw std::runtime_error("Not a statement-node" + to_string(node.type_id())); + } +} +auto Statement::options() -> std::variant< + VariableDeclaration, DoStatement, IfStatement, WhileStatement, + RepeatStatement, ForStatement, ForInStatement, GoTo, Break, Label, FunctionStatement, + LocalFunctionStatement, FunctionCall, Expression> { + if (statement.type_id() == ts::NODE_EXPRESSION) { + return Expression(statement.named_child(0).value()); + } else if (statement.type_id() == ts::NODE_VARIABLE_DECLARATION || statement.type_id() == ts::NODE_LOCAL_VARIABLE_DECLARATION) { + return VariableDeclaration(statement); + } else if (statement.type_id() == ts::NODE_DO_STATEMENT) { + return DoStatement(statement); + } else if (statement.type_id() == ts::NODE_IF_STATEMENT) { + return IfStatement(statement); + } else if (statement.type_id() == ts::NODE_WHILE_STATEMENT) { + return WhileStatement(statement); + } else if (statement.type_id() == ts::NODE_REPEAT_STATEMENT) { + return RepeatStatement(statement); + } else if (statement.type_id() == ts::NODE_FOR_STATEMENT) { + return ForStatement(statement); + } else if (statement.type_id() == ts::NODE_FOR_IN_STATEMENT) { + return ForInStatement(statement); + } else if (statement.type_id() == ts::NODE_GOTO_STATEMENT) { + return GoTo(statement); + } else if (statement.type_id() == ts::NODE_BREAK_STATEMENT) { + return Break(); + } else if (statement.type_id() == ts::NODE_LABEL_STATEMENT) { + return Label(statement); + } else if (statement.type_id() == ts::NODE_FUNCTION) { + return FunctionStatement(statement); + } else if (statement.type_id() == ts::NODE_LOCAL_FUNCTION) { + return LocalFunctionStatement(statement); + } else if (statement.type_id() == ts::NODE_FUNCTION_CALL) { + return FunctionCall(statement); + } else { + throw std::runtime_error("Not a statement-node"); + } +} +} // namespace minilua::details \ No newline at end of file diff --git a/src/details/ast.hpp b/src/details/ast.hpp new file mode 100644 index 00000000..c213f754 --- /dev/null +++ b/src/details/ast.hpp @@ -0,0 +1,700 @@ + +#ifndef MINILUA_AST_HPP +#define MINILUA_AST_HPP + +#include "MiniLua/values.hpp" +#include +#include +namespace minilua::details { +#define INDEX_FIELD std::pair +#define ID_FIELD std::pair +// Some forward declarations +class Expression; +class Statement; +class Prefix; +class Return; +class FieldExpression; +/** + * The Body class groups a variable amount of statements together + * the last statement of a Body might be a return_statement + */ +class Body { + std::vector nodes; + +public: + Body(std::vector); + /** + * This method maps the statement Nodes to Statement classes + * @return the statements in this body excluding a potential return_statement + */ + auto statements() -> std::vector; + /** + * This checks for a return node + * the return_statement is always the last statement of the body + * @return a Return class if the body has a return statement + * else an empty optional + */ + auto ret() -> std::optional; +}; +/** + * class for program nodes. It only holds a body + */ +class Program { + ts::Node program; + +public: + Program(ts::Node); + /** + * the children nodes of the Program get put into a Body class by this method + * @return a Body containing the full program + */ + auto body() -> Body; +}; +/** + * class for identifier_nodes + */ +class Identifier { + ts::Node id; + +public: + Identifier(ts::Node); + /** + * get the identifier name as a string + * @return the identifer as a string + */ + auto str() -> std::string; +}; +/** + * this enum holds all possible BinaryOperators in lua + */ +enum class BinOpEnum { + ADD, // + + SUB, // - + MUL, // * + DIV, // / + MOD, // % + POW, // ^ + LT, // < + GT, // > + LEQ, // <= + GEQ, // >= + EQ, // == + NEQ, // ~= + CONCAT, // .. + AND, // and + OR, // or + BSL, // << + BSR, // >> + BWNOT, // ~ + BWOR, // | + BWAND, // & + INTDIV // // +}; +/** + * class for binary_operation nodes + */ +class BinaryOperation { + ts::Node bin_op; + +public: + BinaryOperation(ts::Node node); + /** + * + * @return The left operand + */ + auto left() -> Expression; + /** + * + * @return The right operand + */ + auto right() -> Expression; + /** + * + * @return the operator of the operation + */ + auto op() -> BinOpEnum; +}; +/** + * This enum holds all unary Operators of lua + */ +enum class UnOpEnum { + NOT, // not + NEG, // - + LEN, // # + BWNOT // ~ +}; +/** + * class for unary_operation nodes + */ +class UnaryOperation { + ts::Node un_op; + +public: + UnaryOperation(ts::Node node); + /** + * + * @return the operator of the operation + */ + auto op() -> UnOpEnum; + /** + * + * @return the operand of + */ + auto exp() -> Expression; +}; +/** + * class for loop_expression nodes + */ +class LoopExpression { + ts::Node loop_exp; + +public: + LoopExpression(ts::Node); + /** + * + * @return The identifier of the loop variable + */ + auto variable() -> Identifier; + /** + * + * @return The start value of the loop variable + */ + auto start() -> Expression; + /** + * + * @return the step size of the loop variable if specified + * otherwise an empty optional (then the step size is 1 by default) + */ + auto step() -> std::optional; + /** + * + * @return the end value of the loop variable + */ + auto end() -> Expression; +}; +/** + * class for for_statement nodes + */ +class ForStatement { + ts::Node for_statement; + +public: + ForStatement(ts::Node node); + /** + * + * @return returns the loop expression of the loop + */ + auto loop_exp() -> LoopExpression; + /** + * + * @return a Body containing all statements inside the loop + */ + auto body() -> Body; +}; +/** + * class for in_loop_expression nodes + */ +class InLoopExpression { + ts::Node loop_exp; + +public: + InLoopExpression(ts::Node); + /** + * + * @return all identifiers that are specified as loop variables by the loop + */ + auto loop_vars() -> std::vector; + /** + * the loop expressions usually should eveluate to a functioncall + * @return the loop expressions + */ + auto loop_exps() -> std::vector; +}; +/** + * class for for_in_statement nodes + */ +class ForInStatement { + ts::Node for_in; + +public: + ForInStatement(ts::Node); + /** + * + * @return a Body containing all statements inside the loop + */ + auto body() -> Body; + /** + * + * @return the corresponding loop expression + */ + auto loop_exp() -> InLoopExpression; +}; +/** + * class for while_statement nodes + */ +class WhileStatement { + ts::Node while_statement; + +public: + WhileStatement(ts::Node node); + /** + * This method gets the contitional expression of the loop. In while loops this is always + * checked before executing the body statements + * @return an expression containing the conditional expression of the loop + */ + auto exit_cond() -> Expression; + /** + * + * @return a body with all statements inside the loop + */ + auto body() -> Body; +}; +/** + * class for repeat_statement nodes + */ +class RepeatStatement { + ts::Node repeat_statement; + +public: + RepeatStatement(ts::Node node); + /** + * This method gets the conditional expression of the loop. In repeat loops this is always + * checked after executing the statements of the body + * @return an expression containing the conditional expression of the loop + */ + auto until_cond() -> Expression; + /** + * + * @return a body with all statements inside the loop + */ + auto body() -> Body; +}; +/** + * class for else_if nodes + */ +class ElseIf { + ts::Node else_if; + +public: + ElseIf(ts::Node); + /** + * + * @return a body with all statements inside the else if statement + */ + auto body() -> Body; + /** + * + * @return an expression that holds the conditional expression of the else_if_statement + */ + auto cond() -> Expression; +}; +/** + * class for else nodes + */ +class Else { + ts::Node else_statement; + +public: + Else(ts::Node); + /** + * + * @return a body containing the statements in the else block + */ + auto body() -> Body; +}; +/** + * class for if_statement nodes + */ +class IfStatement { + ts::Node if_statement; + +public: + IfStatement(ts::Node node); + /** + * + * @return a ody conatining the statements of the if block excluding else_if and else statements + */ + auto body() -> Body; + /** + * + * @return all else_if statements that are inside this if_statement + */ + auto elseifs() -> std::vector; + /** + * + * @return an expression that holds the conditional expression of the if_statement + */ + auto cond() -> Expression; + /** + * + * @return an Else class if there is one + * otherwise an empty optional + */ + auto else_() -> std::optional; +}; +/** + * a class for return_statement nodes + */ +class Return { + ts::Node expressions; + +public: + Return(ts::Node node); + /** + * + * @return a vector holding all expressions that will be returned by this statement + * the vector can be empty + */ + auto explist() -> std::vector; +}; +/** + * class for table_index nodes + */ +class TableIndex { + ts::Node table_index; +public: + TableIndex(ts::Node); + /** + * + * @return a prefix that eveluates to the table of this table index + */ + auto table() -> Prefix; + /** + * + * @return an expression that eveluates to the index + */ + auto index() -> Expression; +}; +/** + * class for variable_declarator nodes + */ +class VariableDeclarator { + ts::Node dec; + +public: + VariableDeclarator(ts::Node node); + /** + * + * @return a variant containing the class this variable declarator gets resolved to + */ + auto var() -> std::variant; +}; +/** + * class for variable_declaration and local_variable_declaration nodes + */ +class VariableDeclaration { + ts::Node var_dec; + bool local_dec; +public: + VariableDeclaration(ts::Node node); + /** + * + * @return true if the declaration is local + */ + auto local() -> bool; + /** + * + * @return a vector containing all variables declared by the varaible declaration + */ + auto declarators() -> std::vector; + /** + * + * @return a vector with the expressions that get assigned to to the declared variables + * there should always be at least one element in it + */ + auto declarations() -> std::vector; +}; +/** + * class for field_expression nodes + */ +class FieldExpression { + ts::Node exp; + +public: + FieldExpression(ts::Node); + /** + * + * @return a Prefix containing the Identifier for the table + */ + auto table_id() -> Prefix; + /** + * + * @return an Identifier for a property of the Table + */ + auto property_id() -> Identifier; +}; +/** + * class for do_statement nodes + */ +class DoStatement { + ts::Node do_statement; + +public: + DoStatement(ts::Node); + /** + * + * @return a body containing all statements of the do block + */ + auto body() -> Body; +}; +/** + * class for go_to_statements + */ +class GoTo { + ts::Node go_to; + +public: + GoTo(ts::Node node); + /** + * + * @return the Identifier of the Label that is specified + */ + auto label() -> Identifier; +}; +/** + * class for label_statements + */ +class Label { + ts::Node label; + +public: + Label(ts::Node node); + /** + * + * @return the identifier of this label + */ + auto id() -> Identifier; +}; +/** + * class for function_name nodes + */ +class FunctionName { + ts::Node func_name; + +public: + FunctionName(ts::Node); + /** + * This method looks through the function name and returns Identifiers in order they reference + * on tables e.g. the vector [id_1,id_2] refers to a function name "id_1.id_2" in lua syntax + * @return a vector containing all identifiers used for the function name + * + */ + auto identifier() -> std::vector; + /** + * The identifier returned here is the last one if it is present + * e.g. identifiers() returns [id_1,id_2] and method returns id_5 then the full functionname is: + * "id_1.id_2:id_5" in lua syntax + * @return + * an empty optional if the function is not a method + * the function-/method- name if the function is a method + */ + auto method() -> std::optional; +}; +/** + * an enum defining the different positions a Spread can occur in the parameters of a method + */ +enum SpreadPos { BEGIN, END, NO_SPREAD }; +/** + * a class for parameter nodes + */ +class Parameters { + ts::Node parameters; + +public: + Parameters(ts::Node); + /** + * self can only be the first parameter this method looks if the self keyword is present in this + * parameter list + * @return true if the first parameter is "self" + * false otherwise + */ + auto leading_self() -> bool; + /** + * + * @return a vector containing all parameters excluding a potential spread at the beginning or + * and or a potential self at the beginning + */ + auto params() -> std::vector; + /** + * specifies the position of a potential spread contained within the parameters + * SpreadPos::BEGIN and a leading self is not possible + * @return SpreadPos::BEGIN if there is a spread as the first parameter + * SpreadPos::END if there is a spread as the last parameter + * SpreadPos::NO_SPREAD if there is no spread amongst the parameters + */ + auto spread() -> SpreadPos; +}; +/** + * class for function_definition nodes + */ +class FunctionDefinition { + ts::Node func_def; + +public: + FunctionDefinition(ts::Node); + /** + * + * @return a body containing all statements of this function + */ + auto body() -> Body; + /** + * + * @return the parameters of this function + */ + auto parameters() -> Parameters; +}; +/** + * class for function_statements + */ +class FunctionStatement { + ts::Node func_stat; + +public: + FunctionStatement(ts::Node); + /** + * + * @return a FunctionName class that can be resolved to the function name + */ + auto name() -> FunctionName; + /** + * + * @return a body containing all statements of this function + */ + auto body() -> Body; + /** + * + * @return a Parameter class containing all information about the Parameters of this function + */ + auto parameters() -> Parameters; +}; +/** + * class for local_function_statements + */ +class LocalFunctionStatement { + ts::Node func_stat; + +public: + LocalFunctionStatement(ts::Node); + auto name() -> Identifier; + auto body() -> Body; + auto parameters() -> Parameters; +}; +class FunctionCall { + ts::Node func_call; + +public: + FunctionCall(ts::Node); + /** + * + * @return + * If the call is a method call id() the Prefix should refer to to a table + * else the Prefix states the functionname + */ + auto id() -> Prefix; + /** + * + * @return + * an empty optional if it is not a method call + * the functionname if it is a method call + */ + auto method() -> std::optional; + auto args() -> std::vector; +}; +enum GV { _G, _VERSION }; +class GlobalVariable { + ts::Node g_var; + +public: + GlobalVariable(ts::Node); + /** + * + * @return the Type of this Global Variable + */ + auto type() -> GV; +}; +/** + * class for field_nodes + */ +class Field { + ts::Node field; + +public: + Field(ts::Node); + /** + * this method splits a field into the identifier of the field and the content of the field + * or just returns the expression + * an INDEX_FIELD looks like this in lua: "[INDEX_FIELD.first()] = INDEX_FIELD.second()" + * an ID_FIELD looks like this in lua: "ID_FIELD.first() = ID_FIELD.second" + * the Expression is + * @return a variant containing the right format for the field + */ + auto content() -> std::variant; +}; +/** + * class for table nodes + */ +class Table { + ts::Node table; + +public: + Table(ts::Node); + /** + * + * @return a vector containing all fields of the table + */ + auto fields() -> std::vector; +}; +// a few empty classes that are just used as additional return types +class Spread {}; +class Self {}; +class Next {}; +class Break {}; +/** + * class for prefix nodes + */ +class Prefix { + ts::Node prefix; + +public: + Prefix(ts::Node); + /** + * + * @return a variant containing the class this Prefix gets resolved to + */ + auto options() + -> std::variant; +}; +/** + * class for expression nodes + */ +class Expression { + ts::Node exp; + +public: + Expression(ts::Node); + /** + * + * @return a variant containing the class this expression gets resolved to + */ + auto options() -> std::variant< + Spread, Prefix, Next, FunctionDefinition, Table, BinaryOperation, UnaryOperation, + minilua::Value, Identifier>; +}; + +class Statement { + ts::Node statement; + +public: + Statement(ts::Node); + /** + * + * @return a variant containing the class this statement gets resolved to + */ + auto options() -> std::variant< + VariableDeclaration, DoStatement, IfStatement, WhileStatement, + RepeatStatement, ForStatement, ForInStatement, GoTo, Break, Label, FunctionStatement, + LocalFunctionStatement, FunctionCall, Expression>; +}; +} // namespace minilua::details + +#endif // MINILUA_AST_HPP diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b57ef1e9..3e797863 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -8,14 +8,15 @@ add_executable(MiniLua-tests public_api/origin.cpp public_api/environment.cpp tree_sitter.cpp - stdlib_tests.cpp) + stdlib_tests.cpp + tree_sitter_ast_tests.cpp) target_include_directories(MiniLua-tests PRIVATE ${tree-sitter_SOURCE_DIR}/lib/include) # add include directory to be able to test internal headers target_include_directories(MiniLua-tests PRIVATE ${MiniLua_SOURCE_DIR}/src) target_link_libraries(MiniLua-tests - PRIVATE MiniLua - PRIVATE Catch2::Catch2 - PRIVATE TreeSitter) + PRIVATE MiniLua + PRIVATE Catch2::Catch2 + PRIVATE TreeSitter) if(COVERAGE) setup_target_for_coverage(MiniLua-tests-coverage MiniLua-tests coverage) diff --git a/tests/tree_sitter_ast_tests.cpp b/tests/tree_sitter_ast_tests.cpp new file mode 100644 index 00000000..63752813 --- /dev/null +++ b/tests/tree_sitter_ast_tests.cpp @@ -0,0 +1,804 @@ +#include "details/ast.hpp" +#include "tree_sitter/tree_sitter.hpp" +#include +#include +#include +namespace minilua::details { +TEST_CASE("statements", "[tree-sitter]") { + ts::Parser parser; + std::string source = "i,t,l = 5\n" + "do\n" + "z = i+k\n" + "end\n" + "if z>k then\n" + "else\n" + "end\n" + "while z>k do\n" + "z = z-1\n" + "end\n" + "repeat\n" + "z = z*k\n" + "until z> k^10\n" + "for l = 1,9 do\n" + "z = z-l\n" + "end\n" + "for k, v in next, t, nil do\n" + " print(k, v)\n" + "end\n" + "goto alpha\n" + "break\n" + "::alpha::\n" + "function foo (f,o,o)\n" + "return f,o*o\n" + "end\n" + "local function foo (f,o,oo)\n" + "return f,o*oo\n" + "end\n" + "foo(i,k,z)\n" + "function (a,b)\n" + "print(a .. b)\n" + "end\n"; + ts::Tree tree = parser.parse_string(source); + ts::Node root = tree.root_node(); + auto prog = Program(root); + Body body = prog.body(); + CHECK(!body.ret().has_value()); + vector statement = body.statements(); + CHECK(statement.size() == 14); + long unsigned int statement_count = statement.size(); + // this loop tests if each statement got parsed to the right Class + for (long unsigned int i = 0; i < statement_count; i++) { + CHECK(statement.at(i).options().index() == i); + } +} +TEST_CASE("expressions", "[tree-sitter]") { + uint exp_count = 29; + ts::Parser parser; + std::string source = "...\n" + "next\n" + "function (a,b)\n" + " foo()\n" + "end\n" + "{1,2,3,4,5}\n" + "1+1\n" + "1-1\n" + "1*1\n" + "1/1\n" + "1%1\n" + "1^1\n" + "1<1\n" + "1>1\n" + "1<=1\n" + "1>=1\n" + "1==1\n" + "1~=1\n" + "1 .. a\n" + "true and true\n" + "true or true\n" + "1<<1\n" + "1>>1\n" + "1~1\n" + "1|1\n" + "1&1\n" + "1//1\n" + "nil\n" + "true\n" + "false\n" + "id\n" + "d = not true\n" + "c = -1\n" + "a = #table\n" + "b = ~a\n"; + ts::Tree tree = parser.parse_string(source); + ts::Node root = tree.root_node(); + auto prog = Program(root); + Body body = prog.body(); + CHECK(!body.ret().has_value()); + vector statement = body.statements(); + vector exps; + exps.reserve(exp_count); + std::transform( + statement.begin(), statement.begin() + exp_count, std::back_inserter(exps), + [](Statement statement) { + auto opt = statement.options(); + return *std::get_if(&opt); + }); + auto spread = exps[0].options(); + CHECK(spread.index() == 0); + auto next = exps[1].options(); + CHECK(next.index() == 2); + auto func_def = exps[2].options(); + CHECK(func_def.index() == 3); + auto table = exps[3].options(); + CHECK(table.index() == 4); + vector bin_ops; + bin_ops.reserve(25); + std::transform( + exps.begin() + 4, exps.begin() + 25, std::back_inserter(bin_ops), [](Expression exp) { + auto opt = exp.options(); + return *get_if(&opt); + }); + for (uint i = 0; i < bin_ops.size(); i++) { + CHECK( + bin_ops[i].op() == + (BinOpEnum)i); // Bin Operations are in the same sequence as in the BinOpEnum + } + + auto nil = exps[25].options(); + CHECK(holds_alternative(nil)); + auto* temp = get_if(&nil); + CHECK(temp->is_nil()); + auto _true = exps[26].options(); + CHECK(holds_alternative(_true)); + temp = get_if(&_true); + CHECK(temp->is_bool()); + CHECK(temp == Value(true)); + auto* b = get_if(&(temp->raw())); + CHECK(b->operator bool()); + auto _false = exps[27].options(); + CHECK(holds_alternative(_false)); + temp = get_if(&_false); + CHECK(temp->is_bool()); + b = get_if(&(temp->raw())); + CHECK(!(b->operator bool())); + auto id = exps[28].options(); + CHECK(holds_alternative(id)); + Identifier temp2 = *get_if(&id); + CHECK(temp2.str() == "id"s); + vector un_op; + un_op.reserve(4); + std::transform( + statement.begin() + exp_count, statement.end(), std::back_inserter(un_op), + [](Statement stat) { + auto opt = stat.options(); + auto* vd = std::get_if(&opt); + auto exps = vd->declarations(); + auto exp = exps[0].options(); + CHECK(holds_alternative(exp)); + return *get_if(&exp); + }); + for (uint j = 0; j < un_op.size(); j++) { + CHECK(un_op[j].op() == (UnOpEnum)j); + } +} + +TEST_CASE("do_statements", "[tree-sitter]") { + ts::Parser parser; + std::string source = "do\n" + "end\n" + "do\n" + "1+1\n" + "end\n" + "do\n" + "1+1\n" + "2+2\n" + "end\n" + "do\n" + "return 2\n" + "end\n" + "do\n" + "1+1\n" + "return 1+1\n" + "end"; + ts::Tree tree = parser.parse_string(source); + ts::Node root = tree.root_node(); + auto prog = Program(root); + Body body = prog.body(); + auto stats = body.statements(); + CHECK(stats.size() == 5); + CHECK(!body.ret().has_value()); + std::vector dos; + std::transform(stats.begin(), stats.end(), std::back_inserter(dos), [](Statement stat) { + auto opt = stat.options(); + CHECK(std::holds_alternative(opt)); + return *std::get_if(&opt); + }); + CHECK(dos.size() == 5); + CHECK(dos[0].body().statements().empty()); + CHECK(!dos[0].body().ret().has_value()); + CHECK(dos[1].body().statements().size() == 1); + CHECK(!dos[1].body().ret().has_value()); + CHECK(dos[2].body().statements().size() == 2); + CHECK(!dos[2].body().ret().has_value()); + CHECK(dos[3].body().statements().empty()); + CHECK(dos[3].body().ret().has_value()); + CHECK(dos[4].body().statements().size() == 1); + CHECK(dos[4].body().ret().has_value()); +} +TEST_CASE("if_statements", "[tree-sitter]") { + ts::Parser parser; + std::string source = "if c ifs; + std::transform(stats.begin(), stats.end(), std::back_inserter(ifs), [](Statement stat) { + auto opt = stat.options(); + CHECK(std::holds_alternative(opt)); + return *std::get_if(&opt); + }); + CHECK(ifs.size() == 6); + CHECK(holds_alternative(ifs[0].cond().options())); + CHECK(ifs[0].body().statements().empty()); + CHECK(!ifs[0].body().ret().has_value()); + CHECK(ifs[0].elseifs().size() == 2); + CHECK(holds_alternative(ifs[0].elseifs()[0].cond().options())); + CHECK(ifs[0].elseifs()[0].body().statements().size() == 1); + CHECK(!ifs[0].elseifs()[0].body().ret().has_value()); + CHECK(holds_alternative(ifs[0].elseifs()[1].cond().options())); + CHECK(ifs[0].elseifs()[1].body().statements().size() == 2); + CHECK(!ifs[0].elseifs()[1].body().ret().has_value()); + CHECK(ifs[0].else_().has_value()); + CHECK(ifs[0].else_().value().body().statements().empty()); + CHECK(ifs[0].else_().value().body().ret().has_value()); + CHECK(std::holds_alternative( + ifs[0].else_().value().body().ret().value().explist()[0].options())); + CHECK(ifs[1].body().statements().empty()); + CHECK(ifs[1].body().ret().has_value()); + CHECK(ifs[1].elseifs().empty()); + CHECK(!ifs[1].else_().has_value()); + CHECK(holds_alternative(ifs[1].cond().options())); + CHECK(holds_alternative(ifs[2].cond().options())); + CHECK(ifs[2].body().statements().size() == 1); + CHECK(!ifs[2].body().ret().has_value()); + CHECK(ifs[2].elseifs().empty()); + CHECK(ifs[2].else_().has_value()); + CHECK(ifs[2].else_().value().body().statements().size() == 1); + CHECK(!ifs[2].else_().value().body().ret().has_value()); + CHECK(holds_alternative(ifs[3].cond().options())); + CHECK(ifs[3].body().statements().empty()); + CHECK(!ifs[3].body().ret().has_value()); + CHECK(ifs[3].elseifs().empty()); + CHECK(ifs[3].else_().has_value()); + CHECK(ifs[3].else_().value().body().statements().size() == 1); + CHECK(!ifs[3].else_().value().body().ret().has_value()); + CHECK(holds_alternative(ifs[4].cond().options())); + CHECK(ifs[4].body().statements().size() == 1); + CHECK(!ifs[4].body().ret().has_value()); + CHECK(ifs[4].elseifs().size() == 2); + CHECK(!ifs[4].else_().has_value()); + CHECK(ifs[4].elseifs()[0].body().statements().size() == 1); + CHECK(!ifs[4].elseifs()[0].body().ret().has_value()); + CHECK(ifs[4].elseifs()[1].body().statements().size() == 2); + CHECK(!ifs[4].elseifs()[1].body().ret().has_value()); + CHECK(ifs[5].body().statements().empty()); + CHECK(!ifs[5].body().ret().has_value()); + CHECK(ifs[5].elseifs().empty()); + CHECK(!ifs[5].else_().has_value()); +} +TEST_CASE("for_statements", "[tree-sitter]") { + ts::Parser parser; + std::string source = "for i = 1,2 do\n" + " foo(12)\n" + "end\n" + + "for i = 1,2 do\n" + "end\n" + + "for c = a,b,42 do\n" + " foo()\n" + " s = a+c+s\n" + " f = 36+6\n" + "end"; + ts::Tree tree = parser.parse_string(source); + ts::Node root = tree.root_node(); + auto prog = Program(root); + Body body = prog.body(); + auto stats = body.statements(); + CHECK(stats.size() == 3); + CHECK(!body.ret().has_value()); + std::vector fors; + std::transform(stats.begin(), stats.end(), std::back_inserter(fors), [](Statement stat) { + auto opt = stat.options(); + CHECK(std::holds_alternative(opt)); + return *std::get_if(&opt); + }); + CHECK(fors.size() == 3); + // 1st loop + CHECK(fors[0].body().statements().size() == 1); + CHECK(!fors[0].body().ret().has_value()); + CHECK(fors[0].loop_exp().variable().str() == "i"s); + auto start1_opt = fors[0].loop_exp().start().options(); + CHECK(holds_alternative(start1_opt)); + auto* start1 = get_if(&start1_opt); + CHECK(start1->is_number()); + CHECK(holds_alternative(start1->raw())); + if (auto* num1 = get_if(&start1->raw())) { + CHECK(num1->value == 1); + } + auto end1_opt = fors[0].loop_exp().end().options(); + CHECK(holds_alternative(end1_opt)); + auto* end1 = get_if(&end1_opt); + CHECK(end1->is_number()); + CHECK(holds_alternative(end1->raw())); + if (auto* num2 = get_if(&end1->raw())) { + CHECK(num2->value == 2); + } + CHECK(!fors[0].loop_exp().step().has_value()); + // 2nd loop just to check if the empty body works fine here + CHECK(fors[1].body().statements().empty()); + // 3rd loop + CHECK(fors[2].body().statements().size() == 3); + CHECK(!fors[2].body().ret().has_value()); + // checking the loopexpression + CHECK(fors[2].loop_exp().variable().str() == "c"s); + auto start3_opt = fors[2].loop_exp().start().options(); + CHECK(holds_alternative(start3_opt)); + auto start3 = get_if(&start3_opt); + CHECK(start3->str() == "a"s); + CHECK(fors[2].loop_exp().step().has_value()); + auto step3_opt = fors[2].loop_exp().step()->options(); + CHECK(holds_alternative(step3_opt)); + auto step3 = get_if(&step3_opt); + CHECK(step3->str() == "b"s); + auto end3_opt = fors[2].loop_exp().end().options(); + CHECK(holds_alternative(end3_opt)); + auto end3 = get_if(&end3_opt); + CHECK(end3->is_number()); + auto end3_num = get_if(&end3->raw()); + CHECK(end3_num->value == 42); +} +TEST_CASE("for_in_statements", "[tree-sitter]") { + ts::Parser parser; + std::string source = "for k, v in next, t, nil do\n" + " print(k, v)\n" + "end\n" + "for a,b,c,d,e in foo(var) do\n" + " return 1\n" + "end"; + + ts::Tree tree = parser.parse_string(source); + ts::Node root = tree.root_node(); + auto prog = Program(root); + Body body = prog.body(); + auto stats = body.statements(); + CHECK(stats.size() == 2); + CHECK(!body.ret().has_value()); + std::vector fors; + std::transform(stats.begin(), stats.end(), std::back_inserter(fors), [](Statement stat) { + auto opt = stat.options(); + CHECK(std::holds_alternative(opt)); + return *std::get_if(&opt); + }); + CHECK(fors.size() == 2); + CHECK(fors[0].loop_exp().loop_vars().size() == 2); + CHECK(fors[0].loop_exp().loop_exps().size() == 3); + CHECK(fors[1].loop_exp().loop_vars().size() == 5); + CHECK(fors[1].loop_exp().loop_exps().size() == 1); +} +TEST_CASE("function_statements", "[tree-sitter]") { + ts::Parser parser; + std::string source = "function foo (a,b,c)\n" + " 1+1\n" + " return 3+3\n" + "end\n" + "function table.prop1.prop2.prop3:method (a,b,c)\n" + " return true\n" + "end\n" + "function foo(self,...)\n" + "end\n" + "function foo(...)\n" + "end\n" + "function foo(self)\n" + "end\n" + "function foo (self,a,b,c)\n" + "end\n" + "function foo (a,b,c,...)\n" + "end\n" + "function foo (...,a,b,c)\n" + "end\n" + "function foo(self,a,b,c,...)\n" + "end\n" + "function foo()\n" + "end"; + ts::Tree tree = parser.parse_string(source); + ts::Node root = tree.root_node(); + auto prog = Program(root); + Body body = prog.body(); + auto stats = body.statements(); + CHECK(stats.size() == 10); + CHECK(!body.ret().has_value()); + std::vector func; + std::transform(stats.begin(), stats.end(), std::back_inserter(func), [](Statement stat) { + auto opt = stat.options(); + CHECK(std::holds_alternative(opt)); + return *std::get_if(&opt); + }); + CHECK(func.size() == 10); + CHECK(func[1].name().method().has_value()); + CHECK(func[1].name().identifier().size() == 4); + CHECK(func[0].name().identifier().size() == 1); + std::vector vec{"a", "b", "c"}; + std::vector params; + vector identifiers; + for (uint i = 0; i < 9; i++) { + if (i < 2 || i > 4) { + CHECK(func[i].parameters().params().size() == 3); + params.clear(); + identifiers = func[i].parameters().params(); + std::transform( + identifiers.begin(), identifiers.end(), std::back_inserter(params), + [](Identifier id) { return id.str(); }); + CHECK(params == vec); + } else { + CHECK(func[i].parameters().params().empty()); + } + switch (i) { + case 0: + case 1: + CHECK(!func[i].parameters().leading_self()); + CHECK(func[i].parameters().spread() == NO_SPREAD); + break; + case 2: + CHECK(func[i].parameters().leading_self()); + CHECK(func[i].parameters().spread() == END); + break; + case 3: + CHECK(!func[i].parameters().leading_self()); + CHECK(func[i].parameters().spread() == BEGIN); + break; + case 4: + case 5: + CHECK(func[i].parameters().leading_self()); + CHECK(func[i].parameters().spread() == NO_SPREAD); + break; + case 6: + CHECK(!func[i].parameters().leading_self()); + CHECK(func[i].parameters().spread() == END); + break; + case 7: + CHECK(!func[i].parameters().leading_self()); + CHECK(func[i].parameters().spread() == BEGIN); + break; + case 8: + CHECK(func[i].parameters().leading_self()); + CHECK(func[i].parameters().spread() == END); + break; + } + } + CHECK(!func[9].parameters().leading_self()); + CHECK(func[9].parameters().spread() == NO_SPREAD); + CHECK(func[9].parameters().params().empty()); +} +TEST_CASE("while_and_repeat_statements", "[tree-sitter]") { + ts::Parser parser; + std::string source = "while i<#table do\n" + " i=i+1\n" + "end\n" + "while i<#table do\n" + "end\n" + "" + "repeat\n" + "until i>42\n" + "repeat\n" + " i=i*9;" + " return 1,2,3\n" + "until b\n"; + ts::Tree tree = parser.parse_string(source); + ts::Node root = tree.root_node(); + auto prog = Program(root); + Body body = prog.body(); + auto stats = body.statements(); + CHECK(stats.size() == 4); + auto opt1 = stats[0].options(); + CHECK(std::holds_alternative(opt1)); + auto while_stat = std::get_if(&opt1); + CHECK(holds_alternative(while_stat->exit_cond().options())); + CHECK(while_stat->body().statements().size() == 1); + CHECK(!while_stat->body().ret().has_value()); + auto opt2 = stats[1].options(); + CHECK(std::holds_alternative(opt2)); + while_stat = std::get_if(&opt2); + CHECK(holds_alternative(while_stat->exit_cond().options())); + CHECK(while_stat->body().statements().empty()); + CHECK(!while_stat->body().ret().has_value()); + auto opt3 = stats[2].options(); + CHECK(std::holds_alternative(opt3)); + auto repeat_stat = std::get_if(&opt3); + CHECK(holds_alternative(repeat_stat->until_cond().options())); + CHECK(repeat_stat->body().statements().empty()); + CHECK(!repeat_stat->body().ret().has_value()); + auto opt4 = stats[3].options(); + CHECK(std::holds_alternative(opt4)); + repeat_stat = std::get_if(&opt4); + CHECK(holds_alternative(repeat_stat->until_cond().options())); + CHECK(repeat_stat->body().statements().size() == 1); + CHECK(repeat_stat->body().ret().has_value()); +} +TEST_CASE("return_statements", "[tree-sitter]") { + ts::Parser parser; + std::string source = "do\n" + "return a,b,c,d;\n" + "end\n" + "do\n" + "return\n" + "end\n" + "do\n" + "return a\n" + "end"; + ts::Tree tree = parser.parse_string(source); + ts::Node root = tree.root_node(); + auto prog = Program(root); + Body body = prog.body(); + auto stats = body.statements(); + CHECK(stats.size() == 3); + std::vector returns; + std::transform(stats.begin(), stats.end(), std::back_inserter(returns), [](Statement stat) { + auto opt = stat.options(); + CHECK(std::holds_alternative(opt)); + auto do_stat = *std::get_if(&opt); + CHECK(do_stat.body().ret().has_value()); + return do_stat.body().ret().value(); + }); + CHECK(returns[0].explist().size() == 4); + for (uint i = 0; i < 4; i++) { + CHECK(std::holds_alternative(returns[0].explist()[i].options())); + } + CHECK(returns[1].explist().empty()); + CHECK(returns[2].explist().size() == 1); + CHECK(std::holds_alternative(returns[2].explist()[0].options())); +} +TEST_CASE("var_dec_statements", "[tree-sitter]") { + ts::Parser parser; + std::string source = "a = 1\n" + "a,b,c,d = 1+2,5+7,a\n" + "local e\n" + "local f,g,h\n" + "local i,j = 42,96\n" + "\n" + "table1.table2.field1 = function() print(2) end\n" + "table1[table2] = 2\n"; + ts::Tree tree = parser.parse_string(source); + ts::Node root = tree.root_node(); + auto prog = Program(root); + Body body = prog.body(); + auto stats = body.statements(); + CHECK(stats.size() == 7); + // 1st statement + auto opt1 = stats[0].options(); + CHECK(std::holds_alternative(opt1)); + auto var_dec1 = std::get_if(&opt1); + CHECK(!var_dec1 -> local()); + CHECK(var_dec1->declarations().size() == 1); + CHECK(var_dec1->declarators().size() == 1); + CHECK(std::holds_alternative(var_dec1->declarators()[0].var())); + CHECK(std::holds_alternative(var_dec1->declarations()[0].options())); + // 2nd statement + auto opt2 = stats[1].options(); + CHECK(std::holds_alternative(opt2)); + auto var_dec2 = std::get_if(&opt2); + CHECK(!var_dec2 -> local()); + CHECK(var_dec2->declarations().size() == 3); + CHECK(var_dec2->declarators().size() == 4); + for (uint i = 0; i < 4; i++) { + CHECK(std::holds_alternative(var_dec2->declarators()[i].var())); + } + CHECK(std::holds_alternative(var_dec2->declarations()[0].options())); + CHECK(std::holds_alternative(var_dec2->declarations()[1].options())); + CHECK(std::holds_alternative(var_dec2->declarations()[2].options())); + // 3rd statement + auto opt3 = stats[2].options(); + CHECK(std::holds_alternative(opt3)); + auto var_dec3 = std::get_if(&opt3); + CHECK(var_dec3->local()); + CHECK(var_dec3->declarations().empty()); + CHECK(var_dec3->declarators().size() == 1); + CHECK(std::holds_alternative(var_dec3->declarators()[0].var())); + auto id1_opt = var_dec3->declarators()[0].var(); + auto id1 = std::get_if(&id1_opt); + CHECK(id1 -> str() == "e"s); + // 4th statement + auto opt4 = stats[3].options(); + CHECK(std::holds_alternative(opt4)); + auto var_dec4 = std::get_if(&opt4); + CHECK(var_dec4->local()); + CHECK(var_dec4->declarations().empty()); + CHECK(var_dec4->declarators().size() == 3); + CHECK(std::holds_alternative(var_dec4->declarators()[0].var())); + auto id2_opt = var_dec4->declarators()[0].var(); + auto id2 = std::get_if(&id2_opt); + CHECK(id2 -> str() == "f"s); + CHECK(std::holds_alternative(var_dec4->declarators()[1].var())); + auto id3_opt = var_dec4->declarators()[1].var(); + auto id3 = std::get_if(&id3_opt); + CHECK(id3 -> str() == "g"s); + CHECK(std::holds_alternative(var_dec4->declarators()[2].var())); + auto id4_opt = var_dec4->declarators()[2].var(); + auto id4 = std::get_if(&id4_opt); + CHECK(id4 -> str() == "h"s); + // 5th statement + auto opt5 = stats[4].options(); + CHECK(std::holds_alternative(opt5)); + auto var_dec5 = std::get_if(&opt5); + CHECK(var_dec5->local()); + CHECK(var_dec5->declarations().size() == 2); + CHECK(var_dec5->declarators().size() == 2); + CHECK(std::holds_alternative(var_dec5->declarations()[0].options())); + CHECK(std::holds_alternative(var_dec5->declarations()[1].options())); + CHECK(std::holds_alternative(var_dec5->declarators()[0].var())); + auto id5_opt = var_dec5->declarators()[0].var(); + auto id5 = std::get_if(&id5_opt); + CHECK(id5 -> str() == "i"s); + CHECK(std::holds_alternative(var_dec5->declarators()[1].var())); + auto id6_opt = var_dec5->declarators()[1].var(); + auto id6 = std::get_if(&id6_opt); + CHECK(id6 -> str() == "j"s); + // 6th statement + auto opt6 = stats[5].options(); + CHECK(std::holds_alternative(opt6)); + auto var_dec6 = std::get_if(&opt6); + CHECK(var_dec6->declarators().size() == 1); + auto declarator = var_dec6->declarators()[0]; + auto dec_opt1 = declarator.var(); + CHECK(std::holds_alternative(dec_opt1)); + auto fe1 = std::get_if(&dec_opt1); + CHECK(fe1->property_id().str() == "field1"); + auto prefix1 = fe1->table_id().options(); + CHECK(std::holds_alternative(prefix1)); + auto dec2 = get_if(&prefix1); + auto dec_opt2 = dec2->var(); + CHECK(holds_alternative(dec_opt2)); + auto fe2 = std::get_if(&dec_opt2); + CHECK(fe2->property_id().str() == "table2"); + auto prefix2 = fe2->table_id().options(); + CHECK(std::holds_alternative(prefix2)); + auto dec3 = std::get_if(&prefix2); + auto dec_opt3 = dec3->var(); + CHECK(holds_alternative(dec_opt3)); + auto table_id = std::get_if(&dec_opt3); + CHECK(table_id->str() == "table1"s); + //7th statement + auto opt7 = stats[6].options(); + CHECK(std::holds_alternative(opt7)); + auto var_dec7 = std::get_if(&opt7); + CHECK(var_dec7->declarations().size()==1); + CHECK(var_dec7->declarators().size()==1); + auto ti_opt1 = var_dec7->declarators()[0].var(); + CHECK(std::holds_alternative(ti_opt1)); + auto ti1 = std::get_if(&ti_opt1); + auto pref1 = ti1->table().options(); + CHECK(std::holds_alternative(pref1)); + auto pref_dec1 = std::get_if(&pref1); + auto pref_opt1 = pref_dec1->var(); + CHECK(std::holds_alternative(pref_opt1)); + auto id7 = std::get_if(&pref_opt1); + CHECK(id7->str()=="table1"); + auto index1_opt = ti1->index().options(); + CHECK(std::holds_alternative(index1_opt)); + auto index1 = std::get_if(&index1_opt); + CHECK(index1->str()=="table2"); +} +TEST_CASE("table_statements", "[tree-sitter]") { + ts::Parser parser; + std::string source = "{\n" + "field1 = \"name\";\n" + "[2+2] = {1,2,3};\n" + "function()\n" + " return field1\n" + "end" + "}\n"; + ts::Tree tree = parser.parse_string(source); + ts::Node root = tree.root_node(); + auto prog = Program(root); + Body body = prog.body(); + auto stats = body.statements(); + CHECK(stats.size() == 1); + auto opt1 = stats[0].options(); + CHECK(std::holds_alternative(opt1)); + auto* exp1 = std::get_if(&opt1); + auto exp_opt1 = exp1->options(); + CHECK(std::holds_alternative(exp_opt1)); + auto table1 = std::get_if
(&exp_opt1); + auto fields = table1->fields(); + CHECK(fields.size() == 3); + auto field1_opt = fields[0].content(); + CHECK(std::holds_alternative(field1_opt)); + auto field1 = std::get_if(&field1_opt); + CHECK(field1->first.str() == "field1"s); + CHECK(std::holds_alternative(field1->second.options())); + auto content1_opt = field1->second.options(); + auto content1 = std::get_if(&content1_opt); + CHECK(content1->is_string()); + auto raw = content1->raw(); + auto content1_str = get_if(&raw); + CHECK(content1_str->value == "name"s); + auto field2_opt = fields[1].content(); + CHECK(std::holds_alternative(field2_opt)); + auto field2 = std::get_if(&field2_opt); + CHECK(std::holds_alternative(field2->first.options())); + CHECK(std::holds_alternative
(field2->second.options())); + auto field3_opt = fields[2].content(); + CHECK(std::holds_alternative(field3_opt)); + auto field3 = std::get_if(&field3_opt); + auto content3_opt = field3->options(); + CHECK(std::holds_alternative(content3_opt)); +} +TEST_CASE("function_calls", "[tree-sitter]") { + ts::Parser parser; + std::string source = "foo(a,b)\n" + "table.foo()\n" + "table:foo()\n" + "foo \"abc\"\n" + "table:foo {1,2,3,4}"; + ts::Tree tree = parser.parse_string(source); + ts::Node root = tree.root_node(); + auto prog = Program(root); + Body body = prog.body(); + auto stats = body.statements(); + CHECK(stats.size() == 5); + std::vector func_calls; + std::transform(stats.begin(), stats.end(), std::back_inserter(func_calls), [](Statement stat) { + auto opt = stat.options(); + CHECK(std::holds_alternative(opt)); + return *std::get_if(&opt); + }); + CHECK(!func_calls[0].method().has_value()); + CHECK(std::holds_alternative(func_calls[0].id().options())); + auto opt1 = func_calls[0].id().options(); + auto name1 = std::get_if(&opt1); + CHECK(holds_alternative(name1->var())); + CHECK(func_calls[0].args().size() == 2); + + CHECK(!func_calls[1].method().has_value()); + CHECK(std::holds_alternative(func_calls[1].id().options())); + auto opt2 = func_calls[1].id().options(); + auto name2 = std::get_if(&opt2); + CHECK(holds_alternative(name2->var())); + CHECK(func_calls[1].args().empty()); + + CHECK(func_calls[2].method().has_value()); + CHECK(std::holds_alternative(func_calls[2].id().options())); + auto opt3 = func_calls[2].id().options(); + auto name3 = std::get_if(&opt3); + CHECK(holds_alternative(name3->var())); + CHECK(func_calls[2].args().empty()); + + CHECK(func_calls[3].args().size() == 1); + CHECK(std::holds_alternative(func_calls[3].args()[0].options())); + auto opt4 = func_calls[3].args()[0].options(); + auto str = std::get_if(&opt4); + CHECK(str->is_string()); + + CHECK(func_calls[4].args().size() == 1); + CHECK(std::holds_alternative
(func_calls[4].args()[0].options())); + auto opt5 = func_calls[4].args()[0].options(); + auto table = std::get_if
(&opt5); + CHECK(table->fields().size() == 4); + for (uint i = 0; i < 4; i++) { + CHECK(std::holds_alternative(table->fields()[i].content())); + } +} +} // namespace minilua::details