diff --git a/csbuild/ecs.json b/csbuild/ecs.json index 020ac97..3e88aad 100644 --- a/csbuild/ecs.json +++ b/csbuild/ecs.json @@ -3,7 +3,7 @@ "Name": "ecs", "Info": "Extended CovScript(ECS Lang) Header", "Author": "Michael Lee", - "Version": "1.4.1", + "Version": "1.4.2", "Target": "imports/ecs.csp", "Dependencies": [ "sdk_extension" diff --git a/csbuild/ecs_bootstrap.json b/csbuild/ecs_bootstrap.json index ef55207..25730d1 100644 --- a/csbuild/ecs_bootstrap.json +++ b/csbuild/ecs_bootstrap.json @@ -3,12 +3,14 @@ "Name": "ecs_bootstrap", "Info": "Extended CovScript(ECS Lang) Bootstrap", "Author": "Michael Lee", - "Version": "1.7.0", + "Version": "1.7.1", "Target": "imports/ecs_bootstrap.csp", "Dependencies": [ "parsergen", "ecs_parser", "ecs_generator", + "ecs_lint", + "ecs_format", "sdk_extension", "codec", "regex" diff --git a/csbuild/ecs_format.json b/csbuild/ecs_format.json new file mode 100644 index 0000000..aa799fa --- /dev/null +++ b/csbuild/ecs_format.json @@ -0,0 +1,14 @@ +{ + "Type": "Package", + "Name": "ecs_format", + "Info": "Extended CovScript(ECS Lang) Formatter", + "Author": "Michael Lee", + "Version": "1.0.0", + "Target": "imports/ecs_format.csp", + "Dependencies": [ + "parsergen", + "regex", + "ecs_parser", + "ecs_lint" + ] +} diff --git a/csbuild/ecs_generator.json b/csbuild/ecs_generator.json index c85c443..14be71d 100644 --- a/csbuild/ecs_generator.json +++ b/csbuild/ecs_generator.json @@ -3,7 +3,7 @@ "Name": "ecs_generator", "Info": "Extended CovScript(ECS Lang) Generator", "Author": "Michael Lee", - "Version": "4.1.0.2", + "Version": "4.1.0.3", "Target": "imports/ecs_generator.csp", "Dependencies": [ "parsergen", diff --git a/csbuild/ecs_lint.json b/csbuild/ecs_lint.json new file mode 100644 index 0000000..ea85b8d --- /dev/null +++ b/csbuild/ecs_lint.json @@ -0,0 +1,13 @@ +{ + "Type": "Package", + "Name": "ecs_lint", + "Info": "Extended CovScript(ECS Lang) Linter & Formatter", + "Author": "Michael Lee", + "Version": "1.0.0", + "Target": "imports/ecs_lint.csp", + "Dependencies": [ + "parsergen", + "regex", + "ecs_parser" + ] +} diff --git a/csbuild/ecs_parser.json b/csbuild/ecs_parser.json index 6679c7d..49069f3 100644 --- a/csbuild/ecs_parser.json +++ b/csbuild/ecs_parser.json @@ -3,7 +3,7 @@ "Name": "ecs_parser", "Info": "Extended CovScript(ECS Lang) Parser", "Author": "Michael Lee", - "Version": "1.3.6", + "Version": "1.4.0", "Target": "imports/ecs_parser.csp", "Dependencies": [ "parsergen", diff --git a/imports/ecs.csp b/imports/ecs.csp index e3765fc..dd2d8b4 100644 --- a/imports/ecs.csp +++ b/imports/ecs.csp @@ -1,4 +1,4 @@ -# Extended Covariant Script Header: v1.4.0 +# Extended Covariant Script Header: v1.4.2 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -27,6 +27,12 @@ package ecs # Type System # ============================================================ +struct lambda_base + # This is an empty base struct used to identify lambda objects + # in the CovScript type system. It serves as a marker for lambda + # functions and allows for type checking and validation of lambda + # objects within the ECS framework. +end namespace type_validator function __type(obj) @@ -43,7 +49,11 @@ namespace type_validator end function __function(obj) link real_type = sdk.typeids.get_real(obj) - return real_type == sdk.typeids.callable || real_type == sdk.typeids.memberfn + if real_type == sdk.typeids.callable || real_type == sdk.typeids.memberfn + return true + else + return global.is_a(typeid obj, typeid lambda_base) + end end function __exception(obj) return sdk.typeids.get_real(obj) == sdk.typeids.exception diff --git a/imports/ecs_bootstrap.csp b/imports/ecs_bootstrap.csp index d73cade..97c68ff 100644 --- a/imports/ecs_bootstrap.csp +++ b/imports/ecs_bootstrap.csp @@ -1,4 +1,4 @@ -# Bootstrap of Extended Covariant Script Generator v1.7.0 +# Bootstrap of Extended Covariant Script Generator v1.7.1 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,10 +20,10 @@ package ecs_bootstrap -import parsergen, ecs_parser, ecs_generator, codec, regex +import parsergen, ecs_parser, ecs_generator, ecs_lint, ecs_format, codec, regex import sdk_extension as sdk -var wrapper_ver = "1.7.0" +var wrapper_ver = "1.7.1" var exit_code = -1 # -1: continue, 0: success, >0: error function show_version_simple() @@ -155,6 +155,9 @@ function show_help() " -f Disable compile cache\n" + " -m Disable beautify\n" + " -c Check grammar only\n" + + " -l Lint check (requires file argument)\n" + + " -F Format source file in-place\n" + + " -n Dry-run format to stdout (implies -F)\n" + " -g Generate cSYM info\n" + " -d Run debugger\n" + " -o Set output path\n" + @@ -188,7 +191,11 @@ var silent = false var splash = null var output = null var csym = false +var explicit_csym = false var repl = false +var lint_only = false +var format_only = false +var format_dry_run = false function process_args(cmd_args) var index = 1 @@ -214,17 +221,27 @@ function process_args(cmd_args) case "-c" no_run = true end + case "-l" + lint_only = true + no_run = true + end + case "-F" + format_only = true + no_run = true + end + case "-n" + format_dry_run = true + end case "-s" silent = true end case "-r" repl = true - ++index - break end case "-g" no_hash = true csym = true + explicit_csym = true end case "-d" executor = "cs_dbg -s " @@ -254,7 +271,11 @@ function process_args(cmd_args) exit_code = 1 return end - csx_path = cmd_args[++index] + if csx_path == null + csx_path = cmd_args[++index] + else + csx_path = csx_path + system.path.delimiter + cmd_args[++index] + end end case "-o" if index == cmd_args.size - 1 @@ -358,7 +379,7 @@ function setup_unicode(charset) return null end var cvt = codecvt_map.at(cvt_name)(unicode_ext) - ecs_parser.grammar.lex = ecs_parser.get_lexical([](str)->unicode_ext.build_optimize_wregex(cvt.local2wide(str))) + ecs_parser.grammar.lex = ecs_parser.get_lexical([](str)->unicode_ext.build_optimize_wregex(cvt.local2wide(str)), false) if cvt_name == "GBK" var wrapper = new gbk_wrapper wrapper.codecvt = cvt @@ -383,13 +404,43 @@ function run_ecs(cmd_args) splash = null output = null csym = false + explicit_csym = false repl = false + lint_only = false + format_only = false + format_dry_run = false exit_code = -1 process_args(cmd_args) if exit_code != -1 return exit_code end + # -n implies format + dry-run; lint/format modes skip the compile cache + if format_dry_run + format_only = true + end + if lint_only && format_only + system.out.println("Warning: -l and -F are mutually exclusive; lint mode takes precedence.") + format_only = false + format_dry_run = false + end + if lint_only || format_only + no_run = true + no_hash = true + if output != null + system.out.println("Warning: -o is ignored in lint/format mode.") + end + if executor != "cs " + system.out.println("Warning: -d is ignored in lint/format mode.") + end + if explicit_csym + system.out.println("Warning: -g is ignored in lint/format mode.") + end + end + if repl && (lint_only || format_only) + system.out.println("Error: -l/-F is incompatible with REPL mode.") + return 1 + end if repl var instance = new repl_instance instance.silent = silent @@ -422,6 +473,9 @@ function run_ecs(cmd_args) if name != null && !name.empty() var mtime = ifs.getline().to_number() if mtime == system.file.mtime(file_name) + if csx_path != null + compiler_args += " -i " + csx_path + end return no_run ? 0 : system.run(process_path(executor + compiler_args + " " + name + arguments)) end end @@ -431,6 +485,19 @@ function run_ecs(cmd_args) end end end + # Lint-only mode (lint_file does its own parsing) + if lint_only + var result = ecs_lint.lint_file(file_name) + if result != null + result[0].print_report() + if result[0].count_by_severity(ecs_lint.SEVERITY_ERROR) > 0 + return 1 + end + else + return 1 + end + return 0 + end var parser = new parsergen.generator if unicode != null parser.unicode_cvt = setup_unicode(unicode) @@ -438,6 +505,25 @@ function run_ecs(cmd_args) return exit_code end end + # Format mode (uses format-specific grammar with com tokens) + if format_only + var cvt = null + if unicode != null + cvt = parser.unicode_cvt + end + var formatted = ecs_format.format_file(file_name, cvt) + if formatted == null + return 1 + end + if format_dry_run + system.out.print(formatted) + else + var ofs = iostream.ofstream(file_name) + ofs.print(formatted) + system.out.println(file_name + ": formatted.") + end + return 0 + end parser.add_grammar("ecs-lang", ecs_parser.grammar) parser.from_file(file_name) if parser.ast != null @@ -473,7 +559,9 @@ function run_ecs(cmd_args) foreach it in parser.code_buff do csym_ofs.println(it) end if !no_hash - iostream.ofstream("./.ecs_output/" + file_hash).println(target_name) + var ofs = iostream.ofstream("./.ecs_output/" + file_hash) + ofs.println(target_name) + ofs.println(system.file.mtime(file_name)) end if splash != null system.out.println(splash) @@ -504,7 +592,7 @@ function run_ecs(cmd_args) return no_run ? 0 : system.run(process_path(executor + compiler_args + " " + target_name + arguments)) end end - return 0 + return 1 end function main(cmd_args) diff --git a/imports/ecs_format.csp b/imports/ecs_format.csp new file mode 100644 index 0000000..b0dbf9b --- /dev/null +++ b/imports/ecs_format.csp @@ -0,0 +1,1424 @@ +# ECS Formatter v1.0.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Copyright (C) 2017-2026 Michael Lee(李登淳) +# +# Email: mikecovlee@163.com +# Github: https://github.com/mikecovlee +# Website: http://covscript.org.cn + +package ecs_format + +import parsergen, ecs_parser, regex + +function get_fmt_grammar() + var g = new parsergen.grammar + g.ext = ".*\\.(csp|csc|ecs|ecsx)" + g.lex = ecs_parser.get_lexical(regex.build_optimize, true) + g.stx := ecs_parser.get_syntax(true) + return g +end + +# ============================================================ +# Format Writer +# ============================================================ + +class format_writer + var output = "" + var indent_size = 4 + var indent_level = 0 + var need_space = false + var last_text = "" + var force_space = false + var max_width = 120 + var continuation = 0 + + var line_buf = new array + var break_points = new array + var line_width = 0 + + function is_ident(text) + if text.empty() + return false + end + var c = to_integer(text[0]) + return (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || c == 95 || (c >= 13312 && c <= 40959) || (c >= 63744 && c <= 64255) + end + + function write(text) + if need_space || force_space + var suppress = false + if !force_space + if text == "," || text == ";" || text == "." || text == "->" || text == "::" || text == ")" || text == "]" || text == "}" + suppress = true + end + if !suppress && (text == "(" || text == "[") + if last_text == ")" || last_text == "]" + suppress = true + else + if this.is_ident(last_text) + suppress = true + end + end + end + end + if !suppress + line_buf.push_back(" ") + line_width += 1 + end + need_space = false + force_space = false + end + line_buf.push_back(text) + line_width += text.size + last_text = text + if text == "(" || text == "[" || text == "{" || text == "." || text == "->" || text == "::" || text == "!" + need_space = false + else + need_space = true + end + end + + function mark_break() + if !line_buf.empty() + break_points.push_back(line_buf.size - 1) + end + end + + function newline() + this.flush_line() + output += '\n' + need_space = false + force_space = false + last_text = "" + end + + function flush_line() + if line_buf.empty() + return + end + var indent_str = "" + for i = 0, i < (indent_level + continuation) * indent_size, ++i + indent_str += ' ' + end + continuation = 0 + var cont_indent_str = "" + for i = 0, i < (indent_level + 1) * indent_size, ++i + cont_indent_str += ' ' + end + var available = max_width - indent_str.size + if available < 0 + available = 0 + end + + var buf = line_buf + var bps = break_points + line_buf = new array + break_points = new array + line_width = 0 + + output += indent_str + + var first = true + while !buf.empty() + var w = 0 + for i = 0, i < buf.size, ++i + w += buf[i].size + end + + if w <= available + for i = 0, i < buf.size, ++i + output += buf[i] + end + break + end + + var bp = -1 + for i = bps.size - 1, i >= 0, --i + var bw = 0 + for j = 0, j <= bps[i], ++j + bw += buf[j].size + end + if bw <= available + bp = bps[i] + break + end + end + + if bp < 0 + for i = 0, i < buf.size, ++i + output += buf[i] + end + break + end + + for j = 0, j <= bp, ++j + output += buf[j] + end + + var rest = new array + var start = bp + 1 + while start < buf.size && buf[start] == " " + ++start + end + for j = start, j < buf.size, ++j + rest.push_back(buf[j]) + end + + if rest.empty() + break + end + + output += '\n' + if first + available = max_width - cont_indent_str.size + if available < 0 + available = 0 + end + end + output += cont_indent_str + + first = false + buf = rest + var new_bps = new array + for i = 0, i < bps.size, ++i + if bps[i] > bp + new_bps.push_back(bps[i] - bp - 1) + end + end + bps = new_bps + end + end + + function space() + need_space = true + end + + function push_indent() + ++indent_level + end + + function pop_indent() + if indent_level > 0 + --indent_level + end + end + + function token(tok) + if tok.type == "endl" || tok.type == "com" + return + end + this.write(tok.data) + end + + function keyword(kw) + this.write(kw) + this.force_space = true + end +end + +# ============================================================ +# Format Visitor +# ============================================================ + +class format_visitor + var w = null + var skip_next_eos = false + + function run(file_name, code_buff, ast) + this.w = new format_writer + this.visit_begin(ast.nodes) + this.w.flush_line() + return this.w.output + end + + # === Generic walker for expression children === + + function visit_children(nodes) + for idx = 0, idx < nodes.size, ++idx + var node = nodes[idx] + if typeid node == typeid parsergen.token_type + if node.type == "com" + this.w.write(node.data) + this.w.newline() + else + if node.type != "endl" + this.w.token(node) + if node.data == "," || node.data == "+" || node.data == "-" || node.data == "*" || node.data == "/" || node.data == "%" || node.data == "^" || node.data == "=" || node.data == "+=" || node.data == "-=" || node.data == "*=" || node.data == "/=" || node.data == "%=" || node.data == "==" || node.data == "!=" || node.data == "<" || node.data == ">" || node.data == "<=" || node.data == ">=" || node.data == "&&" || node.data == "||" + this.w.mark_break() + end + end + end + end + if typeid node == typeid parsergen.syntax_tree + this.dispatch(node) + end + end + end + + function dispatch(node) + var root = node.root + block + var matched = false + if !matched && root == "begin" + matched = true + this.visit_begin(node.nodes) + end + if !matched && root == "stmts" + matched = true + this.visit_stmts(node.nodes) + end + if !matched && root == "decl-stmts" + matched = true + this.visit_decl_stmts(node.nodes) + end + if !matched && root == "statement" + matched = true + this.visit_statement(node.nodes) + end + if !matched && root == "declaration" + matched = true + this.visit_declaration(node.nodes) + end + if !matched && root == "if-stmt" + matched = true + this.visit_if_stmt(node.nodes) + end + if !matched && root == "while-stmt" + matched = true + this.visit_while_stmt(node.nodes) + end + if !matched && root == "loop-stmt" + matched = true + this.visit_loop_stmt(node.nodes) + end + if !matched && root == "for-stmt" + matched = true + this.visit_for_stmt(node.nodes) + end + if !matched && root == "foreach-stmt" + matched = true + this.visit_foreach_stmt(node.nodes) + end + if !matched && root == "switch-stmt" + matched = true + this.visit_switch_stmt(node.nodes) + end + if !matched && root == "try-stmt" + matched = true + this.visit_try_stmt(node.nodes) + end + if !matched && root == "block-stmt" + matched = true + this.visit_block_stmt(node.nodes) + end + if !matched && root == "function-stmt" + matched = true + this.visit_function_stmt(node.nodes) + end + if !matched && root == "async-function-stmt" + matched = true + this.visit_async_function_stmt(node.nodes) + end + if !matched && root == "class-stmt" + matched = true + this.visit_class_stmt(node.nodes) + end + if !matched && root == "namespace-stmt" + matched = true + this.visit_namespace_stmt(node.nodes) + end + if !matched && root == "var-stmt" + matched = true + this.visit_var_stmt(node.nodes) + end + if !matched && root == "return-stmt" + matched = true + this.visit_return_stmt(node.nodes) + end + if !matched && root == "throw-stmt" + matched = true + this.visit_throw_stmt(node.nodes) + end + if !matched && root == "yield-stmt" + matched = true + this.visit_yield_stmt(node.nodes) + end + if !matched && root == "control-stmt" + matched = true + this.visit_control_stmt(node.nodes) + end + if !matched && root == "expr-stmt" + matched = true + this.visit_expr_stmt(node.nodes) + end + if !matched && root == "import-stmt" + matched = true + this.visit_import_stmt(node.nodes) + end + if !matched && root == "package-stmt" + matched = true + this.visit_package_stmt(node.nodes) + end + if !matched && root == "using-stmt" + matched = true + this.visit_using_stmt(node.nodes) + end + if !matched && root == "prep-stmt" + matched = true + this.visit_prep_stmt(node.nodes) + end + if !matched && root == "function-body" + matched = true + this.visit_function_body(node.nodes) + end + if !matched && root == "for-body" + matched = true + this.visit_for_body(node.nodes) + end + if !matched && root == "endline" + matched = true + this.visit_endline(node.nodes) + end + if !matched && root == "else-stmt" + matched = true + this.visit_else_stmt(node.nodes) + end + if !matched && root == "catch-stmt" + matched = true + this.visit_catch_stmt(node.nodes) + end + if !matched && root == "switch-case" + matched = true + this.visit_switch_case(node.nodes) + end + if !matched && root == "switch-default" + matched = true + this.visit_switch_default(node.nodes) + end + if !matched && root == "switch-stmts" + matched = true + this.visit_switch_stmts(node.nodes) + end + if !matched && root == "until-stmt" + matched = true + this.visit_until_stmt(node.nodes) + end + # Format-grammar helpers: eol = optional inline comment + newline + if !matched && root == "eol" + matched = true + this.visit_eol(node.nodes) + end + # Format-grammar helpers: eos = newline or standalone comment + if !matched && root == "eos" + matched = true + this.visit_eos(node.nodes) + end + if !matched && root == "nl" + matched = true + this.visit_nl(node.nodes) + end + if !matched && root == "array" + matched = true + this.visit_array(node.nodes) + end + if !matched && root == "fcall" + matched = true + this.visit_fcall(node.nodes) + end + if !matched && root == "lambda-expr" + matched = true + this.visit_lambda_expr(node.nodes) + end + if !matched && root == "async-lambda-expr" + matched = true + this.visit_async_lambda_expr(node.nodes) + end + if !matched && root == "var-bind" + matched = true + this.visit_var_bind(node.nodes) + end + if !matched && root == "bind-expr" + matched = true + this.visit_bind_expr(node.nodes) + end + if !matched && root == "cond-postfix" + matched = true + this.visit_cond_postfix(node.nodes) + end + if !matched && root == "element" + matched = true + this.visit_element(node.nodes) + end + if !matched && root == "ecsx-extend" + matched = true + this.visit_ecsx_extend(node.nodes) + end + # Unary operators: no space between operator and operand + if !matched && root == "unary-op" + matched = true + var op = node.nodes[0].data + this.w.write(op) + if op == "typeid" || op == "not" + this.w.need_space = true + else + this.w.need_space = false + end + end + # Postfix operators: no space before ++/--/... + if !matched && root == "postfix-expr" + matched = true + this.w.need_space = false + this.visit_children(node.nodes) + end + # Lambda body: { stmts } or -> expr + if !matched && root == "lambda-body" + matched = true + this.visit_lambda_body(node.nodes) + end + if !matched + this.visit_children(node.nodes) + end + end + end + + # === Comment-aware line-end handlers === + + function visit_eol(nodes) + # eol = optional(com) + endl + # Inline comment (if present) before the newline + for idx = 0, idx < nodes.size, ++idx + var node = nodes[idx] + if typeid node == typeid parsergen.token_type + if node.type == "com" + this.w.space() + this.w.write(node.data) + end + if node.type == "endl" + this.w.newline() + end + end + end + end + + function visit_eos(nodes) + # eos = com + endl (standalone comment line) | endl (blank line) + if nodes.size >= 2 && typeid nodes[0] == typeid parsergen.token_type && nodes[0].type == "com" + # Standalone comment line + this.w.write(nodes[0].data) + this.w.newline() + else + # Blank line + this.w.newline() + end + end + + function visit_nl(nodes) + if nodes.size > 0 + this.w.newline() + this.w.continuation = 1 + end + end + + # === Top-level === + + function visit_begin(nodes) + if nodes.empty() + return + end + for idx = 0, idx < nodes[0].nodes.size, ++idx + var node = nodes[0].nodes[idx] + if typeid node == typeid parsergen.syntax_tree + if node.root == "statement" + this.visit_statement(node.nodes) + end + if node.root == "eos" + if this.skip_next_eos && node.nodes.size == 1 + this.skip_next_eos = false + else + this.skip_next_eos = false + this.visit_eos(node.nodes) + end + end + if node.root == "eol" + this.visit_eol(node.nodes) + end + end + if typeid node == typeid parsergen.token_type + if node.type == "com" + this.w.write(node.data) + this.w.newline() + end + end + end + end + + function visit_stmts(nodes) + this.w.push_indent() + for idx = 0, idx < nodes.size, ++idx + var node = nodes[idx] + if typeid node == typeid parsergen.syntax_tree + if node.root == "statement" + this.visit_statement(node.nodes) + end + if node.root == "eos" + if this.skip_next_eos && node.nodes.size == 1 + this.skip_next_eos = false + else + this.skip_next_eos = false + this.visit_eos(node.nodes) + end + end + if node.root == "eol" + this.visit_eol(node.nodes) + end + end + if typeid node == typeid parsergen.token_type + if node.type == "com" + this.w.write(node.data) + this.w.newline() + end + end + end + this.w.pop_indent() + end + + function visit_decl_stmts(nodes) + this.w.push_indent() + for idx = 0, idx < nodes.size, ++idx + var node = nodes[idx] + if typeid node == typeid parsergen.syntax_tree + if node.root == "declaration" + this.visit_declaration(node.nodes) + end + if node.root == "eos" + if this.skip_next_eos && node.nodes.size == 1 + this.skip_next_eos = false + else + this.skip_next_eos = false + this.visit_eos(node.nodes) + end + end + if node.root == "eol" + this.visit_eol(node.nodes) + end + end + if typeid node == typeid parsergen.token_type + if node.type == "com" + this.w.write(node.data) + this.w.newline() + end + end + end + this.w.pop_indent() + end + + function visit_statement(nodes) + if nodes.empty() + return + end + var node = nodes[0] + if typeid node == typeid parsergen.syntax_tree + this.dispatch(node) + end + end + + function visit_declaration(nodes) + if nodes.empty() + return + end + var node = nodes[0] + if typeid node == typeid parsergen.syntax_tree + this.dispatch(node) + end + end + + function visit_array(nodes) + var idx = 0 + if idx >= nodes.size + return + end + if typeid nodes[idx] == typeid parsergen.token_type + this.w.write(nodes[idx++].data) + end + var multiline = false + for i = idx, i < nodes.size, ++i + if typeid nodes[i] == typeid parsergen.syntax_tree && nodes[i].root == "nl" && nodes[i].nodes.size > 0 + multiline = true + break + end + end + if multiline + this.w.newline() + this.w.push_indent() + while idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "nl" + ++idx + end + end + while idx < nodes.size + var node = nodes[idx] + if typeid node == typeid parsergen.token_type + if node.data == "}" || node.data == "]" + break + end + this.w.token(node) + ++idx + else + if typeid node == typeid parsergen.syntax_tree + if node.root == "nl" + if multiline + this.w.newline() + end + ++idx + else + this.dispatch(node) + this.w.continuation = 0 + ++idx + end + end + end + end + if multiline + while idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "nl" + ++idx + end + this.w.pop_indent() + end + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.token_type + this.w.write(nodes[idx++].data) + end + end + + function visit_fcall(nodes) + var idx = 0 + if idx >= nodes.size + return + end + if typeid nodes[idx] == typeid parsergen.token_type + this.w.write(nodes[idx++].data) + end + var multiline = false + for i = idx, i < nodes.size, ++i + if typeid nodes[i] == typeid parsergen.syntax_tree && nodes[i].root == "nl" && nodes[i].nodes.size > 0 + multiline = true + break + end + end + if multiline + this.w.newline() + this.w.push_indent() + while idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "nl" + ++idx + end + end + while idx < nodes.size + var node = nodes[idx] + if typeid node == typeid parsergen.token_type + if node.data == ")" + break + end + this.w.token(node) + ++idx + else + if typeid node == typeid parsergen.syntax_tree + if node.root == "nl" + if multiline + this.w.newline() + end + ++idx + else + this.dispatch(node) + ++idx + end + end + end + end + if multiline + while idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "nl" + ++idx + end + this.w.pop_indent() + end + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.token_type + this.w.write(nodes[idx++].data) + end + end + + function visit_endline(nodes) + for idx = 0, idx < nodes.size, ++idx + var node = nodes[idx] + if typeid node == typeid parsergen.syntax_tree + this.skip_next_eos = false + this.dispatch(node) + end + if typeid node == typeid parsergen.token_type + if node.data == ";" + this.skip_next_eos = true + this.w.write(";") + this.w.newline() + end + end + end + end + + # === Block statements === + + function visit_if_stmt(nodes) + var idx = 0 + ++idx; this.w.keyword("if") + this.visit_children(nodes[idx++].nodes) + # eol (was endl token) + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "eol" + this.visit_eol(nodes[idx++].nodes) + end + this.visit_stmts(nodes[idx++].nodes) + while idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "else-stmt" + this.visit_else_stmt(nodes[idx++].nodes) + this.visit_stmts(nodes[idx++].nodes) + end + ++idx; this.w.keyword("end") + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "eol" + this.visit_eol(nodes[idx++].nodes) + end + end + + function visit_else_stmt(nodes) + var idx = 0 + ++idx; this.w.keyword("else") + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.token_type && nodes[idx].data == "if" + this.w.space() + ++idx; this.w.keyword("if") + this.visit_children(nodes[idx++].nodes) + end + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "eol" + this.visit_eol(nodes[idx++].nodes) + end + end + + function visit_while_stmt(nodes) + var idx = 0 + ++idx; this.w.keyword("while") + this.visit_children(nodes[idx++].nodes) + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "eol" + this.visit_eol(nodes[idx++].nodes) + end + this.visit_stmts(nodes[idx++].nodes) + ++idx; this.w.keyword("end") + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "eol" + this.visit_eol(nodes[idx++].nodes) + end + end + + function visit_loop_stmt(nodes) + var idx = 0 + ++idx; this.w.keyword("loop") + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "eol" + this.visit_eol(nodes[idx++].nodes) + end + this.visit_stmts(nodes[idx++].nodes) + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "until-stmt" + this.visit_until_stmt(nodes[idx++].nodes) + end + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.token_type && nodes[idx].data == "end" + ++idx; this.w.keyword("end") + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "eol" + this.visit_eol(nodes[idx++].nodes) + end + end + end + + function visit_until_stmt(nodes) + var idx = 0 + ++idx; this.w.keyword("until") + this.visit_children(nodes[idx++].nodes) + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "eol" + this.visit_eol(nodes[idx++].nodes) + end + end + + function visit_for_stmt(nodes) + var idx = 0 + ++idx; this.w.keyword("for") + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "var-def" + this.visit_children(nodes[idx++].nodes) + end + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.token_type + this.w.write(nodes[idx++].data) + this.w.space() + end + # skip nl + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "nl" + ++idx + end + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "basic-expr" + this.visit_children(nodes[idx++].nodes) + end + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.token_type + this.w.write(nodes[idx++].data) + this.w.space() + end + # skip nl + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "nl" + ++idx + end + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "basic-expr" + this.visit_children(nodes[idx++].nodes) + end + this.visit_for_body(nodes[idx++].nodes) + end + + function visit_foreach_stmt(nodes) + var idx = 0 + ++idx; this.w.keyword("foreach") + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.token_type && nodes[idx].type == "id" + this.w.write(nodes[idx++].data) + this.w.space() + end + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.token_type && nodes[idx].data == "in" + ++idx; this.w.keyword("in") + end + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "basic-expr" + this.visit_children(nodes[idx++].nodes) + end + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "for-body" + this.visit_for_body(nodes[idx++].nodes) + end + end + + function visit_for_body(nodes) + var idx = 0 + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.token_type && nodes[idx].data == "do" + ++idx; this.w.keyword("do") + this.visit_children(nodes[idx++].nodes) + this.visit_endline(nodes[idx++].nodes) + end + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "eol" + this.visit_eol(nodes[idx++].nodes) + this.visit_stmts(nodes[idx++].nodes) + ++idx; this.w.keyword("end") + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "eol" + this.visit_eol(nodes[idx++].nodes) + end + end + end + + function visit_switch_stmt(nodes) + var idx = 0 + ++idx; this.w.keyword("switch") + this.visit_children(nodes[idx++].nodes) + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "eol" + this.visit_eol(nodes[idx++].nodes) + end + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "switch-stmts" + this.visit_switch_stmts(nodes[idx++].nodes) + end + ++idx; this.w.keyword("end") + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "eol" + this.visit_eol(nodes[idx++].nodes) + end + end + + function visit_switch_stmts(nodes) + for idx = 0, idx < nodes.size, ++idx + var node = nodes[idx] + if typeid node == typeid parsergen.syntax_tree + if node.root == "switch-case" + this.visit_switch_case(node.nodes) + end + if node.root == "switch-default" + this.visit_switch_default(node.nodes) + end + if node.root == "eos" + this.visit_eos(node.nodes) + end + end + end + end + + function visit_switch_case(nodes) + var idx = 0 + ++idx; this.w.keyword("case") + this.visit_children(nodes[idx++].nodes) + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "eol" + this.visit_eol(nodes[idx++].nodes) + end + this.visit_stmts(nodes[idx++].nodes) + ++idx; this.w.keyword("end") + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "eol" + this.visit_eol(nodes[idx++].nodes) + end + end + + function visit_switch_default(nodes) + var idx = 0 + ++idx; this.w.keyword("default") + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "eol" + this.visit_eol(nodes[idx++].nodes) + end + this.visit_stmts(nodes[idx++].nodes) + ++idx; this.w.keyword("end") + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "eol" + this.visit_eol(nodes[idx++].nodes) + end + end + + function visit_try_stmt(nodes) + var idx = 0 + ++idx; this.w.keyword("try") + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "eol" + this.visit_eol(nodes[idx++].nodes) + end + this.visit_stmts(nodes[idx++].nodes) + while idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "catch-stmt" + this.visit_catch_stmt(nodes[idx++].nodes) + this.visit_stmts(nodes[idx++].nodes) + end + ++idx; this.w.keyword("end") + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "eol" + this.visit_eol(nodes[idx++].nodes) + end + end + + function visit_catch_stmt(nodes) + var idx = 0 + ++idx; this.w.keyword("catch") + this.w.write(nodes[idx++].data) + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.token_type && nodes[idx].data == ":" + this.w.write(nodes[idx++].data) + this.w.space() + this.visit_children(nodes[idx++].nodes) + end + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "eol" + this.visit_eol(nodes[idx++].nodes) + end + end + + function visit_block_stmt(nodes) + var idx = 0 + ++idx; this.w.keyword("block") + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "eol" + this.visit_eol(nodes[idx++].nodes) + end + this.visit_stmts(nodes[idx++].nodes) + ++idx; this.w.keyword("end") + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "eol" + this.visit_eol(nodes[idx++].nodes) + end + end + + function visit_lambda_expr_impl(nodes, is_async) + var idx = 0 + if is_async + ++idx; this.w.keyword("async") + end + ++idx; this.w.write("[") + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "capture-list" + this.visit_children(nodes[idx++].nodes) + end + ++idx; this.w.write("]") + ++idx; this.w.write("(") + var arg_multiline = false + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "nl" + if nodes[idx].nodes.size > 0 + arg_multiline = true + end + ++idx + end + if arg_multiline + this.w.newline() + this.w.push_indent() + end + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "argument-list" + this.visit_children(nodes[idx++].nodes) + end + if arg_multiline + this.w.pop_indent() + end + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "nl" + if arg_multiline + this.w.newline() + end + ++idx + end + ++idx; this.w.write(")") + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "lambda-body" + this.visit_lambda_body(nodes[idx++].nodes) + end + end + + function visit_lambda_expr(nodes) + this.visit_lambda_expr_impl(nodes, false) + end + + function visit_async_lambda_expr(nodes) + this.visit_lambda_expr_impl(nodes, true) + end + + function visit_parenthesized(nodes) + var idx = 0 + if idx >= nodes.size + return + end + if typeid nodes[idx] == typeid parsergen.token_type + this.w.write(nodes[idx++].data) + end + var multiline = false + for i = idx, i < nodes.size, ++i + if typeid nodes[i] == typeid parsergen.syntax_tree && nodes[i].root == "nl" && nodes[i].nodes.size > 0 + multiline = true + break + end + end + if multiline + this.w.newline() + this.w.push_indent() + while idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "nl" + ++idx + end + end + while idx < nodes.size + var node = nodes[idx] + if typeid node == typeid parsergen.token_type + if node.data == ")" + break + end + if node.data == "," + this.w.write(",") + this.w.mark_break() + ++idx + else + this.w.token(node) + ++idx + end + else + if typeid node == typeid parsergen.syntax_tree + if node.root == "nl" + if multiline + this.w.newline() + end + ++idx + else + this.dispatch(node) + ++idx + end + end + end + end + if multiline + while idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "nl" + ++idx + end + this.w.pop_indent() + end + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.token_type + this.w.write(nodes[idx++].data) + end + end + + function visit_var_bind(nodes) + this.visit_parenthesized(nodes) + end + + function visit_bind_expr(nodes) + this.visit_parenthesized(nodes) + end + + function visit_cond_postfix(nodes) + var idx = 0 + if idx >= nodes.size + return + end + var multiline = false + for i = 0, i < nodes.size, ++i + if typeid nodes[i] == typeid parsergen.syntax_tree && nodes[i].root == "nl" && nodes[i].nodes.size > 0 + multiline = true + break + end + end + var pushed = false + while idx < nodes.size + var node = nodes[idx] + if typeid node == typeid parsergen.token_type + if node.data == "?" || node.data == ":" + if multiline && pushed + this.w.pop_indent() + this.w.newline() + pushed = false + end + this.w.write(node.data) + this.w.mark_break() + if multiline + this.w.newline() + this.w.push_indent() + pushed = true + end + ++idx + end + else + if typeid node == typeid parsergen.syntax_tree + if node.root == "nl" + if multiline + this.w.newline() + end + ++idx + else + this.dispatch(node) + ++idx + end + end + end + end + if pushed + this.w.pop_indent() + end + end + + function visit_element(nodes) + this.visit_children(nodes) + end + + function visit_ecsx_extend(nodes) + this.visit_children(nodes) + end + + # === Declarations === + + function visit_function_stmt_impl(nodes, is_async) + var idx = 0 + if is_async + ++idx; this.w.keyword("async") + end + ++idx; this.w.keyword("function") + this.w.write(nodes[idx++].data) + ++idx; this.w.write("(") + var arg_multiline = false + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "nl" + if nodes[idx].nodes.size > 0 + arg_multiline = true + end + ++idx + end + if arg_multiline + this.w.newline() + this.w.push_indent() + end + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "argument-list" + this.visit_children(nodes[idx++].nodes) + end + if arg_multiline + this.w.pop_indent() + end + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "nl" + if arg_multiline + this.w.newline() + end + ++idx + end + ++idx; this.w.write(")") + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.token_type && nodes[idx].data == "override" + this.w.space() + ++idx; this.w.write("override") + end + this.visit_function_body(nodes[idx++].nodes) + end + + function visit_function_stmt(nodes) + this.visit_function_stmt_impl(nodes, false) + end + + function visit_async_function_stmt(nodes) + this.visit_function_stmt_impl(nodes, true) + end + + function visit_function_body(nodes) + var idx = 0 + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.token_type && nodes[idx].data == "{" + ++idx; this.w.write("{") + this.w.newline() + this.skip_next_eos = true + this.visit_stmts(nodes[idx++].nodes) + ++idx; this.w.write("}") + end + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "eol" + this.visit_eol(nodes[idx++].nodes) + this.visit_stmts(nodes[idx++].nodes) + ++idx; this.w.keyword("end") + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "eol" + this.visit_eol(nodes[idx++].nodes) + end + end + end + + function visit_lambda_body(nodes) + var idx = 0 + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.token_type && nodes[idx].data == "{" + ++idx; this.w.write("{") + this.w.newline() + this.skip_next_eos = true + this.visit_stmts(nodes[idx++].nodes) + ++idx; this.w.write("}") + end + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.token_type && nodes[idx].data == "->" + ++idx; this.w.write("->") + this.w.space() + this.visit_children(nodes[idx++].nodes) + end + end + + function visit_class_stmt(nodes) + var idx = 0 + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.token_type + this.w.keyword(nodes[idx++].data) + end + this.w.write(nodes[idx++].data) + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.token_type && nodes[idx].data == "extends" + this.w.space() + ++idx; this.w.keyword("extends") + this.visit_children(nodes[idx++].nodes) + end + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "eol" + this.visit_eol(nodes[idx++].nodes) + end + this.visit_decl_stmts(nodes[idx++].nodes) + ++idx; this.w.keyword("end") + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "eol" + this.visit_eol(nodes[idx++].nodes) + end + end + + function visit_namespace_stmt(nodes) + var idx = 0 + ++idx; this.w.keyword("namespace") + this.w.write(nodes[idx++].data) + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "eol" + this.visit_eol(nodes[idx++].nodes) + end + this.visit_decl_stmts(nodes[idx++].nodes) + ++idx; this.w.keyword("end") + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "eol" + this.visit_eol(nodes[idx++].nodes) + end + end + + function visit_var_stmt(nodes) + var idx = 0 + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.token_type + this.w.keyword(nodes[idx++].data) + end + this.visit_children(nodes[idx++].nodes) + this.visit_endline(nodes[idx++].nodes) + end + + # === Simple statements === + + function visit_return_stmt(nodes) + var idx = 0 + ++idx; this.w.keyword("return") + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root != "endline" + this.w.space() + this.visit_children(nodes[idx++].nodes) + end + this.visit_endline(nodes[idx++].nodes) + end + + function visit_throw_stmt(nodes) + var idx = 0 + ++idx; this.w.keyword("throw") + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root != "endline" + this.w.space() + this.visit_children(nodes[idx++].nodes) + end + this.visit_endline(nodes[idx++].nodes) + end + + function visit_yield_stmt(nodes) + var idx = 0 + ++idx; this.w.keyword("yield") + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root != "endline" + this.w.space() + this.visit_children(nodes[idx++].nodes) + end + this.visit_endline(nodes[idx++].nodes) + end + + function visit_control_stmt(nodes) + var idx = 0 + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.token_type + this.w.keyword(nodes[idx++].data) + end + this.visit_endline(nodes[idx++].nodes) + end + + function visit_expr_stmt(nodes) + var idx = 0 + this.visit_children(nodes[idx++].nodes) + this.visit_endline(nodes[idx++].nodes) + end + + function visit_import_stmt(nodes) + var idx = 0 + ++idx; this.w.keyword("import") + this.visit_children(nodes[idx++].nodes) + this.visit_endline(nodes[idx++].nodes) + end + + function visit_package_stmt(nodes) + var idx = 0 + ++idx; this.w.keyword("package") + this.w.write(nodes[idx++].data) + this.visit_endline(nodes[idx++].nodes) + end + + function visit_using_stmt(nodes) + var idx = 0 + ++idx; this.w.keyword("using") + this.visit_children(nodes[idx++].nodes) + this.visit_endline(nodes[idx++].nodes) + end + + function visit_prep_stmt(nodes) + for idx = 0, idx < nodes.size, ++idx + if typeid nodes[idx] == typeid parsergen.token_type + this.w.write(nodes[idx].data) + end + end + this.w.newline() + end +end + +# ============================================================ +# Public API +# ============================================================ + +function format_ast(file_name, code_buff, ast) + var vis = new format_visitor + return vis.run(file_name, code_buff, ast) +end + +function format_file(file_name, unicode_cvt) + # Use format-specific grammar so comments appear as "com" tokens in AST + var parser = new parsergen.generator + if unicode_cvt != null + parser.unicode_cvt = unicode_cvt + end + parser.add_grammar("ecs-lang-fmt", get_fmt_grammar()) + parser.from_file(file_name) + if parser.ast == null + system.out.println("Error: Failed to parse file '" + file_name + "'.") + return null + end + return format_ast(file_name, parser.code_buff, parser.ast) +end + +var version = "1.0.0" diff --git a/imports/ecs_generator.csp b/imports/ecs_generator.csp index 2024652..6a1458e 100644 --- a/imports/ecs_generator.csp +++ b/imports/ecs_generator.csp @@ -40,7 +40,7 @@ package ecs_generator # Master namespace ecs_info - constant version = "4.1.0 Cuon alpinus(Stable) Build 2" + constant version = "4.1.0 Cuon alpinus(Stable) Build 3" constant std_version = "260702" constant min_runtime = "260702" end @@ -1017,15 +1017,17 @@ class generator function visit_lambda_body(nodes) var idx = 0 if typeid nodes[idx] == typeid parsergen.token_type && nodes[idx].data == "{" - # Brace body: { stmts... } + # Brace body: { stmts } ++idx - while idx < nodes.size && (typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "statement") - if stack.front.body_stmts == null - stack.front.body_stmts = new array - end - stack.front.body_stmts.push_back(nodes[idx++].nodes) - while idx < nodes.size && (typeid nodes[idx] == typeid parsergen.token_type && nodes[idx].type == "endl") - ++idx + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "stmts" + var stmts_nodes = nodes[idx].nodes + for i = 0, i < stmts_nodes.size, ++i + if typeid stmts_nodes[i] == typeid parsergen.syntax_tree && stmts_nodes[i].root == "statement" + if stack.front.body_stmts == null + stack.front.body_stmts = new array + end + stack.front.body_stmts.push_back(stmts_nodes[i].nodes) + end end end else @@ -2529,7 +2531,7 @@ class generator print_indent() println("end", dat.pos) else - println("struct __" + ecs_prefix + "ecs_lambda_impl_" + to_string(lambda_base + i + 1) + "__", dat.pos) + println("struct __" + ecs_prefix + "ecs_lambda_impl_" + to_string(lambda_base + i + 1) + "__ extends " + ecs_prefix + "ecs.lambda_base", dat.pos) foreach it in dat.capture_list print_indent() println("var " + it.first + " = null", dat.pos) @@ -2620,6 +2622,7 @@ class generator end function repl_run(ast) slice_ext = false + lambda_base = 0 indent = -1 recursion_depth = 0 async_depth = 0 @@ -2630,14 +2633,16 @@ class generator try visit_begin(ast.nodes) catch e + lambda_list = new array + stack = new array + indent = -1 + recursion_depth = 0 + async_depth = 0 + in_struct_depth = 0 if e.what == "ECS_ERROR" - lambda_list = new array - stack = new array - indent = -1 - recursion_depth = 0 - async_depth = 0 - in_struct_depth = 0 return null + else + throw e end end var header = new iostream.char_buff diff --git a/imports/ecs_lint.csp b/imports/ecs_lint.csp new file mode 100644 index 0000000..3f6283b --- /dev/null +++ b/imports/ecs_lint.csp @@ -0,0 +1,806 @@ +# ECS Linter & Formatter v1.0.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Copyright (C) 2017-2026 Michael Lee(李登淳) +# +# Email: mikecovlee@163.com +# Github: https://github.com/mikecovlee +# Website: http://covscript.org.cn + +package ecs_lint + +import parsergen, ecs_parser + +# ============================================================ +# Severity Levels +# ============================================================ + +constant SEVERITY_ERROR = "error" +constant SEVERITY_WARNING = "warning" +constant SEVERITY_INFO = "info" + +# ============================================================ +# Issue +# ============================================================ + +struct issue + var pos = {0, 0} + var message = "" + var severity = "" + var rule_name = "" +end + +# ============================================================ +# Lint Context +# ============================================================ + +struct lint_context + var file_name = "" + var code_buff = new array + var issues = new array + + function add_issue(pos, message, severity, rule_name) + var iss = new issue + iss.pos = pos + iss.message = message + iss.severity = severity + iss.rule_name = rule_name + this.issues.push_back(iss) + end +end + +# ============================================================ +# Helpers +# ============================================================ + +function get_pos(nodes) + # Walk into nested syntax trees to find the first token + var current = nodes + while !current.empty() && typeid current[0] == typeid parsergen.syntax_tree + current = current[0].nodes + end + if !current.empty() && typeid current[0] == typeid parsergen.token_type + return current[0].pos + else + return {0, 0} + end +end + +function is_snake_case(name) + for i = 0, i < name.size, ++i + var code = to_integer(name[i]) + if code >= 65 && code <= 90 + return false + end + end + return true +end + +function is_upper_snake_case(name) + for i = 0, i < name.size, ++i + var code = to_integer(name[i]) + if code >= 97 && code <= 122 + return false + end + end + return true +end + + +# ============================================================ +# Lint Rules +# ============================================================ + +function rule_function_naming(ctx, nodes) + # nodes: function-stmt children + # Pattern: "function" id "(" ... ")" ... + var idx = 0 + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.token_type && nodes[idx].data == "function" + ++idx + end + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.token_type && nodes[idx].type == "id" + var name = nodes[idx].data + if !is_snake_case(name) + ctx.add_issue(nodes[idx].pos, "Function name '" + name + "' should be snake_case.", SEVERITY_WARNING, "function_naming") + end + end +end + +function rule_async_function_naming(ctx, nodes) + var idx = 0 + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.token_type && nodes[idx].data == "async" + ++idx + end + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.token_type && nodes[idx].data == "function" + ++idx + end + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.token_type && nodes[idx].type == "id" + var name = nodes[idx].data + if !is_snake_case(name) + ctx.add_issue(nodes[idx].pos, "Function name '" + name + "' should be snake_case.", SEVERITY_WARNING, "function_naming") + end + end +end + +function rule_variable_naming(ctx, nodes) + # nodes: var-stmt children + # Pattern: ("var"|"link"|"constant") (id ... | var-list | var-bind) + var idx = 0 + var decl_kind = "" + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.token_type + decl_kind = nodes[idx].data + ++idx + end + # var-def + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree && nodes[idx].root == "var-def" + var def_nodes = nodes[idx].nodes + if decl_kind == "constant" + # Check constant naming + check_var_names(ctx, def_nodes, true) + else + # Check var/link naming + check_var_names(ctx, def_nodes, false) + end + end +end + +function check_var_names(ctx, def_nodes, is_constant) + var didx = 0 + if didx < def_nodes.size && typeid def_nodes[didx] == typeid parsergen.syntax_tree + if def_nodes[didx].root == "var-bind" + check_var_bind(ctx, def_nodes[didx].nodes, is_constant) + return + end + if def_nodes[didx].root == "var-list" + check_var_list(ctx, def_nodes[didx].nodes, is_constant) + end + end +end + +function check_var_bind(ctx, nodes, is_constant) + for i = 0, i < nodes.size, ++i + if typeid nodes[i] == typeid parsergen.token_type && nodes[i].type == "id" + var name = nodes[i].data + if is_constant + if !is_upper_snake_case(name) + ctx.add_issue(nodes[i].pos, "Constant name '" + name + "' should be UPPER_SNAKE_CASE.", SEVERITY_WARNING, "constant_naming") + end + else + if !is_snake_case(name) + ctx.add_issue(nodes[i].pos, "Variable name '" + name + "' should be snake_case.", SEVERITY_WARNING, "variable_naming") + end + end + end + if typeid nodes[i] == typeid parsergen.syntax_tree + check_var_bind(ctx, nodes[i].nodes, is_constant) + end + end +end + +function check_var_list(ctx, nodes, is_constant) + var idx = 0 + while idx < nodes.size + if typeid nodes[idx] == typeid parsergen.token_type && nodes[idx].type == "id" + var name = nodes[idx].data + if is_constant + if !is_upper_snake_case(name) + ctx.add_issue(nodes[idx].pos, "Constant name '" + name + "' should be UPPER_SNAKE_CASE.", SEVERITY_WARNING, "constant_naming") + end + else + if !is_snake_case(name) + ctx.add_issue(nodes[idx].pos, "Variable name '" + name + "' should be snake_case.", SEVERITY_WARNING, "variable_naming") + end + end + end + # Skip to next comma or end + ++idx + while idx < nodes.size && typeid nodes[idx] == typeid parsergen.syntax_tree + ++idx + end + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.token_type && nodes[idx].data == "," + ++idx + end + end +end + +function rule_class_naming(ctx, nodes) + # nodes: class-stmt children + # Pattern: ("class"|"struct") id ... + var idx = 0 + # skip "class" or "struct" + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.token_type + ++idx + end + if idx < nodes.size && typeid nodes[idx] == typeid parsergen.token_type && nodes[idx].type == "id" + var name = nodes[idx].data + if !is_snake_case(name) + ctx.add_issue(nodes[idx].pos, "Class/struct name '" + name + "' should be snake_case.", SEVERITY_WARNING, "class_naming") + end + end +end + +function check_empty_stmts(ctx, node, first_pos, keyword_name) + if node.root == "stmts" + var has_stmt = false + for j = 0, j < node.nodes.size, ++j + if typeid node.nodes[j] == typeid parsergen.syntax_tree && node.nodes[j].root == "statement" + has_stmt = true + break + end + end + if !has_stmt + ctx.add_issue(first_pos, "Empty " + keyword_name + " block body.", SEVERITY_WARNING, "empty_block") + end + else + if node.root == "decl-stmts" + var has_decl = false + for j = 0, j < node.nodes.size, ++j + if typeid node.nodes[j] == typeid parsergen.syntax_tree && node.nodes[j].root == "declaration" + has_decl = true + break + end + end + if !has_decl + ctx.add_issue(first_pos, "Empty " + keyword_name + " block body.", SEVERITY_WARNING, "empty_block") + end + else + for j = 0, j < node.nodes.size, ++j + if typeid node.nodes[j] == typeid parsergen.syntax_tree + check_empty_stmts(ctx, node.nodes[j], first_pos, keyword_name) + end + end + end + end +end + +function rule_empty_block(ctx, nodes) + var first_pos = {0, 0} + var keyword_name = "block" + for i = 0, i < nodes.size, ++i + if typeid nodes[i] == typeid parsergen.token_type + if first_pos[1] == 0 + first_pos = nodes[i].pos + end + if keyword_name == "block" && nodes[i].data != "endl" && nodes[i].data != "async" + keyword_name = nodes[i].data + end + end + if typeid nodes[i] == typeid parsergen.syntax_tree + check_empty_stmts(ctx, nodes[i], first_pos, keyword_name) + end + end +end + +function rule_unreachable_code(ctx, nodes) + # Check if there are statements after return/throw/break/continue + # within the same stmts sequence + var seen_terminator = false + for i = 0, i < nodes.size, ++i + if typeid nodes[i] == typeid parsergen.syntax_tree && nodes[i].root == "statement" + var stmt_nodes = nodes[i].nodes + if !stmt_nodes.empty() + var first = stmt_nodes[0] + if typeid first == typeid parsergen.syntax_tree + if first.root == "return-stmt" || first.root == "throw-stmt" || first.root == "control-stmt" + seen_terminator = true + else + if seen_terminator + ctx.add_issue(get_pos(stmt_nodes), "Unreachable code after return, throw, break or continue.", SEVERITY_ERROR, "unreachable_code") + end + end + end + end + end + end +end + +function is_side_effect_free(nodes) + for i = 0, i < nodes.size, ++i + if typeid nodes[i] == typeid parsergen.syntax_tree + var root = nodes[i].root + if root == "fcall" || root == "index" + return false + end + if root == "asi-op" + return false + end + if root == "unary-op" + if !nodes[i].nodes.empty() + var op = nodes[i].nodes[0].data + if op == "++" || op == "--" + return false + end + end + end + if root == "postfix-expr" + if !nodes[i].nodes.empty() + var pf = nodes[i].nodes[0].data + if pf == "++" || pf == "--" + return false + end + end + end + if !is_side_effect_free(nodes[i].nodes) + return false + end + end + if typeid nodes[i] == typeid parsergen.token_type + var data = nodes[i].data + if data == "new" || data == "gcnew" + return false + end + end + end + return true +end + +function rule_useless_expression(ctx, nodes) + if nodes.empty() + return + end + if is_side_effect_free(nodes) + var pos = get_pos(nodes) + ctx.add_issue(pos, "Useless expression with no side effect.", SEVERITY_WARNING, "useless_expression") + end +end + +# === Source-level rules (operate on code_buff, not AST) === + +function rule_trailing_whitespace(ctx) + for i = 0, i < ctx.code_buff.size, ++i + var line = ctx.code_buff[i] + var line_len = line.size + # Remove trailing \n for check + if line_len > 0 && line[line_len - 1] == '\n' + --line_len + end + if line_len > 0 && line[line_len - 1] == '\r' + --line_len + end + if line_len > 0 && (line[line_len - 1] == ' ' || line[line_len - 1] == '\t') + ctx.add_issue({line_len, i}, "Trailing whitespace.", SEVERITY_INFO, "trailing_whitespace") + end + end +end + +function rule_consecutive_blank_lines(ctx) + var blank_count = 0 + for i = 0, i < ctx.code_buff.size, ++i + var line = ctx.code_buff[i] + var is_blank = true + for j = 0, j < line.size, ++j + var ch = line[j] + if ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n' + is_blank = false + break + end + end + if is_blank + ++blank_count + else + if blank_count > 2 + ctx.add_issue({1, i - blank_count}, "Too many consecutive blank lines (" + to_string(blank_count) + ").", SEVERITY_INFO, "consecutive_blank_lines") + end + blank_count = 0 + end + end + if blank_count > 2 + ctx.add_issue({1, ctx.code_buff.size - blank_count}, "Too many consecutive blank lines (" + to_string(blank_count) + ").", SEVERITY_INFO, "consecutive_blank_lines") + end +end + +function rule_indentation_consistency(ctx) + var has_tabs = false + var has_spaces = false + for i = 0, i < ctx.code_buff.size, ++i + var line = ctx.code_buff[i] + var in_indent = true + for j = 0, j < line.size, ++j + var ch = line[j] + if ch == '\t' + if in_indent + has_tabs = true + end + end + if ch == ' ' + if in_indent + has_spaces = true + end + end + if ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n' + in_indent = false + end + end + end + if has_tabs && has_spaces + ctx.add_issue({1, 1}, "Mixed tabs and spaces in indentation.", SEVERITY_WARNING, "indentation_consistency") + end +end + +# ============================================================ +# Linter — AST Walker + Rule Engine +# ============================================================ + +class linter + var ctx = null + + function run(file_name, code_buff, ast) + this.ctx = new lint_context + this.ctx.file_name = file_name + this.ctx.code_buff = code_buff + # Source-level checks + rule_trailing_whitespace(this.ctx) + rule_consecutive_blank_lines(this.ctx) + rule_indentation_consistency(this.ctx) + # Walk AST + if !ast.nodes.empty() + this.visit_begin(ast.nodes) + end + return this.ctx.issues + end + + function print_report() + if this.ctx.issues.empty() + return + end + var errors = new array + foreach iss in this.ctx.issues + var err = new parsergen.lex_error + err.text = iss.severity + ": [" + iss.rule_name + "] " + iss.message + err.pos = iss.pos + errors.push_back(err) + end + parsergen.print_error(this.ctx.file_name, this.ctx.code_buff, errors) + end + + function count_by_severity(severity) + var count = 0 + foreach iss in this.ctx.issues + if iss.severity == severity + ++count + end + end + return count + end + + # === AST Dispatch === + + function visit_node(nodes) + if nodes.empty() + return + end + var idx = 0 + while idx < nodes.size + var node = nodes[idx] + if typeid node == typeid parsergen.syntax_tree + var root = node.root + # Dispatch to specific visitor + block + var matched = false + if !matched && root == "begin" + matched = true + this.visit_begin(node.nodes) + end + if !matched && root == "stmts" + matched = true + this.visit_stmts(node.nodes) + end + if !matched && root == "decl-stmts" + matched = true + this.visit_decl_stmts(node.nodes) + end + if !matched && root == "statement" + matched = true + this.visit_statement(node.nodes) + end + if !matched && root == "declaration" + matched = true + this.visit_declaration(node.nodes) + end + if !matched && root == "var-stmt" + matched = true + this.visit_var_stmt(node.nodes) + end + if !matched && root == "function-stmt" + matched = true + this.visit_function_stmt(node.nodes) + end + if !matched && root == "async-function-stmt" + matched = true + this.visit_async_function_stmt(node.nodes) + end + if !matched && root == "class-stmt" + matched = true + this.visit_class_stmt(node.nodes) + end + if !matched && root == "if-stmt" + matched = true + this.visit_if_stmt(node.nodes) + end + if !matched && root == "while-stmt" + matched = true + this.visit_while_stmt(node.nodes) + end + if !matched && root == "loop-stmt" + matched = true + this.visit_loop_stmt(node.nodes) + end + if !matched && root == "for-stmt" + matched = true + this.visit_for_stmt(node.nodes) + end + if !matched && root == "foreach-stmt" + matched = true + this.visit_foreach_stmt(node.nodes) + end + if !matched && root == "switch-stmt" + matched = true + this.visit_switch_stmt(node.nodes) + end + if !matched && root == "try-stmt" + matched = true + this.visit_try_stmt(node.nodes) + end + if !matched && root == "namespace-stmt" + matched = true + this.visit_namespace_stmt(node.nodes) + end + if !matched && root == "block-stmt" + matched = true + this.visit_block_stmt(node.nodes) + end + if !matched && root == "return-stmt" + matched = true + this.visit_return_stmt(node.nodes) + end + if !matched && root == "throw-stmt" + matched = true + this.visit_throw_stmt(node.nodes) + end + if !matched && root == "control-stmt" + matched = true + this.visit_control_stmt(node.nodes) + end + if !matched && root == "yield-stmt" + matched = true + this.visit_yield_stmt(node.nodes) + end + if !matched && root == "expr-stmt" + matched = true + this.visit_expr_stmt(node.nodes) + end + if !matched + # For expression-related nodes, just recurse into children + this.visit_node(node.nodes) + end + end + end + ++idx + end + end + + # === Top-level === + + function visit_begin(nodes) + this.visit_node(nodes) + end + + function visit_stmts(nodes) + # Check unreachable code + rule_unreachable_code(this.ctx, nodes) + # Recurse into children + for i = 0, i < nodes.size, ++i + if typeid nodes[i] == typeid parsergen.syntax_tree && nodes[i].root == "statement" + this.visit_statement(nodes[i].nodes) + end + end + end + + function visit_decl_stmts(nodes) + for i = 0, i < nodes.size, ++i + if typeid nodes[i] == typeid parsergen.syntax_tree && nodes[i].root == "declaration" + this.visit_declaration(nodes[i].nodes) + end + end + end + + function visit_statement(nodes) + this.visit_node(nodes) + end + + function visit_declaration(nodes) + this.visit_node(nodes) + end + + # === Declarations === + + function visit_var_stmt(nodes) + rule_variable_naming(this.ctx, nodes) + # Recurse into var-def and initializers + for i = 0, i < nodes.size, ++i + if typeid nodes[i] == typeid parsergen.syntax_tree + this.visit_node(nodes[i].nodes) + end + end + end + + function visit_function_stmt(nodes) + rule_function_naming(this.ctx, nodes) + rule_empty_block(this.ctx, nodes) + for i = 0, i < nodes.size, ++i + if typeid nodes[i] == typeid parsergen.syntax_tree + this.visit_node(nodes[i].nodes) + end + end + end + + function visit_async_function_stmt(nodes) + rule_async_function_naming(this.ctx, nodes) + rule_empty_block(this.ctx, nodes) + for i = 0, i < nodes.size, ++i + if typeid nodes[i] == typeid parsergen.syntax_tree + this.visit_node(nodes[i].nodes) + end + end + end + + function visit_class_stmt(nodes) + rule_class_naming(this.ctx, nodes) + rule_empty_block(this.ctx, nodes) + for i = 0, i < nodes.size, ++i + if typeid nodes[i] == typeid parsergen.syntax_tree + if nodes[i].root == "decl-stmts" + this.visit_decl_stmts(nodes[i].nodes) + else + this.visit_node(nodes[i].nodes) + end + end + end + end + + # === Control Flow === + + function visit_if_stmt(nodes) + rule_empty_block(this.ctx, nodes) + for i = 0, i < nodes.size, ++i + if typeid nodes[i] == typeid parsergen.syntax_tree + if nodes[i].root == "stmts" + rule_unreachable_code(this.ctx, nodes[i].nodes) + end + this.visit_node(nodes[i].nodes) + end + end + end + + function visit_while_stmt(nodes) + rule_empty_block(this.ctx, nodes) + for i = 0, i < nodes.size, ++i + if typeid nodes[i] == typeid parsergen.syntax_tree + if nodes[i].root == "stmts" + rule_unreachable_code(this.ctx, nodes[i].nodes) + end + this.visit_node(nodes[i].nodes) + end + end + end + + function visit_loop_stmt(nodes) + rule_empty_block(this.ctx, nodes) + for i = 0, i < nodes.size, ++i + if typeid nodes[i] == typeid parsergen.syntax_tree + if nodes[i].root == "stmts" + rule_unreachable_code(this.ctx, nodes[i].nodes) + end + this.visit_node(nodes[i].nodes) + end + end + end + + function visit_for_stmt(nodes) + rule_empty_block(this.ctx, nodes) + this.visit_node(nodes) + end + + function visit_foreach_stmt(nodes) + rule_empty_block(this.ctx, nodes) + this.visit_node(nodes) + end + + function visit_switch_stmt(nodes) + rule_empty_block(this.ctx, nodes) + this.visit_node(nodes) + end + + function visit_try_stmt(nodes) + # Check empty catch blocks + rule_empty_block(this.ctx, nodes) + for i = 0, i < nodes.size, ++i + if typeid nodes[i] == typeid parsergen.syntax_tree + if nodes[i].root == "stmts" + rule_unreachable_code(this.ctx, nodes[i].nodes) + end + this.visit_node(nodes[i].nodes) + end + end + end + + # === Other statements === + + function visit_namespace_stmt(nodes) + for i = 0, i < nodes.size, ++i + if typeid nodes[i] == typeid parsergen.syntax_tree + if nodes[i].root == "decl-stmts" + this.visit_decl_stmts(nodes[i].nodes) + else + this.visit_node(nodes[i].nodes) + end + end + end + end + + function visit_block_stmt(nodes) + rule_empty_block(this.ctx, nodes) + for i = 0, i < nodes.size, ++i + if typeid nodes[i] == typeid parsergen.syntax_tree + if nodes[i].root == "stmts" + rule_unreachable_code(this.ctx, nodes[i].nodes) + end + this.visit_node(nodes[i].nodes) + end + end + end + + function visit_return_stmt(nodes) + # For unreachable code detection, handled in visit_stmts + this.visit_node(nodes) + end + + function visit_throw_stmt(nodes) + this.visit_node(nodes) + end + + function visit_control_stmt(nodes) + # break/continue — for unreachable code detection + this.visit_node(nodes) + end + + function visit_yield_stmt(nodes) + this.visit_node(nodes) + end + + function visit_expr_stmt(nodes) + rule_useless_expression(this.ctx, nodes) + this.visit_node(nodes) + end +end + + +# ============================================================ +# Public API +# ============================================================ + +function lint_file(file_name) + var parser = new parsergen.generator + parser.add_grammar("ecs-lang", ecs_parser.grammar) + parser.from_file(file_name) + if parser.ast == null + system.out.println("Error: Failed to parse file '" + file_name + "'.") + return null + end + var lint = new linter + var issues = lint.run(file_name, parser.code_buff, parser.ast) + return {lint, issues} +end + +var version = "1.0.0" diff --git a/imports/ecs_parser.csp b/imports/ecs_parser.csp index e68a0ea..9cffb1a 100644 --- a/imports/ecs_parser.csp +++ b/imports/ecs_parser.csp @@ -1,4 +1,4 @@ -# Covariant Script Parser Generator: Grammar of Extended CovScript(ECS Lang) v1.3.5 +# Covariant Script Parser Generator: Grammar of Extended CovScript(ECS Lang) v1.4.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -24,9 +24,9 @@ import parsergen, regex constant syntax = parsergen.syntax -function get_lexical(reg_builder) +function get_lexical(reg_builder, strict) @begin - return { + var lex = { "endl" : reg_builder("^\\n+$"), "id" : reg_builder("^[A-Za-z_\\p{Han}](\\w|\\p{Han})*$"), "num" : reg_builder("^[0-9]+\\.?([0-9]+)?$"), @@ -37,343 +37,377 @@ function get_lexical(reg_builder) "lsig" : reg_builder("^(>|<|&|(\\|)|&&|(\\|\\|)|!|=(=|>)?|!=?|>=?|<=?)$"), "brac" : reg_builder("^(\\(|\\)|\\[|\\]|\\{|\\}|,)$"), "prep" : reg_builder("^@.*$"), - "ign" : reg_builder("^([ \\f\\r\\t]+|#.*)$"), "err" : reg_builder("^(\"|\'|(\\|)|\\.\\.)$") }.to_hash_map() @end + if strict + lex["com"] = reg_builder("^#.*$") + lex["ign"] = reg_builder("^[ \\f\\r\\t]+$") + else + lex["ign"] = reg_builder("^([ \\f\\r\\t]+|#.*)$") + end + return lex end -@begin -var covscript_syntax = { - # Beginning of Parsing - "begin" : { - syntax.ref("stmts") - }, - # Ignore if not match initiatively - "ignore" : { - syntax.repeat(syntax.token("endl")) - }, - # End of Line - "endline" : {syntax.cond_or( - {syntax.token("endl")}, - {syntax.term(";")} - )}, - # Bootstrap - "stmts" : { - syntax.repeat(syntax.nlook(syntax.ref("endblock")), syntax.ref("statement"), syntax.repeat(syntax.token("endl"))) - }, - "decl-stmts" : { - syntax.repeat(syntax.nlook(syntax.ref("endblock")), syntax.ref("declaration"), syntax.repeat(syntax.token("endl"))) - }, - "endblock" : {syntax.cond_or( - {syntax.ref("end-stmt")}, - {syntax.ref("else-stmt")}, - {syntax.ref("until-stmt")}, - {syntax.ref("catch-stmt")} - )}, - "statement" : {syntax.cond_or( - {syntax.ref("prep-stmt")}, - {syntax.ref("package-stmt")}, - {syntax.ref("import-stmt")}, - {syntax.ref("var-stmt")}, - {syntax.ref("block-stmt")}, - {syntax.ref("namespace-stmt")}, - {syntax.ref("using-stmt")}, - {syntax.ref("if-stmt")}, - {syntax.ref("switch-stmt")}, - {syntax.ref("while-stmt")}, - {syntax.ref("loop-stmt")}, - {syntax.ref("for-stmt")}, - {syntax.ref("foreach-stmt")}, - {syntax.ref("async-function-stmt")}, - {syntax.ref("control-stmt")}, - {syntax.ref("function-stmt")}, - {syntax.ref("yield-stmt")}, - {syntax.ref("return-stmt")}, - {syntax.ref("try-stmt")}, - {syntax.ref("throw-stmt")}, - {syntax.ref("class-stmt")}, - {syntax.ref("expr-stmt")} - )}, - "declaration" : {syntax.cond_or( - {syntax.ref("prep-stmt")}, - {syntax.ref("namespace-stmt")}, - {syntax.ref("var-stmt")}, - {syntax.ref("using-stmt")}, - {syntax.ref("async-function-stmt")}, - {syntax.ref("function-stmt")}, - {syntax.ref("class-stmt")} - )}, - # Statements - "prep-stmt" : { - syntax.token("prep"), syntax.token("endl") - }, - "package-stmt" : { - syntax.term("package"), syntax.token("id"), syntax.ref("endline") - }, - "import-stmt" : { - syntax.term("import"), syntax.ref("import-list"), syntax.ref("endline") - }, - "module-list" : { - syntax.token("id"), syntax.optional(syntax.term("."), syntax.cond_or({syntax.term("*")}, {syntax.ref("module-list")})) - }, - "import-list" : { - syntax.ref("module-list"), syntax.optional(syntax.term("as"), syntax.token("id")), syntax.optional(syntax.term(","), syntax.ref("import-list")) - }, - "var-def" : { - syntax.cond_or({syntax.ref("var-bind"), syntax.term("="), syntax.ref("basic-expr")}, {syntax.ref("var-list")}) - }, - "var-stmt" : { - syntax.cond_or({syntax.term("var")}, {syntax.term("link")}, {syntax.term("constant")}), syntax.ref("var-def"), syntax.ref("endline") - }, - "var-bind" : { - syntax.term("("), syntax.ref("var-bind-list"), syntax.repeat(syntax.term(","), syntax.ref("var-bind-list")), syntax.term(")") - }, - "var-bind-list" : {syntax.cond_or( - {syntax.token("id")}, - {syntax.term("...")}, - {syntax.ref("var-bind")} - )}, - "var-list" : { - syntax.token("id"), syntax.cond_or( - {syntax.term("="), syntax.ref("basic-expr")}, - {syntax.term("as"), syntax.ref("unary-expr"), syntax.optional(syntax.ref("array"))} - ), syntax.optional(syntax.term(","), syntax.ref("var-list")) - }, - "block-stmt" : { - syntax.term("block"), syntax.token("endl"), syntax.ref("stmts"), syntax.term("end"), syntax.token("endl") - }, - "namespace-stmt" : { - syntax.term("namespace"), syntax.token("id"), syntax.token("endl"), syntax.ref("decl-stmts"), syntax.term("end"), syntax.token("endl") - }, - "using-stmt" : { - syntax.term("using"), syntax.ref("using-list"), syntax.ref("endline") - }, - "using-list" : { - syntax.ref("module-list"), syntax.optional(syntax.term(","), syntax.ref("using-list")) - }, - "if-stmt" : { - syntax.term("if"), syntax.ref("basic-expr"), syntax.token("endl"), syntax.ref("stmts"), syntax.repeat(syntax.ref("else-stmt"), syntax.ref("stmts")), syntax.term("end"), syntax.token("endl") - }, - "else-stmt" : { - syntax.term("else"), syntax.optional(syntax.nlook(syntax.token("endl")), syntax.term("if"), syntax.ref("basic-expr")), syntax.token("endl") - }, - "switch-stmt" : { - syntax.term("switch"), syntax.ref("basic-expr"), syntax.token("endl"), syntax.ref("switch-stmts"), syntax.term("end"), syntax.token("endl") - }, - "switch-stmts" : { - syntax.repeat(syntax.cond_or({syntax.ref("switch-case")}, {syntax.ref("switch-default")}), syntax.repeat(syntax.token("endl"))) - }, - "switch-case" : { - syntax.term("case"), syntax.ref("logic-or-expr"), syntax.token("endl"), syntax.ref("stmts"), syntax.term("end"), syntax.token("endl") - }, - "switch-default" : { - syntax.term("default"), syntax.token("endl"), syntax.ref("stmts"), syntax.term("end"), syntax.token("endl") - }, - "while-stmt" : { - syntax.term("while"), syntax.ref("basic-expr"), syntax.token("endl"), syntax.ref("stmts"), syntax.term("end"), syntax.token("endl") - }, - "loop-stmt" : { - syntax.term("loop"), syntax.token("endl"), syntax.ref("stmts"), syntax.cond_or({syntax.ref("until-stmt")}, {syntax.term("end"), syntax.token("endl")}) - }, - "until-stmt" : { - syntax.term("until"), syntax.ref("basic-expr"), syntax.token("endl") - }, - "for-stmt" : { - syntax.term("for"), syntax.optional(syntax.ref("var-def")), syntax.cond_or({syntax.term(";")}, {syntax.term(",")}), syntax.optional(syntax.ref("basic-expr")), syntax.cond_or({syntax.term(";")}, {syntax.term(",")}), syntax.optional(syntax.ref("basic-expr")), syntax.ref("for-body") - }, - "foreach-stmt" : { - syntax.term("foreach"), syntax.optional(syntax.nlook(syntax.term("in")), syntax.token("id")), syntax.term("in"), syntax.ref("basic-expr"), syntax.ref("for-body") - }, - "for-body" : {syntax.cond_or( - {syntax.term("do"), syntax.ref("basic-expr"), syntax.ref("endline")}, - {syntax.token("endl"), syntax.ref("stmts"), syntax.term("end"), syntax.token("endl")} - )}, - "function-stmt" : { - syntax.term("function"), syntax.token("id"), syntax.term("("), syntax.optional(syntax.ref("argument-list")), syntax.term(")"), syntax.optional(syntax.term("override")), syntax.ref("function-body") - }, - "function-body" : {syntax.cond_or( - {syntax.term("{"), syntax.ref("stmts"), syntax.term("}")}, - {syntax.token("endl"), syntax.ref("stmts"), syntax.term("end"), syntax.token("endl")} - )}, - "return-stmt" : { - syntax.term("return"), syntax.optional(syntax.nlook(syntax.token("endl")), syntax.ref("expr")), syntax.ref("endline") - }, - "try-stmt" : { - syntax.term("try"), syntax.token("endl"), syntax.ref("stmts"), syntax.repeat(syntax.ref("catch-stmt"), syntax.ref("stmts")), syntax.term("end"), syntax.token("endl") - }, - "catch-stmt" : { - syntax.term("catch"), syntax.token("id"), syntax.optional(syntax.term(":"), syntax.ref("visit-expr")), syntax.token("endl") - }, - "throw-stmt" : { - syntax.term("throw"), syntax.optional(syntax.nlook(syntax.token("endl")), syntax.ref("expr")), syntax.ref("endline") - }, - "class-stmt" : { - syntax.cond_or({syntax.term("class")}, {syntax.term("struct")}), syntax.token("id"), syntax.optional(syntax.term("extends"), syntax.ref("visit-expr")), syntax.token("endl"), - syntax.ref("decl-stmts"), syntax.term("end"), syntax.token("endl") - }, - "async-function-stmt" : { - syntax.term("async"), syntax.term("function"), syntax.token("id"), - syntax.term("("), syntax.optional(syntax.ref("argument-list")), syntax.term(")"), - syntax.optional(syntax.term("override")), syntax.ref("function-body") - }, - "yield-stmt" : { - syntax.term("yield"), syntax.optional(syntax.nlook(syntax.token("endl")), syntax.ref("expr")), syntax.ref("endline") - }, - "control-stmt" : { - syntax.cond_or({syntax.term("break")}, {syntax.term("continue")}), syntax.ref("endline") - }, - "expr-stmt" : { - syntax.ref("expr"), syntax.ref("endline") - }, - "end-stmt" : { - syntax.term("end"), syntax.token("endl") - }, - # Expression - "expr" : { - syntax.ref("basic-expr"), syntax.optional(syntax.term(","), syntax.ref("expr")) - }, - "bind-expr" : { - syntax.term("("), syntax.ref("bind-list"), syntax.repeat(syntax.term(","), syntax.ref("bind-list")), syntax.term(")") - }, - "bind-list" : {syntax.cond_or( - {syntax.token("id")}, - {syntax.term("...")}, - {syntax.ref("bind-expr")} - )}, - "basic-expr" : {syntax.cond_or( - {syntax.ref("bind-expr"), syntax.term("="), syntax.ref("cond-expr")}, - {syntax.ref("cond-expr"), syntax.optional(syntax.ref("asi-op"), syntax.ref("basic-expr"))} - )}, - "asi-op" : {syntax.cond_or( - {syntax.term("=")}, - {syntax.term(":=")}, - {syntax.term("+=")}, - {syntax.term("-=")}, - {syntax.term("*=")}, - {syntax.term("/=")}, - {syntax.term("%=")}, - {syntax.term("^=")} - )}, - "async-lambda-expr" : { - syntax.term("async"), syntax.term("["), syntax.optional(syntax.ref("capture-list")), syntax.term("]"), - syntax.term("("), syntax.optional(syntax.ref("argument-list")), syntax.term(")"), - syntax.ref("lambda-body") - }, - "lambda-expr" : { - syntax.term("["), syntax.optional(syntax.ref("capture-list")), syntax.term("]"), syntax.term("("), syntax.optional(syntax.ref("argument-list")), syntax.term(")"), syntax.ref("lambda-body") - }, - "capture-list" : { - syntax.optional(syntax.term("=")), syntax.token("id"), syntax.repeat(syntax.term(","), syntax.ref("capture-list")) - }, - "argument-list" : {syntax.cond_or( - {syntax.term("..."), syntax.token("id")}, - {syntax.optional(syntax.term("=")), syntax.token("id"), syntax.optional(syntax.term(":"), syntax.ref("visit-expr")), syntax.repeat(syntax.term(","), syntax.ref("argument-list"))} - )}, - "lambda-body" : {syntax.cond_or( - {syntax.term("{"), syntax.repeat(syntax.ref("statement"), syntax.repeat(syntax.token("endl"))), syntax.term("}")}, - {syntax.term("->"), syntax.ref("cond-expr")} - )}, - "cond-expr" : {syntax.cond_or( - {syntax.ref("async-lambda-expr")}, - {syntax.ref("lambda-expr")}, - {syntax.ref("logic-or-expr"), syntax.optional(syntax.ref("cond-postfix"))} - )}, - "cond-postfix" : {syntax.cond_or( - {syntax.term("?"), syntax.ref("value-expr"), syntax.term(":"), syntax.ref("cond-expr")}, - {syntax.term(":"), syntax.ref("value-expr")} - )}, - "value-expr" : {syntax.cond_or( - {syntax.ref("async-lambda-expr")}, - {syntax.ref("lambda-expr")}, - {syntax.ref("logic-or-expr")} - )}, - "logic-or-expr" : { - syntax.ref("logic-and-expr"), syntax.optional(syntax.cond_or({syntax.term("||")}, {syntax.term("or")}), syntax.ref("logic-or-expr")) - }, - "logic-and-expr" : { - syntax.ref("equal-expr"), syntax.optional(syntax.cond_or({syntax.term("&&")}, {syntax.term("and")}), syntax.ref("logic-and-expr")) - }, - "equal-expr" : { - syntax.ref("relat-expr"), syntax.optional(syntax.cond_or({syntax.term("==")}, {syntax.term("!=")}, {syntax.term("is")}, {syntax.term("not")}), syntax.ref("equal-expr")) - }, - "relat-expr" : { - syntax.ref("add-expr"), syntax.optional(syntax.cond_or({syntax.term(">")}, {syntax.term("<")}, {syntax.term(">=")}, {syntax.term("<=")}), syntax.ref("relat-expr")) - }, - "add-expr" : { - syntax.ref("mul-expr"), syntax.optional(syntax.cond_or({syntax.term("+")}, {syntax.term("-")}), syntax.ref("add-expr")) - }, - "mul-expr" : { - syntax.ref("conv-expr"), syntax.optional(syntax.nlook(syntax.token("endl")), syntax.cond_or({syntax.term("*")}, {syntax.term("/")}, {syntax.term("%")}, {syntax.term("^")}), syntax.ref("mul-expr")) - }, - "conv-expr" : { - syntax.ref("unary-expr"), syntax.optional(syntax.nlook(syntax.token("endl")), syntax.cond_or({syntax.term("=>")}, {syntax.term("as")}), syntax.ref("visit-expr")) - }, - "unary-expr" : {syntax.cond_or( - {syntax.ref("unary-op"), syntax.ref("unary-expr")}, - {syntax.cond_or({syntax.term("new")}, {syntax.term("gcnew")}), syntax.ref("visit-expr"), syntax.optional(syntax.ref("array"))}, - {syntax.ref("prim-expr"), syntax.optional(syntax.nlook(syntax.token("endl")), syntax.ref("postfix-expr"))} - )}, - "unary-op" : {syntax.cond_or( - {syntax.term("typeid")}, - {syntax.term("++")}, - {syntax.term("--")}, - {syntax.term("*")}, - {syntax.term("&")}, - {syntax.term("-")}, - {syntax.term("!")}, - {syntax.term("not")} - )}, - "postfix-expr" : { - syntax.cond_or({syntax.term("++")}, {syntax.term("--")}, {syntax.term("...")}), syntax.optional(syntax.ref("postfix-expr")) - }, - "await-expr" : { - syntax.term("await"), syntax.ref("unary-expr") - }, - "prim-expr" : {syntax.cond_or( - {syntax.ref("await-expr")}, - {syntax.ref("visit-expr")}, - {syntax.ref("constant")} - )}, - "visit-expr" : { - syntax.ref("object"), syntax.optional(syntax.cond_or({syntax.term("->")}, {syntax.term(".")}), syntax.ref("visit-expr")) - }, - "object" : {syntax.cond_or( - {syntax.ref("array"), syntax.optional(syntax.ref("index"))}, - {syntax.token("str"), syntax.optional(syntax.ref("index"))}, - {syntax.term("local")}, - {syntax.term("global")}, - {syntax.ref("ecsx-extend")}, - {syntax.ref("element")}, - {syntax.token("char")} - )}, - "ecsx-extend" : { - syntax.token("id"), syntax.nlook(syntax.token("endl")), syntax.term("::"), syntax.token("id"), syntax.term("("), syntax.optional(syntax.ref("basic-expr")), syntax.term(")") - }, - "element" : { - syntax.cond_or({syntax.token("id")}, {syntax.term("("), syntax.ref("basic-expr"), syntax.term(")")}), - syntax.repeat(syntax.nlook(syntax.token("endl")), syntax.cond_or({syntax.ref("fcall")}, {syntax.ref("index")})) - }, - "constant" : {syntax.cond_or( - {syntax.token("num")}, - {syntax.term("null")}, - {syntax.term("true")}, - {syntax.term("false")} - )}, - "array" : { - syntax.term("{"), syntax.optional(syntax.ref("expr")), syntax.term("}") - }, - "fcall" : { - syntax.term("("), syntax.optional(syntax.ref("expr")), syntax.term(")") - }, - "index" : {syntax.cond_or( - {syntax.term("["), syntax.optional(syntax.ref("add-expr")), syntax.optional(syntax.term(":"), syntax.optional(syntax.ref("add-expr")), syntax.optional(syntax.term(":"), syntax.optional(syntax.ref("add-expr")))), syntax.term("]")}, - {syntax.term("["), syntax.term("::"), syntax.term("]")} - )} -}.to_hash_map() -@end +function get_syntax(strict) + var syntax_eol_fn = null + var syntax_eos_fn = null + var syntax_op_fn = null + if strict + syntax_eol_fn = []()->syntax.ref("eol") + syntax_eos_fn = []()->syntax.ref("eos") + syntax_op_fn = [](...args)->args + else + syntax_eol_fn = []()->syntax.token("endl") + syntax_eos_fn = []()->syntax.token("endl") + # No op on syntax tree if not strict + syntax_op_fn = [](...args)->{} + end + @begin + var stx = { + # Beginning of Parsing + "begin" : { + syntax.ref("stmts") + }, + # End of Line + "endline" : {syntax.cond_or( + {syntax_eol_fn()}, + {syntax.term(";")} + )}, + # Bootstrap + "stmts" : { + (syntax_op_fn(syntax.repeat(syntax_eos_fn())))..., + syntax.repeat(syntax.nlook(syntax.ref("endblock")), syntax.ref("statement"), syntax.repeat(syntax_eos_fn())) + }, + "decl-stmts" : { + (syntax_op_fn(syntax.repeat(syntax_eos_fn())))..., + syntax.repeat(syntax.nlook(syntax.ref("endblock")), syntax.ref("declaration"), syntax.repeat(syntax_eos_fn())) + }, + "endblock" : {syntax.cond_or( + {syntax.ref("end-stmt")}, + {syntax.ref("else-stmt")}, + {syntax.ref("until-stmt")}, + {syntax.ref("catch-stmt")} + )}, + "statement" : {syntax.cond_or( + {syntax.ref("prep-stmt")}, + {syntax.ref("package-stmt")}, + {syntax.ref("import-stmt")}, + {syntax.ref("var-stmt")}, + {syntax.ref("block-stmt")}, + {syntax.ref("namespace-stmt")}, + {syntax.ref("using-stmt")}, + {syntax.ref("if-stmt")}, + {syntax.ref("switch-stmt")}, + {syntax.ref("while-stmt")}, + {syntax.ref("loop-stmt")}, + {syntax.ref("for-stmt")}, + {syntax.ref("foreach-stmt")}, + {syntax.ref("async-function-stmt")}, + {syntax.ref("control-stmt")}, + {syntax.ref("function-stmt")}, + {syntax.ref("yield-stmt")}, + {syntax.ref("return-stmt")}, + {syntax.ref("try-stmt")}, + {syntax.ref("throw-stmt")}, + {syntax.ref("class-stmt")}, + {syntax.ref("expr-stmt")} + )}, + "declaration" : {syntax.cond_or( + {syntax.ref("prep-stmt")}, + {syntax.ref("namespace-stmt")}, + {syntax.ref("var-stmt")}, + {syntax.ref("using-stmt")}, + {syntax.ref("async-function-stmt")}, + {syntax.ref("function-stmt")}, + {syntax.ref("class-stmt")} + )}, + # Statements + "prep-stmt" : { + syntax.token("prep"), syntax_eol_fn() + }, + "package-stmt" : { + syntax.term("package"), syntax.token("id"), syntax.ref("endline") + }, + "import-stmt" : { + syntax.term("import"), syntax.ref("import-list"), syntax.ref("endline") + }, + "module-list" : { + syntax.token("id"), syntax.optional(syntax.term("."), syntax.cond_or({syntax.term("*")}, {syntax.ref("module-list")})) + }, + "import-list" : { + syntax.ref("module-list"), syntax.optional(syntax.term("as"), syntax.token("id")), syntax.optional(syntax.term(","), syntax.ref("import-list")) + }, + "var-def" : { + syntax.cond_or({syntax.ref("var-bind"), syntax.term("="), (syntax_op_fn(syntax.ref("nl")))..., syntax.ref("basic-expr")}, {syntax.ref("var-list")}) + }, + "var-stmt" : { + syntax.cond_or({syntax.term("var")}, {syntax.term("link")}, {syntax.term("constant")}), syntax.ref("var-def"), syntax.ref("endline") + }, + "var-bind" : { + syntax.term("("), (syntax_op_fn(syntax.ref("nl")))..., syntax.ref("var-bind-list"), syntax.repeat(syntax.term(","), (syntax_op_fn(syntax.ref("nl")))..., syntax.ref("var-bind-list")), (syntax_op_fn(syntax.ref("nl")))..., syntax.term(")") + }, + "var-bind-list" : {syntax.cond_or( + {syntax.token("id")}, + {syntax.term("...")}, + {syntax.ref("var-bind")} + )}, + "var-list" : { + syntax.token("id"), syntax.cond_or( + {syntax.term("="), (syntax_op_fn(syntax.ref("nl")))..., syntax.ref("basic-expr")}, + {syntax.term("as"), syntax.ref("unary-expr"), syntax.optional(syntax.ref("array"))} + ), syntax.optional(syntax.term(","), (syntax_op_fn(syntax.ref("nl")))..., syntax.ref("var-list")) + }, + "block-stmt" : { + syntax.term("block"), syntax_eol_fn(), syntax.ref("stmts"), syntax.term("end"), syntax_eol_fn() + }, + "namespace-stmt" : { + syntax.term("namespace"), syntax.token("id"), syntax_eol_fn(), syntax.ref("decl-stmts"), syntax.term("end"), syntax_eol_fn() + }, + "using-stmt" : { + syntax.term("using"), syntax.ref("using-list"), syntax.ref("endline") + }, + "using-list" : { + syntax.ref("module-list"), syntax.optional(syntax.term(","), syntax.ref("using-list")) + }, + "if-stmt" : { + syntax.term("if"), syntax.ref("basic-expr"), syntax_eol_fn(), syntax.ref("stmts"), syntax.repeat(syntax.ref("else-stmt"), syntax.ref("stmts")), syntax.term("end"), syntax_eol_fn() + }, + "else-stmt" : { + syntax.term("else"), syntax.optional(syntax.nlook(syntax_eol_fn()), syntax.term("if"), syntax.ref("basic-expr")), syntax_eol_fn() + }, + "switch-stmt" : { + syntax.term("switch"), syntax.ref("basic-expr"), syntax_eol_fn(), syntax.ref("switch-stmts"), syntax.term("end"), syntax_eol_fn() + }, + "switch-stmts" : { + (syntax_op_fn(syntax.repeat(syntax_eos_fn())))..., + syntax.repeat(syntax.cond_or({syntax.ref("switch-case")}, {syntax.ref("switch-default")}), syntax.repeat(syntax_eos_fn())) + }, + "switch-case" : { + syntax.term("case"), syntax.ref("logic-or-expr"), syntax_eol_fn(), syntax.ref("stmts"), syntax.term("end"), syntax_eol_fn() + }, + "switch-default" : { + syntax.term("default"), syntax_eol_fn(), syntax.ref("stmts"), syntax.term("end"), syntax_eol_fn() + }, + "while-stmt" : { + syntax.term("while"), syntax.ref("basic-expr"), syntax_eol_fn(), syntax.ref("stmts"), syntax.term("end"), syntax_eol_fn() + }, + "loop-stmt" : { + syntax.term("loop"), syntax_eol_fn(), syntax.ref("stmts"), syntax.cond_or({syntax.ref("until-stmt")}, {syntax.term("end"), syntax_eol_fn()}) + }, + "until-stmt" : { + syntax.term("until"), syntax.ref("basic-expr"), syntax_eol_fn() + }, + "for-stmt" : { + syntax.term("for"), syntax.optional(syntax.ref("var-def")), syntax.cond_or({syntax.term(";")}, {syntax.term(",")}), (syntax_op_fn(syntax.ref("nl")))..., syntax.optional(syntax.ref("basic-expr")), syntax.cond_or({syntax.term(";")}, {syntax.term(",")}), (syntax_op_fn(syntax.ref("nl")))..., syntax.optional(syntax.ref("basic-expr")), syntax.ref("for-body") + }, + "foreach-stmt" : { + syntax.term("foreach"), syntax.optional(syntax.nlook(syntax.term("in")), syntax.token("id")), syntax.term("in"), syntax.ref("basic-expr"), syntax.ref("for-body") + }, + "for-body" : {syntax.cond_or( + {syntax.term("do"), syntax.ref("basic-expr"), syntax.ref("endline")}, + {syntax_eol_fn(), syntax.ref("stmts"), syntax.term("end"), syntax_eol_fn()} + )}, + "function-stmt" : { + syntax.term("function"), syntax.token("id"), syntax.term("("), (syntax_op_fn(syntax.ref("nl")))..., syntax.optional(syntax.ref("argument-list")), (syntax_op_fn(syntax.ref("nl")))..., syntax.term(")"), syntax.optional(syntax.term("override")), syntax.ref("function-body") + }, + "function-body" : {syntax.cond_or( + {syntax.term("{"), syntax.ref("stmts"), syntax.term("}")}, + {syntax_eol_fn(), syntax.ref("stmts"), syntax.term("end"), syntax_eol_fn()} + )}, + "return-stmt" : { + syntax.term("return"), syntax.optional(syntax.nlook(syntax_eol_fn()), syntax.ref("expr")), syntax.ref("endline") + }, + "try-stmt" : { + syntax.term("try"), syntax_eol_fn(), syntax.ref("stmts"), syntax.repeat(syntax.ref("catch-stmt"), syntax.ref("stmts")), syntax.term("end"), syntax_eol_fn() + }, + "catch-stmt" : { + syntax.term("catch"), syntax.token("id"), syntax.optional(syntax.term(":"), syntax.ref("visit-expr")), syntax_eol_fn() + }, + "throw-stmt" : { + syntax.term("throw"), syntax.optional(syntax.nlook(syntax_eol_fn()), syntax.ref("expr")), syntax.ref("endline") + }, + "class-stmt" : { + syntax.cond_or({syntax.term("class")}, {syntax.term("struct")}), syntax.token("id"), syntax.optional(syntax.term("extends"), syntax.ref("visit-expr")), syntax_eol_fn(), + syntax.ref("decl-stmts"), syntax.term("end"), syntax_eol_fn() + }, + "async-function-stmt" : { + syntax.term("async"), syntax.term("function"), syntax.token("id"), + syntax.term("("), (syntax_op_fn(syntax.ref("nl")))..., syntax.optional(syntax.ref("argument-list")), (syntax_op_fn(syntax.ref("nl")))..., syntax.term(")"), + syntax.optional(syntax.term("override")), syntax.ref("function-body") + }, + "yield-stmt" : { + syntax.term("yield"), syntax.optional(syntax.nlook(syntax_eol_fn()), syntax.ref("expr")), syntax.ref("endline") + }, + "control-stmt" : { + syntax.cond_or({syntax.term("break")}, {syntax.term("continue")}), syntax.ref("endline") + }, + "expr-stmt" : { + syntax.ref("expr"), syntax.ref("endline") + }, + "end-stmt" : { + syntax.term("end"), syntax_eol_fn() + }, + # Expression + "expr" : { + syntax.ref("basic-expr"), syntax.optional(syntax.term(","), (syntax_op_fn(syntax.ref("nl")))..., syntax.ref("expr")) + }, + "basic-expr" : {syntax.cond_or( + {syntax.ref("bind-expr"), syntax.term("="), (syntax_op_fn(syntax.ref("nl")))..., syntax.ref("cond-expr")}, + {syntax.ref("cond-expr"), syntax.optional(syntax.ref("asi-op"), (syntax_op_fn(syntax.ref("nl")))..., syntax.ref("basic-expr"))} + )}, + "bind-expr" : { + syntax.term("("), (syntax_op_fn(syntax.ref("nl")))..., syntax.ref("bind-list"), syntax.repeat(syntax.term(","), (syntax_op_fn(syntax.ref("nl")))..., syntax.ref("bind-list")), (syntax_op_fn(syntax.ref("nl")))..., syntax.term(")") + }, + "bind-list" : {syntax.cond_or( + {syntax.token("id")}, + {syntax.term("...")}, + {syntax.ref("bind-expr")} + )}, + "asi-op" : {syntax.cond_or( + {syntax.term("=")}, + {syntax.term(":=")}, + {syntax.term("+=")}, + {syntax.term("-=")}, + {syntax.term("*=")}, + {syntax.term("/=")}, + {syntax.term("%=")}, + {syntax.term("^=")} + )}, + "async-lambda-expr" : { + syntax.term("async"), syntax.term("["), syntax.optional(syntax.ref("capture-list")), syntax.term("]"), + syntax.term("("), (syntax_op_fn(syntax.ref("nl")))..., syntax.optional(syntax.ref("argument-list")), (syntax_op_fn(syntax.ref("nl")))..., syntax.term(")"), + syntax.ref("lambda-body") + }, + "lambda-expr" : { + syntax.term("["), syntax.optional(syntax.ref("capture-list")), syntax.term("]"), syntax.term("("), (syntax_op_fn(syntax.ref("nl")))..., syntax.optional(syntax.ref("argument-list")), (syntax_op_fn(syntax.ref("nl")))..., syntax.term(")"), syntax.ref("lambda-body") + }, + "capture-list" : { + syntax.optional(syntax.term("=")), syntax.token("id"), syntax.repeat(syntax.term(","), (syntax_op_fn(syntax.ref("nl")))..., syntax.ref("capture-list")) + }, + "argument-list" : {syntax.cond_or( + {syntax.term("..."), syntax.token("id")}, + {syntax.optional(syntax.term("=")), syntax.token("id"), syntax.optional(syntax.term(":"), syntax.ref("visit-expr")), syntax.repeat(syntax.term(","), (syntax_op_fn(syntax.ref("nl")))..., syntax.ref("argument-list"))} + )}, + "lambda-body" : {syntax.cond_or( + {syntax.term("{"), syntax.ref("stmts"), syntax.term("}")}, + {syntax.term("->"), syntax.ref("cond-expr")} + )}, + "cond-expr" : {syntax.cond_or( + {syntax.ref("async-lambda-expr")}, + {syntax.ref("lambda-expr")}, + {syntax.ref("logic-or-expr"), syntax.optional(syntax.ref("cond-postfix"))} + )}, + "cond-postfix" : {syntax.cond_or( + {syntax.term("?"), (syntax_op_fn(syntax.ref("nl")))..., syntax.ref("value-expr"), syntax.term(":"), (syntax_op_fn(syntax.ref("nl")))..., syntax.ref("cond-expr")}, + {syntax.term(":"), (syntax_op_fn(syntax.ref("nl")))..., syntax.ref("value-expr")} + )}, + "value-expr" : {syntax.cond_or( + {syntax.ref("async-lambda-expr")}, + {syntax.ref("lambda-expr")}, + {syntax.ref("logic-or-expr")} + )}, + "logic-or-expr" : { + syntax.ref("logic-and-expr"), syntax.optional(syntax.cond_or({syntax.term("||")}, {syntax.term("or")}), (syntax_op_fn(syntax.ref("nl")))..., syntax.ref("logic-or-expr")) + }, + "logic-and-expr" : { + syntax.ref("equal-expr"), syntax.optional(syntax.cond_or({syntax.term("&&")}, {syntax.term("and")}), (syntax_op_fn(syntax.ref("nl")))..., syntax.ref("logic-and-expr")) + }, + "equal-expr" : { + syntax.ref("relat-expr"), syntax.optional(syntax.cond_or({syntax.term("==")}, {syntax.term("!=")}, {syntax.term("is")}, {syntax.term("not")}), (syntax_op_fn(syntax.ref("nl")))..., syntax.ref("equal-expr")) + }, + "relat-expr" : { + syntax.ref("add-expr"), syntax.optional(syntax.cond_or({syntax.term(">")}, {syntax.term("<")}, {syntax.term(">=")}, {syntax.term("<=")}), (syntax_op_fn(syntax.ref("nl")))..., syntax.ref("relat-expr")) + }, + "add-expr" : { + syntax.ref("mul-expr"), syntax.optional(syntax.cond_or({syntax.term("+")}, {syntax.term("-")}), (syntax_op_fn(syntax.ref("nl")))..., syntax.ref("add-expr")) + }, + "mul-expr" : { + syntax.ref("conv-expr"), syntax.optional(syntax.nlook(syntax_eol_fn()), syntax.cond_or({syntax.term("*")}, {syntax.term("/")}, {syntax.term("%")}, {syntax.term("^")}), (syntax_op_fn(syntax.ref("nl")))..., syntax.ref("mul-expr")) + }, + "conv-expr" : { + syntax.ref("unary-expr"), syntax.optional(syntax.nlook(syntax_eol_fn()), syntax.cond_or({syntax.term("=>")}, {syntax.term("as")}), (syntax_op_fn(syntax.ref("nl")))..., syntax.ref("visit-expr")) + }, + "unary-expr" : {syntax.cond_or( + {syntax.ref("unary-op"), syntax.ref("unary-expr")}, + {syntax.cond_or({syntax.term("new")}, {syntax.term("gcnew")}), syntax.ref("visit-expr"), syntax.optional(syntax.ref("array"))}, + {syntax.ref("prim-expr"), syntax.optional(syntax.nlook(syntax_eol_fn()), syntax.ref("postfix-expr"))} + )}, + "unary-op" : {syntax.cond_or( + {syntax.term("typeid")}, + {syntax.term("++")}, + {syntax.term("--")}, + {syntax.term("*")}, + {syntax.term("&")}, + {syntax.term("-")}, + {syntax.term("!")}, + {syntax.term("not")} + )}, + "postfix-expr" : { + syntax.cond_or({syntax.term("++")}, {syntax.term("--")}, {syntax.term("...")}), syntax.optional(syntax.ref("postfix-expr")) + }, + "await-expr" : { + syntax.term("await"), syntax.ref("unary-expr") + }, + "prim-expr" : {syntax.cond_or( + {syntax.ref("await-expr")}, + {syntax.ref("visit-expr")}, + {syntax.ref("constant")} + )}, + "visit-expr" : { + syntax.ref("object"), syntax.optional(syntax.cond_or({syntax.term("->")}, {syntax.term(".")}), (syntax_op_fn(syntax.ref("nl")))..., syntax.ref("visit-expr")) + }, + "object" : {syntax.cond_or( + {syntax.ref("array"), syntax.optional(syntax.ref("index"))}, + {syntax.token("str"), syntax.optional(syntax.ref("index"))}, + {syntax.term("local")}, + {syntax.term("global")}, + {syntax.ref("ecsx-extend")}, + {syntax.ref("element")}, + {syntax.token("char")} + )}, + "ecsx-extend" : { + syntax.token("id"), syntax.nlook(syntax_eol_fn()), syntax.term("::"), syntax.token("id"), syntax.term("("), (syntax_op_fn(syntax.ref("nl")))..., syntax.optional(syntax.ref("basic-expr")), (syntax_op_fn(syntax.ref("nl")))..., syntax.term(")") + }, + "element" : { + syntax.cond_or({syntax.token("id")}, {syntax.term("("), (syntax_op_fn(syntax.ref("nl")))..., syntax.ref("basic-expr"), (syntax_op_fn(syntax.ref("nl")))..., syntax.term(")")}), + syntax.repeat(syntax.nlook(syntax_eol_fn()), syntax.cond_or({syntax.ref("fcall")}, {syntax.ref("index")})) + }, + "constant" : {syntax.cond_or( + {syntax.token("num")}, + {syntax.term("null")}, + {syntax.term("true")}, + {syntax.term("false")} + )}, + "array" : { + syntax.term("{"), (syntax_op_fn(syntax.ref("nl")))..., syntax.optional(syntax.ref("expr")), (syntax_op_fn(syntax.ref("nl")))..., syntax.term("}") + }, + "fcall" : { + syntax.term("("), (syntax_op_fn(syntax.ref("nl")))..., syntax.optional(syntax.ref("expr")), (syntax_op_fn(syntax.ref("nl")))..., syntax.term(")") + }, + "index" : {syntax.cond_or( + {syntax.term("["), syntax.optional(syntax.ref("add-expr")), syntax.optional(syntax.term(":"), syntax.optional(syntax.ref("add-expr")), syntax.optional(syntax.term(":"), syntax.optional(syntax.ref("add-expr")))), syntax.term("]")}, + {syntax.term("["), syntax.term("::"), syntax.term("]")} + )} + }.to_hash_map() + @end + if strict + stx["nl"] = {syntax.repeat(syntax.token("endl"))} + stx["eol"] = {syntax.optional(syntax.token("com")), syntax.token("endl")} + @begin + stx["eos"] = {syntax.cond_or( + {syntax.token("com"), syntax.token("endl")}, + {syntax.token("endl")} + )} + @end + else + # Ignore if not match initiatively + stx["ignore"] = {syntax.repeat(syntax.token("endl"))} + end + return stx +end var grammar = new parsergen.grammar grammar.ext = ".*\\.(csp|csc|ecs|ecsx)" -grammar.lex = get_lexical(regex.build_optimize) -grammar.stx := covscript_syntax +grammar.lex = get_lexical(regex.build_optimize, false) +grammar.stx := get_syntax(false) diff --git a/tests/test_lint.ecs b/tests/test_lint.ecs new file mode 100644 index 0000000..329e199 --- /dev/null +++ b/tests/test_lint.ecs @@ -0,0 +1,41 @@ +# Linter test file - intentionally contains style issues for lint verification +# === Naming convention violations === +function CamelCase() + return 1 +end +var BadName = 2 +constant bad_constant = 3 +class BadClass + var x = 0 +end +# === Unreachable code === +function unreachable_test() + return 1 + var dead_code = 2 +end +# === Empty block === +if true + # empty +end +var trailing = 1 +# === Consecutive blank lines (intentional) === + + + +var a = 1 +var b = 2 +# === Useless expression === +var c = 1 +c +# === Properly formatted code (should pass clean) === +function good_function(x, y) + var result = x + y + foreach item in result + if item > 0 + return item + else + continue + end + end + return result +end diff --git a/unit_tests/test_lint_rules.ecs b/unit_tests/test_lint_rules.ecs new file mode 100644 index 0000000..ad51f72 --- /dev/null +++ b/unit_tests/test_lint_rules.ecs @@ -0,0 +1,129 @@ +# Unit tests: Lint rules automated verification +import ecs_lint + +var passed = 0 +var failed = 0 + +function assert(condition, msg) + if condition + ++passed + else + system.out.println("FAIL: " + msg) + ++failed + end +end + +function assert_eq(actual, expected, msg) + if actual == expected + ++passed + else + system.out.println("FAIL: " + msg + " -- expected " + to_string(expected) + ", got " + to_string(actual)) + ++failed + end +end + +function assert_ge(actual, min_expected, msg) + if actual >= min_expected + ++passed + else + system.out.println("FAIL: " + msg + " -- expected >= " + to_string(min_expected) + ", got " + to_string(actual)) + ++failed + end +end + +var result = ecs_lint.lint_file("tests/test_lint.ecs") +assert(result != null, "lint_file returns non-null result") +if result == null + system.out.println("Test: lint rules -- " + to_string(passed) + " passed, " + to_string(failed) + " failed") + if failed > 0 + system.exit(1) + end + return +end + +var lint = result[0] +var issues = result[1] + +assert(typeid lint == typeid ecs_lint.linter, "result[0] is a linter") +assert(typeid issues == typeid array, "result[1] is an array (issues)") + +var function_naming_count = 0 +var variable_naming_count = 0 +var constant_naming_count = 0 +var class_naming_count = 0 +var unreachable_code_count = 0 +var empty_block_count = 0 +var trailing_whitespace_count = 0 +var consecutive_blank_lines_count = 0 +var indentation_count = 0 +var useless_expression_count = 0 +var error_count = 0 +var warning_count = 0 +var info_count = 0 + +foreach iss in issues + if iss.rule_name == "function_naming" + ++function_naming_count + end + if iss.rule_name == "variable_naming" + ++variable_naming_count + end + if iss.rule_name == "constant_naming" + ++constant_naming_count + end + if iss.rule_name == "class_naming" + ++class_naming_count + end + if iss.rule_name == "unreachable_code" + ++unreachable_code_count + end + if iss.rule_name == "empty_block" + ++empty_block_count + end + if iss.rule_name == "trailing_whitespace" + ++trailing_whitespace_count + end + if iss.rule_name == "consecutive_blank_lines" + ++consecutive_blank_lines_count + end + if iss.rule_name == "indentation_consistency" + ++indentation_count + end + if iss.rule_name == "useless_expression" + ++useless_expression_count + end + if iss.severity == ecs_lint.SEVERITY_ERROR + ++error_count + end + if iss.severity == ecs_lint.SEVERITY_WARNING + ++warning_count + end + if iss.severity == ecs_lint.SEVERITY_INFO + ++info_count + end +end + +assert_ge(function_naming_count, 1, "detects function_naming violations") +assert_ge(variable_naming_count, 1, "detects variable_naming violations") +assert_ge(constant_naming_count, 1, "detects constant_naming violations") +assert_ge(class_naming_count, 1, "detects class_naming violations") +assert_ge(unreachable_code_count, 1, "detects unreachable_code errors") +assert_ge(empty_block_count, 1, "detects empty_block warnings") +assert_ge(trailing_whitespace_count, 1, "detects trailing_whitespace warnings") +assert_ge(consecutive_blank_lines_count, 1, "detects consecutive_blank_lines info") +assert_eq(indentation_count, 0, "no indentation_consistency issues in test file") +assert_ge(useless_expression_count, 1, "detects useless_expression warnings") + +assert_eq(lint.count_by_severity(ecs_lint.SEVERITY_ERROR), error_count, "count_by_severity(error) matches") +assert_eq(lint.count_by_severity(ecs_lint.SEVERITY_WARNING), warning_count, "count_by_severity(warning) matches") +assert_eq(lint.count_by_severity(ecs_lint.SEVERITY_INFO), info_count, "count_by_severity(info) matches") + +assert_ge(error_count, 1, "has at least one severity_error") +assert_ge(warning_count, 6, "has at least six severity_warning") +assert_ge(issues.size, 7, "total issues >= 7") + +system.out.println("") +system.out.println("Test: lint rules -- " + to_string(passed) + " passed, " + to_string(failed) + " failed") +if failed > 0 + system.exit(1) +end diff --git a/unit_tests/test_parser_modes.ecs b/unit_tests/test_parser_modes.ecs new file mode 100644 index 0000000..714501c --- /dev/null +++ b/unit_tests/test_parser_modes.ecs @@ -0,0 +1,88 @@ +# Unit tests: Parser strict/non-strict mode coverage +import ecs +import ecs_parser, parsergen, regex + +var passed = 0 +var failed = 0 + +function assert(condition, msg) + if condition + ++passed + else + system.out.println("FAIL: " + msg) + ++failed + end +end + +function key_exists(map, key) + try + var val = map.at(key) + return true + catch e + return false + end +end + +# === Lexical returns valid hash_maps === +var lex_ns = ecs_parser.get_lexical(regex.build_optimize, false) +assert(typeid lex_ns == typeid hash_map, "non-strict lex is hash_map") + +var lex_s = ecs_parser.get_lexical(regex.build_optimize, true) +assert(typeid lex_s == typeid hash_map, "strict lex is hash_map") + +# Non-strict: "ign" must exist, "com" must not +assert(key_exists(lex_ns, "ign"), "non-strict lex has 'ign'") +assert(!key_exists(lex_ns, "com"), "non-strict lex does not have 'com'") + +# Strict: both "ign" and "com" must exist +assert(key_exists(lex_s, "ign"), "strict lex has 'ign'") +assert(key_exists(lex_s, "com"), "strict lex has 'com'") + +# === Syntax returns valid hash_maps === +var stx_ns = ecs_parser.get_syntax(false) +assert(typeid stx_ns == typeid hash_map, "non-strict stx is hash_map") + +var stx_s = ecs_parser.get_syntax(true) +assert(typeid stx_s == typeid hash_map, "strict stx is hash_map") + +# Non-strict: must have "ignore" and main rules +assert(key_exists(stx_ns, "ignore"), "non-strict stx has 'ignore'") +assert(key_exists(stx_ns, "begin"), "non-strict stx has 'begin'") +assert(key_exists(stx_ns, "stmts"), "non-strict stx has 'stmts'") +assert(key_exists(stx_ns, "statement"), "non-strict stx has 'statement'") + +# Strict: must have "nl", "eol", "eos" and main rules +assert(key_exists(stx_s, "nl"), "strict stx has 'nl'") +assert(key_exists(stx_s, "eol"), "strict stx has 'eol'") +assert(key_exists(stx_s, "eos"), "strict stx has 'eos'") +assert(key_exists(stx_s, "begin"), "strict stx has 'begin'") +assert(key_exists(stx_s, "stmts"), "strict stx has 'stmts'") + +# === Parse with non-strict grammar (default) === +var grammar_ns = new parsergen.grammar +grammar_ns.ext = ".*\\.(csp|csc|ecs|ecsx)" +grammar_ns.lex = ecs_parser.get_lexical(regex.build_optimize, false) +grammar_ns.stx := ecs_parser.get_syntax(false) + +var parser_ns = new parsergen.generator +parser_ns.add_grammar("ecs-lang-ns", grammar_ns) +parser_ns.from_file("unit_tests/test_functions.ecs") +assert(parser_ns.ast != null, "non-strict mode parses test_functions.ecs") + +# === Parse with strict grammar === +var grammar_s = new parsergen.grammar +grammar_s.ext = ".*\\.(csp|csc|ecs|ecsx)" +grammar_s.lex = ecs_parser.get_lexical(regex.build_optimize, true) +grammar_s.stx := ecs_parser.get_syntax(true) + +var parser_s = new parsergen.generator +parser_s.add_grammar("ecs-lang-s", grammar_s) +parser_s.from_file("unit_tests/test_functions.ecs") +assert(parser_s.ast != null, "strict mode parses test_functions.ecs") + +# === results === +system.out.println("") +system.out.println("Test: parser modes -- " + to_string(passed) + " passed, " + to_string(failed) + " failed") +if failed > 0 + system.exit(1) +end