From 62ab65125075b727358e92ce8b7e308c13c2e065 Mon Sep 17 00:00:00 2001 From: syldium Date: Sat, 29 Jul 2023 16:39:20 +0200 Subject: [PATCH] Initial commit --- .gitignore | 11 +++ README.md | 3 + corpus/commands.txt | 112 +++++++++++++++++++++++ corpus/control_flow.txt | 138 ++++++++++++++++++++++++++++ grammar.js | 194 ++++++++++++++++++++++++++++++++++++++++ package.json | 32 +++++++ queries/highlights.scm | 73 +++++++++++++++ 7 files changed, 563 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 corpus/commands.txt create mode 100644 corpus/control_flow.txt create mode 100644 grammar.js create mode 100644 package.json create mode 100644 queries/highlights.scm diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bda6ad6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +Cargo.toml +Cargo.lock +node_modules +build +package-lock.json +pnpm-lock.yaml +/bindings/ +/src/ +/target/ +.build/ +binding.gyp diff --git a/README.md b/README.md new file mode 100644 index 0000000..585299e --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# tree-sitter-moshell + +Moshell grammar subset for [tree-sitter](https://github.com/tree-sitter/tree-sitter). diff --git a/corpus/commands.txt b/corpus/commands.txt new file mode 100644 index 0000000..ef20fcc --- /dev/null +++ b/corpus/commands.txt @@ -0,0 +1,112 @@ +=============================== +Command +=============================== + +whoami + +--- + +(source_file + (command (command_name (identifier)))) + +=============================== +Command without trailing new line +=============================== + +echo hello +--- + +(source_file (command (command_name (identifier)) (word))) + +=============================== +Variable expansion +=============================== + +cat $FOO + +--- + +(source_file + (command + (command_name (identifier)) + (expansion (variable (identifier))))) + +=============================== +Redirection +=============================== + +cat file 2> /dev/null +echo message >&2 +python <<< 'print("hello")' + +--- + +(source_file + (command + (command_name + (identifier)) + (word) + (word) + (redirection + (word))) + (command + (command_name + (identifier)) + (word) + (redirection + (word))) + (command + (command_name + (identifier)) + (redirection + (raw_string)))) + +=============================== +Pipeline +=============================== + +cat path/to/file.txt | wc -l + +--- + +(source_file + (pipeline + (command + (command_name + (identifier)) + (word)) + (command + (command_name + (identifier)) + (word)))) + +=============================== +String interpolation +=============================== + +echo "The result is $result" + +--- + +(source_file + (command + (command_name + (identifier)) + (template_string + (expansion + (variable + (identifier)))))) + +=============================== +Negated command +=============================== + +! grep foo + +--- + +(source_file + (command + (command_name + (identifier)) + (word))) diff --git a/corpus/control_flow.txt b/corpus/control_flow.txt new file mode 100644 index 0000000..59952b9 --- /dev/null +++ b/corpus/control_flow.txt @@ -0,0 +1,138 @@ +=============================== +If expression +=============================== + +if grep found file { + echo present +} else { + echo absent +} + +--- + +(source_file + (if + (command + (command_name + (identifier)) + (word) + (word)) + (block + (command + (command_name + (identifier)) + (word))) + (block + (command + (command_name + (identifier)) + (word))))) + +=============================== +While statement +=============================== + +val i = 0 +while $i < 10 { + i += 1 +} + +--- + +(source_file + (variable_declaration + (identifier) + (primary + (number))) + (while + (infix_expression + (expansion + (variable + (identifier))) + (primary + (number))) + (block + (assignation + (identifier) + (primary + (number)))))) + +=============================== +Function declaration +=============================== + +fun square(n: Int) -> Int = $n * $n + +--- + +(source_file + (function_declaration + (identifier) + (parameter_list + (function_parameter + (identifier) + (type_identifier + (identifier)))) + (type_identifier + (identifier)) + (infix_expression + (expansion + (variable + (identifier))) + (expansion + (variable + (identifier)))))) + +=============================== +Implicit return +=============================== + +fun fibonacci(n: Int) -> Int = + if $n <= 1 { + 1 + } else { + fibonacci($n - 1) + fibonacci($n - 2) + } + +--- + +(source_file + (function_declaration + (identifier) + (parameter_list + (function_parameter + (identifier) + (type_identifier + (identifier)))) + (type_identifier + (identifier)) + (if + (infix_expression + (expansion + (variable + (identifier))) + (primary + (number))) + (block + (primary + (number))) + (block + (infix_expression + (call_expression + (identifier) + (argument_list + (infix_expression + (expansion + (variable + (identifier))) + (primary + (number))))) + (call_expression + (identifier) + (argument_list + (infix_expression + (expansion + (variable + (identifier))) + (primary + (number)))))))))) diff --git a/grammar.js b/grammar.js new file mode 100644 index 0000000..a0ae8e2 --- /dev/null +++ b/grammar.js @@ -0,0 +1,194 @@ +const commaSep = (rule) => seq(rule, repeat(seq(',', rule))); + +module.exports = grammar({ + name: 'moshell', + extras: $ => [ + /\s/, + /\\\r?\n/, + $.line_comment, + $.block_comment, + ], + rules: { + source_file: $ => optional($._declarations), + + _declarations: $ => seq( + repeat(seq( + $._declaration, + $._terminator + )), + $._declaration, + optional($._terminator) + ), + _declaration: $ => choice( + $.function_declaration, + $.use_declaration, + $.variable_declaration, + $._statement, + $.return, + $.while, + 'break', + 'continue', + ), + function_declaration: $ => seq( + 'fun', + field('name', $.identifier), + field('parameters', $.parameter_list), + optional(seq( + '->', + field('return_type', $.type_identifier) + )), + '=', + $._expression + ), + parameter_list: $ => seq( + '(', + optional(commaSep($.function_parameter)), + ')' + ), + function_parameter: $ => seq( + $.identifier, + ':', + $.type_identifier + ), + use_declaration: $ => seq( + 'use', + $._use_clause, + ), + _use_clause: $ => seq( + $.identifier, + repeat(seq('::', $.identifier)), + optional(seq('as', $.identifier)), + ), + variable_declaration: $ => seq( + choice('var', 'val'), + $.identifier, + optional(seq(':', $.type_identifier)), + optional(seq('=', $._expression)) + ), + command: $ => seq( + $.command_name, + repeat($._argument), + repeat($.redirection), + ), + + _statement: $ => choice( + $.command, + $.pipeline, + seq('!', $.command), + $._expression, + ), + while: $ => seq( + 'while', + $._statement, + $.block + ), + return: $ => seq( + 'return', + optional($._expression) + ), + + _expression: $ => choice( + $.block, + $.if, + $.call_expression, + $.assignation, + $.infix_expression, + $.prefix_expression, + $.expansion, + $.primary + ), + block: $ => seq( + '{', + optional($._declarations), + '}' + ), + if: $ => seq( + 'if', + field('condition', $._statement), + field('then', $.block), + optional(seq( + 'else', + field('else', $.block) + )) + ), + call_expression: $ => seq( + field('name', $.identifier), + field('arguments', $.argument_list), + ), + argument_list: $ => seq( + '(', + optional($._expression), + repeat(seq(',', $._expression)), + ')' + ), + assignation: $ => prec.right(seq( + choice($.identifier, $._expression), + choice('=', '+=', '-=', '*=', '/=', '%='), + $._expression + )), + cast_expression: $ => prec.left(seq( + field('value', $._expression), + 'as', + field('type', $.type_identifier) + )), + infix_expression: $ => prec.left(seq( + $._expression, + choice('+', '-', '*', '/', '%', '==', '!=', '<', '>', '<=', '>=', '&&', '||', 'in'), + $._expression + )), + prefix_expression: $ => prec.right(seq( + choice('!', '-'), + $._expression + )), + + expansion: $ => choice( + $.variable, + $.command_substitution + ), + pipeline: $ => prec.left(seq( + $._statement, + '|', + $._statement + )), + redirection: $ => seq( + field('descriptor', optional($.number)), + choice('<', '>', '>>', '<<<', '&>', '&>>', '<&', '>&'), + field('destination', $._argument) + ), + command_substitution: $ => seq('$(', $._declaration, ')'), + + command_name: $ => $.identifier, + _argument: $ => choice( + $.raw_string, + $.template_string, + $.expansion, + $.word + ), + + primary: $ => choice( + $.number, + $.boolean, + $.raw_string, + $.template_string, + ), + line_comment: $ => token(seq('//', /.*/)), + block_comment: $ => token(seq('/*', /[^*]*\*+([^/*][^*]*\*+)*/, '/')), + identifier: $ => /[a-zA-Z_][a-zA-Z0-9_]*/, + word: $ => /[^#'"\\\s$<>{}&;]+/, + number: $ => /\d+/, + boolean: $ => choice('true', 'false'), + template_string: $ => seq( + '"', + repeat(choice($._string_content, $.expansion)), + '"' + ), + raw_string: $ => /'[^']*'/, + _string_content: $ => token(prec(-1, /([^"`$\\]|\\(.|\r?\n))+/)), + _terminator: $ => choice(';', '\n'), + type_identifier: $ => $.identifier, + variable: $ => seq( + '$', + $.identifier + ), + } +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000..aeaf91b --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "tree-sitter-moshell", + "version": "0.1.0", + "description": "C grammar for node-tree-sitter", + "main": "bindings/node", + "keywords": [ + "parser", + "lexer" + ], + "repository": { + "type": "git", + "url": "https://github.com/moshell-lang/tree-sitter-moshell.git" + }, + "scripts": { + "test": "tree-sitter test" + }, + "license": "GPL-3.0-only", + "dependencies": { + "nan": "^2.17.0" + }, + "devDependencies": { + "tree-sitter-cli": "^0.20.8" + }, + "tree-sitter": [ + { + "scope": "source.moshell", + "file-types": [ + "msh" + ] + } + ] +} diff --git a/queries/highlights.scm b/queries/highlights.scm new file mode 100644 index 0000000..88f3a06 --- /dev/null +++ b/queries/highlights.scm @@ -0,0 +1,73 @@ +; Literals + +(raw_string) @string +(template_string) @string +(number) @number +(command_name) @function.macro +(expansion) @embedded + +; Functions + +(function_declaration + name: (identifier) @function) +(function_parameter (identifier) @variable.parameter) +(call_expression + name: (identifier) @function) + +(line_comment) @comment +(block_comment) @comment + +; Punctuation and operators + +"(" @punctuation.bracket +")" @punctuation.bracket +"{" @punctuation.bracket +"}" @punctuation.bracket +":" @punctuation.delimiter +"::" @punctuation.delimiter +"," @punctuation.delimiter +";" @punctuation.delimiter + +[ + "+" + "-" + "*" + "/" + "%" + "=" + "+=" + "-=" + "*=" + "/=" + "%=" + "<" + "<=" + ">" + ">=" + "&&" + "||" + "!" + "|" +] @operator + +; Identifiers + +(type_identifier) @type +[ + "as" + "break" + "continue" + "else" + "fun" + "if" + "in" + "return" + "use" + "val" + "var" + "while" +] @keyword +[ + "true" + "false" +] @constant.builtin