diff --git a/CMakeLists.txt b/CMakeLists.txt index d0a871c..313a684 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,8 @@ set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "CMake Targets") project(aim_tools) +find_package(BISON 3.0 REQUIRED) +find_package(FLEX 2.5.35 REQUIRED) find_package(Git) if (MSVC) @@ -28,7 +30,7 @@ add_definitions(-DPOLYGON4_TOOLS=1) add_definitions(-DPOLYGON4_STATIC=1) if (NOT DATA_MANAGER_DIR) - message(STATUS "DATA_MANAGER_DIR variable is not set! Some targets won't be built") + message(FATAL_ERROR "DATA_MANAGER_DIR variable is not set!") else() set(SOLUTION_FOLDER Extern) add_subdirectory(${DATA_MANAGER_DIR} DataManager) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d80ab93..0963171 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -9,12 +9,6 @@ add_executable(db_extractor ${db_extractor_src}) target_link_libraries(db_extractor common) endif() -file(GLOB script2txt_src "script2txt/*") -add_executable(script2txt ${script2txt_src}) -target_link_libraries(script2txt common) - - -if (DATA_MANAGER_DIR) file(GLOB mmm_extractor_src "mmm_extractor/*") add_executable(mmm_extractor ${mmm_extractor_src}) target_link_libraries(mmm_extractor DataManager common) @@ -22,8 +16,6 @@ target_link_libraries(mmm_extractor DataManager common) file(GLOB mmo_extractor_src "mmo_extractor/*") add_executable(mmo_extractor ${mmo_extractor_src}) target_link_libraries(mmo_extractor DataManager common) -endif() - file(GLOB mmp_extractor_src "mmp_extractor/*") add_executable(mmp_extractor ${mmp_extractor_src}) @@ -44,4 +36,5 @@ target_link_libraries(tm_converter common) file(GLOB name_generator_src "name_generator/*") add_executable(name_generator ${name_generator_src}) -add_subdirectory(common) \ No newline at end of file +add_subdirectory(common) +add_subdirectory(script2txt) diff --git a/src/script2txt/CMakeLists.txt b/src/script2txt/CMakeLists.txt new file mode 100644 index 0000000..adca6f8 --- /dev/null +++ b/src/script2txt/CMakeLists.txt @@ -0,0 +1,48 @@ +# +# Polygon-4 Data generator +# + +set(BSRC ${CMAKE_CURRENT_BINARY_DIR}) + +set(_CPP ".*\\.cpp") +set(CPP "${_CPP}$") + +set(_H "(.*\\.h|.*\\.hpp)") +set(H "${_H}$") + +set(H_CPP "(${H}|${CPP})") + +source_group("Generated" "${BSRC}/${H_CPP}") + +BISON_TARGET(grammar + "${CMAKE_CURRENT_SOURCE_DIR}/grammar.yy" + "${CMAKE_CURRENT_BINARY_DIR}/grammar.cpp" +) +FLEX_TARGET(lexer + "${CMAKE_CURRENT_SOURCE_DIR}/lexer.ll" + "${CMAKE_CURRENT_BINARY_DIR}/lexer.cpp" + COMPILE_FLAGS --header-file="${CMAKE_CURRENT_BINARY_DIR}/lexer.h" +) +ADD_FLEX_BISON_DEPENDENCY(lexer grammar) + +include_directories(${CMAKE_CURRENT_BINARY_DIR}) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + +set(parser_src + ${BISON_grammar_OUTPUTS} + ${FLEX_lexer_OUTPUTS} + ${CMAKE_CURRENT_BINARY_DIR}/lexer.h +) +set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/lexer.h PROPERTIES GENERATED TRUE) + +file(GLOB script2txt_src "*.cpp" "*.h") + +set(script2txt_src ${script2txt_src} + grammar.yy + lexer.ll + ${parser_src} + ${SCHEMA_FILE} +) + +add_executable (script2txt ${script2txt_src} ${script2txt_inc}) +target_link_libraries (script2txt common schema) diff --git a/src/script2txt/ParserDriver.cpp b/src/script2txt/ParserDriver.cpp new file mode 100644 index 0000000..6436447 --- /dev/null +++ b/src/script2txt/ParserDriver.cpp @@ -0,0 +1,62 @@ +/* + * Polygon-4 script2txt + * Copyright (C) 2015-2016 lzwdgc + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include "ParserDriver.h" + +#include +#include + +// Prevent using because of bug in flex. +#define YY_NO_UNISTD_H 1 +#define YY_DECL 1 +#include +extern yy::parser::symbol_type yylex(yyscan_t yyscanner, yy::location &loc); + +ParserDriver::ParserDriver() +{ + debug = false; +} + +yy::parser::symbol_type ParserDriver::lex() +{ + return yylex(scanner, location); +} + +int ParserDriver::parse(const std::string &s) +{ + yylex_init(&scanner); + yy_scan_string(s.c_str(), scanner); + + yy::parser parser(*this); + parser.set_debug_level(debug); + int res = parser.parse(); + + yylex_destroy(scanner); + + return res; +} + +void ParserDriver::error(const yy::location &l, const std::string &m) +{ + std::cerr << l << " " << m << std::endl; +} + +void ParserDriver::error(const std::string& m) +{ + std::cerr << m << std::endl; +} diff --git a/src/script2txt/ParserDriver.h b/src/script2txt/ParserDriver.h new file mode 100644 index 0000000..48b7b1f --- /dev/null +++ b/src/script2txt/ParserDriver.h @@ -0,0 +1,54 @@ +/* + * Polygon-4 script2txt + * Copyright (C) 2015-2016 lzwdgc + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include +#include + +#include + +class ParserDriver +{ +public: + ParserDriver(); + + yy::parser::symbol_type lex(); + int parse(const std::string &s); + + void error(const yy::location &l, const std::string &m); + void error(const std::string &m); + + void setContext(Context &&ctx) { context = std::move(ctx); } + const Context &getContext() const { return context; } + + // lex & parse +private: + void *scanner; + yy::location location; + bool debug; + + // data +private: + Context context; + + // other +public: + std::set functions; +}; diff --git a/src/script2txt/grammar.yy b/src/script2txt/grammar.yy new file mode 100644 index 0000000..1dafa2f --- /dev/null +++ b/src/script2txt/grammar.yy @@ -0,0 +1,316 @@ +%{ +#include +#include +#include + +#include "ParserDriver.h" + +#define yylex(p) p.lex() +%} + +//////////////////////////////////////// + +// general settings +%require "3.0" +%debug +%start file +%locations +%verbose +%no-lines +%error-verbose + +//////////////////////////////////////// + +// c++ skeleton and options +%skeleton "lalr1.cc" + +%define api.value.type variant +%define api.token.constructor // C++ style of handling variants +%define parse.assert // check C++ variant types + +%code requires // forward decl of C++ driver (our parser) in HPP +{ +#include + +class ParserDriver; +} + +// param to yy::parser() constructor +// the parsing context +%param { ParserDriver &driver } + +//////////////////////////////////////// + +// tokens and types +%token EOQ 0 "end of file" +%token ERROR_SYMBOL +%token L_BRACKET R_BRACKET COMMA QUOTE SEMICOLON COLON POINT + L_CURLY_BRACKET R_CURLY_BRACKET SHARP R_ARROW EQUAL + L_SQUARE_BRACKET R_SQUARE_BRACKET ASTERISK +%token IF ELSE NOT AND OR +%token END PROC _PROC + +%token STRING +%token INTEGER + +%type string integer number + object_variable object + variables variable + function_name procedure_begin + parameters parameter + conds cond condition_body + function_call + +%type condition condition_begin + statements statement + proc_statements proc_statement + procedure + global_statements global_statement + script + +//////////////////////////////////////// + +%% + +file: script EOQ + { driver.setContext(std::move($1)); } + ; + +script: global_statements + { $$ = std::move($1); } + ; + +global_statements: global_statement + { $$ = std::move($1); } + | global_statements global_statement + { + auto &ctx = $1; + ctx.addLine(); + ctx.addWithRelativeIndent($2); + $$ = std::move(ctx); + } + ; + +global_statement: function_call + { + Context ctx; + ctx.addLine($1); + $$ = std::move(ctx); + } + | condition + { $$ = std::move($1); } + | procedure + { $$ = std::move($1); } + | R_CURLY_BRACKET + { $$ = Context(); } + | END + { $$ = Context(); } + | ERROR_SYMBOL + { $$ = Context(); } + | POINT + { $$ = Context(); } + | STRING + { $$ = Context(); } + | R_BRACKET + { $$ = Context(); } + ; + +procedure: procedure_begin proc_statements END + { + Context ctx; + ctx.beginBlock($1); + ctx.addWithRelativeIndent($2); + ctx.endBlock(); + $$ = std::move(ctx); + } + | procedure_begin END + { + Context ctx; + ctx.beginBlock($1); + ctx.endBlock(); + $$ = std::move(ctx); + } + | procedure_begin L_CURLY_BRACKET statements R_CURLY_BRACKET + { + Context ctx; + ctx.beginBlock($1); + ctx.addWithRelativeIndent($3); + ctx.endBlock(); + $$ = std::move(ctx); + } + ; +procedure_begin: PROC function_name L_BRACKET R_BRACKET + { $$ = "PROC " + $2 + "()"; } + | PROC function_name + { $$ = "PROC " + $2 + "()"; } + ; + +proc_statements: proc_statement + { $$ = std::move($1); } + | proc_statements proc_statement + { + auto &ctx = $1; + ctx.addWithRelativeIndent($2); + $$ = std::move(ctx); + } + ; +proc_statement: function_call + { + Context ctx; + ctx.addLine($1); + $$ = std::move(ctx); + } + | _PROC function_call + { + Context ctx; + ctx.addLine("_PROC " + $2); + $$ = std::move(ctx); + } + | condition + { $$ = std::move($1); } + | COLON + { $$ = Context(); } + | R_BRACKET + { $$ = Context(); } + | ERROR_SYMBOL + { $$ = Context(); } + ; + +statements: statement + { $$ = std::move($1); } + | statements statement + { + auto &ctx = $1; + ctx.addWithRelativeIndent($2); + $$ = std::move(ctx); + } + ; +statement: proc_statement + { $$ = std::move($1); } + | END + { + Context ctx; + ctx.addLine("END"); + $$ = std::move(ctx); + } + ; + +function_call: function_name L_BRACKET parameters R_BRACKET + { $$ = $1 + "(" + $3 + ")"; driver.functions.insert($1); } + | function_name L_BRACKET parameters COMMA R_BRACKET + { $$ = $1 + "(" + $3 + ")"; driver.functions.insert($1); } + | function_name L_BRACKET R_BRACKET + { $$ = $1 + "()"; driver.functions.insert($1); } + ; +parameters: parameter + { $$ = $1; } + | parameters COMMA parameter + { $$ = $1 + ", " + $3; } + ; +parameter: object + { $$ = $1; } + | number + { $$ = $1; } + | object_variable + { $$ = $1; } + | ASTERISK + { $$ = "*"; } + ; + +condition: condition_begin + { $$ = std::move($1); } + | condition_begin ELSE L_CURLY_BRACKET statements R_CURLY_BRACKET + { + auto &ctx = $1; + ctx.beginBlock("else"); + ctx.addWithRelativeIndent($4); + ctx.endBlock(); + $$ = std::move(ctx); + } + ; +condition_begin: IF L_BRACKET condition_body R_BRACKET L_CURLY_BRACKET statements R_CURLY_BRACKET + { + Context ctx; + ctx.beginBlock("if (" + $3 + ")"); + ctx.addWithRelativeIndent($6); + ctx.endBlock(); + $$ = std::move(ctx); + } + | IF L_BRACKET condition_body L_CURLY_BRACKET statements R_CURLY_BRACKET + { + Context ctx; + ctx.beginBlock("if (" + $3 + ")"); + ctx.addWithRelativeIndent($5); + ctx.endBlock(); + $$ = std::move(ctx); + } + | IF L_BRACKET condition_body R_BRACKET L_CURLY_BRACKET R_CURLY_BRACKET + { + Context ctx; + ctx.beginBlock("if (" + $3 + ")"); + ctx.endBlock(); + $$ = std::move(ctx); + } + ; +condition_body: conds + { $$ = $1; } + ; +conds: cond + { $$ = $1; } + | conds AND cond + { $$ = $1 + " && " + $3; } + | conds OR cond + { $$ = $1 + " || " + $3; } + ; +cond: object + { $$ = $1; } + | object_variable + { $$ = $1; } + | function_call + { $$ = $1; } + | NOT cond + { $$ = "!" + $2; } + ; + +object_variable: object POINT variables + { $$ = $1 + "." + $3; } + ; +variables: /* empty */ + { $$ = ""; } + | variable + { $$ = $1; } + | variables POINT variable + { $$ = $1 + "." + $3; } + ; + +function_name: string + { $$ = $1; } + ; +object: string + { $$ = $1; } + ; +variable: string + { $$ = $1; } + | integer + { $$ = $1; } + ; + +number: integer POINT integer + { $$ = $1 + "." + $3; } + | integer + { $$ = $1; } + ; + +string: STRING + { $$ = $1; } + ; +integer: INTEGER + { $$ = std::to_string($1); } + ; + +%% + +void yy::parser::error(const location_type& l, const std::string& m) +{ + driver.error(l, m); +} diff --git a/src/script2txt/lexer.ll b/src/script2txt/lexer.ll new file mode 100644 index 0000000..0fe6fc5 --- /dev/null +++ b/src/script2txt/lexer.ll @@ -0,0 +1,98 @@ +%{ +#pragma warning(disable: 4005) +#include + +#include "grammar.hpp" + +#define YY_USER_ACTION loc.columns(yyleng); + +#define PUSH_STATE(x) BEGIN(x) +#define POP_STATE() BEGIN(0) + +#define YY_DECL yy::parser::symbol_type yylex(yyscan_t yyscanner, yy::location &loc) + +#define MAKE(x) yy::parser::make_ ## x(loc) +#define MAKE_VALUE(x, v) yy::parser::make_ ## x((v), loc) +%} + +%option nounistd +%option yylineno +%option nounput +%option batch +%option never-interactive +%option reentrant +%option noyywrap + + +DIGIT [0-9] +DIGITS {DIGIT}{DIGIT}* +INTEGER {DIGITS}[Ff]? + +STRING [[:alpha:]_-][[:alnum:]_-]* + + +%x user_string + + +%% + +%{ + // Code run each time yylex is called. + loc.step(); +%} + +#.*/\n ; // ignore comments + +[ \t]+ loc.step(); +\r loc.step(); +\n { + loc.lines(yyleng); + loc.step(); + } + +";" return MAKE(SEMICOLON); +":" return MAKE(COLON); +"(" return MAKE(L_BRACKET); +")" return MAKE(R_BRACKET); +"{" return MAKE(L_CURLY_BRACKET); +"}" return MAKE(R_CURLY_BRACKET); +"[" return MAKE(L_SQUARE_BRACKET); +"]" return MAKE(R_SQUARE_BRACKET); +"," return MAKE(COMMA); +"\." return MAKE(POINT); +"->" return MAKE(R_ARROW); +"=" return MAKE(EQUAL); +"\*" return MAKE(ASTERISK); + +IF { return MAKE(IF); } +ELSE { return MAKE(ELSE); } +"!" { return MAKE(NOT); } +"&" { return MAKE(AND); } +"|" { return MAKE(OR); } +"||" { return MAKE(OR); } +END { return MAKE(END); } +PROC { return MAKE(PROC); } +_PROC { return MAKE(_PROC); } + +{INTEGER} { return MAKE_VALUE(INTEGER, std::stoi(yytext)); } +{STRING} { return MAKE_VALUE(STRING, yytext); } + +\" { PUSH_STATE(user_string); return MAKE(QUOTE); } +\" { POP_STATE(); return MAKE(QUOTE); } +(?:[^"\\]|\\.)*/\" { + int n = 0; + char *p = yytext; + while ((p = strstr(p, "\n"))++ != 0) + n++; + if (n) + { + loc.lines(n); + loc.step(); + } + return MAKE_VALUE(STRING, yytext); +} + +. { /*driver.error(loc, "invalid character");*/ return MAKE(ERROR_SYMBOL); } +<> return MAKE(EOQ); + +%% diff --git a/src/script2txt/script.h b/src/script2txt/script.h index db71ab6..b4110af 100644 --- a/src/script2txt/script.h +++ b/src/script2txt/script.h @@ -43,7 +43,8 @@ struct script READ(b, raw_text_size); READ(b, unk1); raw_text.resize(raw_text_size); - READ_N(b, raw_text[0], raw_text.size()); + if (raw_text_size) + READ_N(b, raw_text[0], raw_text.size()); READ(b, array_len); unk2.resize(array_len); READ_N(b, unk2[0], unk2.size()); @@ -56,7 +57,6 @@ struct script } fix_text(); - beautify(); } void fix_text() @@ -74,58 +74,6 @@ struct script } } - void beautify() - { - const std::string space = " "; - int brace_count = 0; - int proc_started = 0; - bool proc_started_now = false; - for (auto &line : lines) - { - if (proc_started_now && line.find("{") != line.npos) - { - proc_started--; - } - if (brace_count > 0 || proc_started > 0) - { - auto space_count = brace_count + proc_started; - if (line == "}" || - (brace_count == 0 && proc_started == 1 && - line.find("END") != line.npos)) - space_count--; - std::string s; - for (int i = 0; i < space_count; i++) - s += space; - line = s + line; - } - if (line.find("PROC") != line.npos && - line.find("()") != line.npos) - { - proc_started++; - proc_started_now = true; - continue; - } - if (proc_started_now) - proc_started_now = false; - if (line.find("END") != line.npos && proc_started == 1) - { - proc_started--; - } - for (auto &c : line) - { - if (c == '{') - brace_count++; - else if (c == '}') - brace_count--; - if (brace_count < 0) - { - c = '\n'; - brace_count++; - } - } - } - } - std::string get_text() const { std::string s; @@ -134,16 +82,30 @@ struct script if (line != "\n") s += line + "\n"; } - replace_all(s, "IF(", "IF ("); - replace_all(s, "\nIF", "\n\nIF"); + replace_all(s, "PROC", "PROC "); - replace_all(s, "END\nPROC", "END\n\nPROC"); - replace_all(s, "|", " || "); - replace_all(s, "&", " && "); - replace_all(s, "(", "( "); - replace_all(s, ")", " )"); - replace_all(s, ",", ", "); - replace_all(s, "!", "! "); + replace_all(s, "ENFD", "END"); + replace_all(s, "\nEN\n", "\n"); + replace_all(s, "?", " "); + s += "\nEND\n"; + + // remove wrong braces + int braces = 0; + for (auto &c : s) + { + switch (c) + { + case '{': + braces++; + break; + case '}': + if (braces == 0) + c = ' '; + else + braces--; + break; + } + } return s; } }; diff --git a/src/script2txt/script2txt.cpp b/src/script2txt/script2txt.cpp index 3b025e3..73faff9 100644 --- a/src/script2txt/script2txt.cpp +++ b/src/script2txt/script2txt.cpp @@ -20,6 +20,7 @@ #include #include +#include "ParserDriver.h" #include "script.h" using std::cout; @@ -42,11 +43,35 @@ try s.load(b); auto str = s.get_text(); - // write - filename += ".txt"; - std::ofstream ofile(filename); - if (ofile) - ofile << str; + ParserDriver driver; + if (driver.parse(str)) + { + throw std::runtime_error("error during parsing input file"); + } + auto &ctx = driver.getContext(); + + // write script + { + filename += ".txt"; + std::ofstream ofile(filename); + if (ofile) + ofile << ctx.getText(); + } + + // write function calls + { + std::ofstream functions("functions.txt", std::ios::app); + if (functions) + { + for (auto &f : driver.functions) + { + std::string f2(f.size(), 0); + std::transform(f.begin(), f.end(), f2.begin(), tolower); + functions << f2 << "\n"; + } + } + } + return 0; } catch (std::exception &e)