{"id":17017527,"url":"https://github.com/kpiorno/mean","last_synced_at":"2026-04-28T00:31:36.910Z","repository":{"id":90091973,"uuid":"110502827","full_name":"kpiorno/mean","owner":"kpiorno","description":"A LL-N Grammar Parser for C++11. Fun to travel. Python-like bytecode generator and controller. Zero dependencies.","archived":false,"fork":false,"pushed_at":"2018-11-15T00:50:11.000Z","size":123,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-06-23T16:40:04.345Z","etag":null,"topics":["bytecode","cpp11","grammar","grammar-parser","parser","python","python3"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kpiorno.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-11-13T05:10:01.000Z","updated_at":"2018-11-15T00:50:13.000Z","dependencies_parsed_at":null,"dependency_job_id":"13422f12-38dd-4cba-9e42-bce97db4ee8e","html_url":"https://github.com/kpiorno/mean","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/kpiorno/mean","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kpiorno%2Fmean","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kpiorno%2Fmean/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kpiorno%2Fmean/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kpiorno%2Fmean/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kpiorno","download_url":"https://codeload.github.com/kpiorno/mean/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kpiorno%2Fmean/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32361477,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-27T20:07:02.737Z","status":"ssl_error","status_checked_at":"2026-04-27T20:07:00.910Z","response_time":128,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bytecode","cpp11","grammar","grammar-parser","parser","python","python3"],"created_at":"2024-10-14T06:36:52.946Z","updated_at":"2026-04-28T00:31:36.894Z","avatar_url":"https://github.com/kpiorno.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Mean\nA LL-N Grammar Parser for C++11. Fun to travel. Python-like bytecode generator and controller. No dependencies. \n\n### Motivation\n\"Mean\" is the result of a personal journey to parse and make custom changes to the Python 3 grammar. \nThe library uses an EBNF-like syntax, automaticaly generates the node tree and has a funny way to travel it. Support a\nPython-like bytecode generator and a mechanism for traveling it for Virtual Machines, Generators, etc...  More details at \"examples\" folder. \n\n### Quickstart\nPlease check the full example ```simple.cpp ``` at \"examples\" folder.\n\nCreate a grammar file: ```expr_grammar.mng ```\n```\nexpr: term ( ('+' | '-') term )*\nterm: factor ( ('*' | '/') factor )*\nfactor: base [ '^' term ]\nbase: '(' expr ')' | ['-'] NUMBER\n```\nCreate a source code file: ```expr_grammar.mn ```\n\n```2 ^ 6 -- 1```\n\nSo we need a ```MN_Lexer``` which will read the source code file. We could use built-in lexer \"MN_Simple\" which generates tokens: ```NAME, NUMBER, HEX, +, -, ^```:\n\n```cpp\n#include \u003cmn_root.h\u003e\n#include \u003clexers/mn_simple.h\u003e\n\n...\n\nint main(int /*argc*/, char **/*argv[]*/)\n{\n    //Class with shortcuts methods\n    auto root = new mn::MNRoot();\n    //Class to store the encountered errors\n    mn::MNErrorMsg* err = new mn::MNErrorMsg();\n    \n    //Built-in Lexer\n    auto parse_lexer = new mn::lexer::MN_Simple(\"path/to/expr_grammar.mn\", err);  \n    //Output the tree node\n    root-\u003etest_parser(\"expr_grammar.mng\", \"expr\", parse_lexer)   \n}    \n```\nThe above code will output the next tree:\n\n```\n|expr -- 100\n  |factor -- 100\n    |base -- 100\n      |(?) -- 17\n      |2 -- 26\n    |^ -- 2\n    |base -- 100\n      |(?) -- 17\n      |6 -- 26\n  |[.] -- 30\n    |- -- 2\n    |base -- 100\n      |- -- 2\n      |1 -- 26\n```\n\nThe nodes with the number ```100``` are non-terminals.  \n\nThe nodes ```[.] - 30``` are lists.\n\nThe nodes ```[?] - 17``` are optionals nodes. \n\nThe rest of the nodes are terminals.\n\nSo lets do the right stuff to travel it.\nIt's possible to use a custom way to do that, but luckily we have the template class ```MN_Traveler``` which help us.\n\nNow lets declare a class which inherit from ```MN_Traveler```:\n\n```cpp\n#include \u003cmn_root.h\u003e\n#include \u003clexers/mn_simple.h\u003e\n\n//Class to travel the tree\nclass Expr: public mn::MNTraveler\u003cExpr, float\u003e\n{\npublic:\n    Expr(){\n        //Bind the \"expr\" method with the \"expr\" grammar's non-terminal\n        bind(\"expr\", \u0026Expr::expr, this);    \n    }\n    float expr(mn::MNNode* node)\n    {\n        return 1.1; \n    }\n    //Entry point to start the travel   \n    float walk(mn::MNNode* node)\n    {\n        return call(node);\n    }    \n};    \n\nint main(int /*argc*/, char **/*argv[]*/) \n{\n    //Class with shortcuts methods\n    auto root = new mn::MNRoot();\n    //Class to store the encountered errors\n    mn::MNErrorMsg* err = new mn::MNErrorMsg();\n    auto parse_lexer = new mn::lexer::MN_Simple(\"expr_grammar.mn\", err);\n\n    auto traveler = new Expr();\n    //The run_parser method generate the tree and use the traveler class to travel it.\n    std::cout \u003c\u003c \"Result is: \" \u003c\u003c root-\u003erun_parser(\"expr_grammar.mng\", \"expr\",\n                                                   parse_lexer, traveler) \u003c\u003c std::endl;\n    delete traveler;\n    delete err;\n    delete root;\n}\n```\n\nThe second parameter of the template class define \"float\" as the type of all methods bounds to the grammar's non-terminals. Thus we can define specific custom types. \n\n```bind(\"expr\", \u0026Expr::expr, this);``` bind the \"expr\" method with the \"expr\" grammar's non-terminal.\n\nThe ```float expr(mn::MNNode* node) ...``` method will handle the output generated by the non-terminal \"expr\".\n\nThe ```float walk(mn::MNNode* node) ...``` is the first method which is invoked. The method ```call(node)``` in ```return call(node)``` will invoke the \nright method bound to the non-terminal (\"expr\" non-terminal according to the tree generated by the previous example). Use \"call\" method when you need to travel a non-terminal.\n\nSo, executing the last code we got the next output:\n\n```Result is: 1.1```\n\nWhich is the value that returns \"expr\" method.\n\nSo, adding the proper funcionalities we got a simple expression evaluator with the next code: \n\n```cpp\n#include \u003ciostream\u003e\n#include \u003cmath.h\u003e\n#include \u003cmn_root.h\u003e\n#include \u003clexers/mn_simple.h\u003e\n\n\nclass Expr: public mn::MNTraveler\u003cExpr, float\u003e\n{\npublic:\n    Expr(){\n        bind(\"expr\", \u0026Expr::expr, this);\n        bind(\"term\", \u0026Expr::term, this);\n        bind(\"factor\", \u0026Expr::factor, this);\n        bind(\"base\", \u0026Expr::base, this);\n    }\n    float expr(mn::MNNode* node)\n    {\n        //expr: term ( ('+' | '-') term )*\n        return arith_op(node);\n    }\n\n    float term(mn::MNNode* node)\n    {\n        //term: factor ( ('*' | '/') factor )*    \n        return arith_op(node);\n    }\n\n    float factor(mn::MNNode* node)\n    {\n        //factor: base [ '^' term ]\n        auto base = node-\u003eat(0);\n        auto exp = node-\u003eat(2);\n        float base_value = call(\u0026base);\n        float exp_value = call(\u0026exp);\n        return pow(base_value, exp_value);\n    }\n\n    float base(mn::MNNode* node)\n    {\n        //base: '(' expr ')' |  ['-'] NUMBER        \n        auto n = node-\u003eat(0);\n        if (n.get_meta() == 2 \u0026\u0026 n.get_lexeme() == \"(\")\n        {\n            auto expr = node-\u003eat(1);\n            return call(\u0026expr);\n        }\n        else\n        {\n            auto number = node-\u003eat(1);\n            float res = atof(number.get_lexeme().c_str());\n            if (n.is_found())\n                res *= -1;\n            return res;\n        }\n    }\n\n    float arith_op(mn::MNNode* node)\n    {\n        auto term = node-\u003eat(0);\n        float res = call(\u0026term);\n        auto term_list = node-\u003eat(1);\n        for (unsigned int i=0; i \u003c term_list.get_count(); ++i)\n        {\n            if ((i+1) % 2 == 0)\n            {\n                auto sign_node = term_list.at(i-1);\n                char op = sign_node.get_lexeme()[0];\n                auto res_node = term_list.at(i);\n                switch (op) {\n                case '+':\n                    res += call(\u0026res_node);\n                    break;\n                case '-':\n                    res -= call(\u0026res_node);\n                    break;\n                case '*':\n                    res *= call(\u0026res_node);\n                    break;\n                case '/':\n                    res /= call(\u0026res_node);\n                    break;\n                default:\n                    res -= call(\u0026res_node);\n                    break;\n                }\n            }\n        }\n        return res;\n    }\n\n    float walk(mn::MNNode* node)\n    {\n        return call(node);\n    }\n};\nint main(int /*argc*/, char **/*argv[]*/)\n{\n    //Class with shortcuts methods\n    auto root = new mn::MNRoot();\n    //Class to store the encountered errors\n    mn::MNErrorMsg* err = new mn::MNErrorMsg();\n    auto parse_lexer = new mn::lexer::MN_Simple(\"expr_grammar.mn\", err);\n\n    auto traveler = new Expr();\n    //The run_parser method generate the tree and use the traveler class to travel it.\n    std::cout \u003c\u003c \"Result is: \" \u003c\u003c root-\u003erun_parser(\"expr_grammar.mng\", \"expr\",\n                                                   parse_lexer, traveler) \u003c\u003c std::endl;\n    delete traveler;\n    delete err;\n    delete root;\n}\n};\n```\n### Designs\nIn order to reduce the amount of nodes from generated trees, for the rules like this ```A: B [C]```:\n\n```If [C] not encountered then A is replaced by B```. According to the last example if you change the context of file ```expr_grammar.mn ``` to 1 and execute the example, you will got\nthe next output:\n\n```\n|base -- 100\n  |(?) -- 17\n  |1 -- 26\n```\n\nAnd the tree will be right traveled because the method \"walk\" invoke the bound method \"base\" via ```return call(node);```.\n \n### Extending Lexers\nExtending lexers adding new tokens is straighfordward, e.g:\n\n```cpp\nenum MN_PY_TOKENS\n{\n    //Please use tokens index from MN_CUSTOM_TOKEN_INDEX, values below are reserved by the library.\n    HEX = MN_CUSTOM_TOKEN_INDEX,\n    STAR\n};\n\nnamespace mn\n{\n    namespace lexer\n    {\n        MN_Simple::MN_Simple(const std::string\u0026 file_name, MNErrorMsg* error_msg)\n            :MNLexer(file_name, error_msg)\n        {\n            //Register the token HEX. Could be referenced at grammar definition with the alias \"HEX\"\n            register_custom_token(\"HEX\", HEX);\n            //Register the token STAR. Could be referenced at grammar definition with the alias \"STAR\"\n            register_custom_token(\"STAR\", STAR);\n        }\n\n        MNToken* MN_Simple::next_token()\n        {\n            ...\n            //Somewhere at your \"next_token\" func\n            if (current_char == '★')\n                //return the STAR token \n                return ret_token(new MNToken(STAR, \"★\", row, col));\n            ...    \n        }    \n    }\n}    \n```\n\nSo, you could use the new tokens at your grammar like this:\n\n```\nSKY: (STAR)*\n```\n\n### Limitations \nDespite that it's possible to declare terminals tokens like identifiers or numbers via the EBNF, the \"mean\" library is designed to generate tokens using ```MN_Lexer``` classes. \nSo regular expression to declare tokens is not supported at now. The goal is to use another tokens sources beyond those generated via regular expressions.\n\nThe ```* +``` operators could be used only inmediate next to ```)```. So expression like ```NAME*``` is not supported. Use ```(NAME)*``` instead.\n\nif you have a rule like: ```A: B | D | [C]``` please consider change it to the form: ```A: [B | D | [C]]``` to avoid unexpected behaviours.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkpiorno%2Fmean","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkpiorno%2Fmean","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkpiorno%2Fmean/lists"}