diff --git a/tools/common/tree-sitter-hyperscript/README.md b/tools/common/tree-sitter-hyperscript/README.md new file mode 100644 index 000000000..0ec1fc085 --- /dev/null +++ b/tools/common/tree-sitter-hyperscript/README.md @@ -0,0 +1,9 @@ +# Tree-sitter + +## TODO + +If anybody wants to help out, here are some things that can be done: + +- [ ] Make the grammar smaller (use `parser.c` size as a metric) +- [ ] Review and improve tests (especially for the new version and edge cases) +- [ ] Update the grammar to the latest version (if there are any changes) diff --git a/tools/common/tree-sitter-hyperscript/grammar.js b/tools/common/tree-sitter-hyperscript/grammar.js index 96145eac4..85e80c03c 100644 --- a/tools/common/tree-sitter-hyperscript/grammar.js +++ b/tools/common/tree-sitter-hyperscript/grammar.js @@ -1,122 +1,1118 @@ /** - * Tree-sitter grammar for _hyperscript. - * - * Highlighting-focused: identifies token types for correct coloring - * without trying to fully parse the language structure. - * The real parser (IIFE via LSP/GraalVM) handles structural parsing. + * @file A scripting language for the web + * @author tanomartinoli + * @license MIT */ + +/// +// @ts-check + +const PREC = { + OR: 1, + COMPARE: 2, + MATH: 3, + UNARY: 4, + MEMBER: 5, + POSTFIX: 6, +}; + module.exports = grammar({ - name: 'hyperscript', - - extras: $ => [/\s/], - - rules: { - program: $ => repeat($._token), - - _token: $ => choice( - $.comment, - $.string, - $.template_string, - $.number, - $.boolean, - $.css_class, - $.css_id, - $.css_selector, - $.attribute_ref, - $.style_ref, - $.feature_keyword, - $.command_keyword, - $.control_keyword, - $.modifier, - $.type_name, - $.operator, - $.punctuation, - $.identifier, - ), - - // Comments - comment: $ => choice( - seq('--', /[^\n]*/), - seq('/*', /[^*]*\*+([^/*][^*]*\*+)*/, '/'), - ), - - // Strings - string: $ => choice( - seq("'", optional(/[^'\\]*(\\.?[^'\\]*)*/), "'"), - seq('"', optional(/[^"\\]*(\\.?[^"\\]*)*/), '"'), - ), - - template_string: $ => seq('`', repeat(choice( - /[^`\\$]+/, - /\\./, - seq('${', /[^}]*/, '}'), - seq('$', /[a-zA-Z_]\w*/), - )), '`'), - - // Numbers (with optional time suffixes) - number: $ => /\d+(\.\d+)?(ms|s|milliseconds|seconds)?/, - - // Booleans and null - boolean: $ => choice('true', 'false', 'null'), - - // CSS references - css_class: $ => token(seq('.', /[a-zA-Z][a-zA-Z0-9_\-$]*/)), - css_id: $ => token(seq('#', /[a-zA-Z][a-zA-Z0-9_\-$]*/)), - css_selector: $ => token(seq('<', /[a-zA-Z][^>]*/, '/>')), - attribute_ref: $ => token(seq('@', /[a-zA-Z][a-zA-Z0-9_\-]*/)), - style_ref: $ => token(seq('*', /[a-zA-Z][a-zA-Z0-9_\-]*/)), - - // Feature keywords - feature_keyword: $ => choice( - 'on', 'def', 'init', 'behavior', 'install', 'js', 'worker', - 'eventsource', 'socket', 'catch', - ), - - // Command keywords - command_keyword: $ => choice( - 'add', 'remove', 'toggle', 'set', 'get', 'put', 'call', - 'send', 'trigger', 'take', 'log', 'return', 'throw', - 'fetch', 'go', 'hide', 'show', 'wait', 'halt', 'exit', - 'tell', 'transition', 'settle', 'make', 'append', 'pick', - 'default', 'increment', 'decrement', 'measure', 'focus', - 'blur', 'swap', 'morph', 'open', 'close', 'render', - 'empty', 'answer', 'ask', 'speak', 'select', 'scroll', 'beep', - ), - - // Control flow keywords - control_keyword: $ => choice( - 'if', 'else', 'otherwise', 'then', 'end', - 'repeat', 'for', 'while', 'until', - 'break', 'continue', 'async', - ), - - // Modifiers (contextual keywords) - modifier: $ => choice( - 'from', 'in', 'to', 'with', 'over', 'into', 'before', 'after', - 'at', 'is', 'am', 'as', 'and', 'or', 'not', 'no', 'of', 'the', - 'closest', 'first', 'last', 'next', 'previous', 'random', - 'when', 'where', 'unless', 'between', 'forever', 'every', - 'queue', 'debounced', 'throttled', 'elsewhere', 'called', - 'its', 'my', 'me', 'it', 'I', 'result', 'you', 'your', 'yourself', - 'by', - ), - - // Built-in type names - type_name: $ => choice( - 'String', 'Number', 'Int', 'Float', 'Date', 'Array', - 'HTML', 'Fragment', 'JSON', 'Object', 'Values', 'Boolean', 'Fixed', - ), - - // Operators - operator: $ => choice( - '+', '-', '/', '%', '=', '==', '===', '!=', '!==', - '<=', '>=', '..', '->', '|', '!', '\\', - ), - - // Punctuation - punctuation: $ => choice('(', ')', '{', '}', '[', ']', ',', ':', ';', '?', '&'), - - // Identifiers (catch-all for unrecognized words) - identifier: $ => /[a-zA-Z_$][a-zA-Z0-9_$]*/, - }, + name: "hyperscript", + + externals: ($) => [$.query_literal, $._lt, $._lte, $.class_literal, $.js_body, $._transition_over, $._transition_using, $._feature_on, $._feature_init, $._template_chars, $._attr_query_open, $._possessive_s, $._single_string, $._show_when], + + extras: ($) => [/[ \t\r\f]+/, $.comment], + + word: ($) => $.identifier, + + supertypes: ($) => [$.feature, $.command, $.literal, $.dom_literal], + + conflicts: ($) => [ + [$.event_predicate, $.special_identifier], + [$.command_sequence], + ], + + rules: { + source_file: ($) => + seq( + repeat($._sep), + optional(choice(prec(1, $.feature_script), $.command_script)), + ), + + feature_script: ($) => $.feature_sequence, + command_script: ($) => $.command_sequence_top, + + _sep: (_) => token(/(?:[;\n][ \t\r\f]*)+/), + + _cmd_sep: ($) => choice("then", $._sep), + + feature_sequence: ($) => + prec.right( + seq( + $.feature, + repeat( + choice( + alias($._behavior_on_feature, $.on_feature), + alias($._behavior_init_feature, $.init_feature), + seq($._sep, $.feature), + ), + ), + optional($._sep), + ), + ), + + command_sequence_top: ($) => + prec.right( + seq( + $.command_statement, + repeat(seq(repeat1($._cmd_sep), $.command_statement)), + optional($._cmd_sep), + ), + ), + + feature: ($) => + choice( + $.on_feature, + $.init_feature, + $.def_feature, + $.behavior_feature, + $.eventsource_feature, + $.socket_feature, + $.worker_feature, + ), + + on_feature: ($) => + prec.right( + seq( + choice("on", alias($._feature_on, "on")), + field("events", $.event_spec), + field("body", $.feature_body), + optional(field("catch", $.catch_block)), + optional(field("finally", $.finally_block)), + optional("end"), + ), + ), + + init_feature: ($) => + prec.right( + seq( + choice("init", alias($._feature_init, "init")), + optional("immediately"), + field("body", $.feature_body), + optional("end"), + ), + ), + + feature_body: ($) => seq(repeat($._cmd_sep), $.command_sequence), + + def_feature: ($) => + seq( + "def", + field("name", choice($.dotted_identifier, $.identifier)), + optional(field("params", $.param_list)), + field("body", $.feature_body), + optional(field("catch", $.catch_block)), + optional(field("finally", $.finally_block)), + "end", + ), + + catch_block: ($) => + seq( + "catch", + field("error", $.identifier), + field("body", $.feature_body), + ), + + finally_block: ($) => + seq( + "finally", + field("body", $.feature_body), + ), + + behavior_feature: ($) => + seq( + "behavior", + field("name", $.identifier), + optional(field("params", $.param_list)), + field("body", $.behavior_body), + optional($._sep), + "end", + ), + + behavior_body: ($) => + repeat1( + choice( + alias($._behavior_on_feature, $.on_feature), + alias($._behavior_init_feature, $.init_feature), + ), + ), + + _behavior_on_feature: ($) => + prec.right( + seq( + alias($._feature_on, "on"), + field("events", $.event_spec), + field("body", $.feature_body), + optional(field("catch", $.catch_block)), + optional(field("finally", $.finally_block)), + optional("end"), + ), + ), + + _behavior_init_feature: ($) => + prec.right( + seq( + alias($._feature_init, "init"), + optional("immediately"), + field("body", $.feature_body), + optional("end"), + ), + ), + + eventsource_feature: ($) => + seq( + "eventsource", + field("name", $.identifier), + optional( + seq("from", field("url", choice($.naked_string, $.string, $.identifier))), + ), + repeat(field("handler", $.event_handler)), + optional($._sep), + "end", + ), + + socket_feature: ($) => + seq( + "socket", + field("name", $.identifier), + field("url", choice($.naked_string, $.string, $.identifier)), + optional( + seq("with", "timeout", field("timeout", $.time_literal)), + ), + repeat(field("handler", $.event_handler)), + optional($._sep), + "end", + ), + + event_handler: ($) => + prec.right( + seq( + alias($._feature_on, "on"), + field("event", $.event_name), + optional(seq("as", field("encoding", $.identifier))), + field("body", $.feature_body), + optional("end"), + ), + ), + + worker_feature: ($) => + seq( + "worker", + field("name", $.identifier), + optional(field("scripts", $.argument_list)), + repeat( + seq(repeat1($._sep), field("member", choice($.def_feature, $.js_command))), + ), + optional($._sep), + "end", + ), + + command_sequence: ($) => + prec.right( + seq( + $.command_statement, + repeat(seq(repeat1($._cmd_sep), $.command_statement)), + optional($._cmd_sep), + ), + ), + + command_statement: ($) => + prec.right( + seq( + choice($.async_command, $.if_statement, $.repeat_statement, $.command), + optional(seq("unless", field("unless", $.expression))), + ), + ), + + async_command: ($) => + seq("async", choice($.if_statement, $.repeat_statement, $.command)), + + event_predicate: ($) => + seq( + "event", + field("event", $._postfix), + optional(seq("from", field("source", $.expression))), + ), + + event_spec: ($) => + seq( + field("event", $.event_binding), + repeat(seq("or", field("event", $.event_binding))), + optional( + seq("queue", field("queue", choice("all", "first", "last", "none"))), + ), + ), + + event_binding: ($) => + seq( + optional("every"), + field("name", $.event_name), + optional(field("params", $.param_list)), + optional(field("filter", $.filter_clause)), + optional(seq("of", field("of", $.dom_literal))), + optional(field("count", $.count_filter)), + optional(seq("from", field("source", $.expression))), + optional(seq("in", field("in", $.expression))), + optional(field("rate_limit", $.rate_limit)), + optional(field("having", $.having_clause)), + ), + + event_name: ($) => + choice($.dotted_identifier, $.colon_identifier, $.identifier, $.string), + + colon_identifier: ($) => + seq($.identifier, repeat1(seq(":", $.identifier))), + + dotted_identifier: ($) => + seq($.identifier, repeat1(seq(".", $.identifier))), + + param_list: ($) => + seq( + "(", + optional(seq($.identifier, repeat(seq(",", $.identifier)))), + ")", + ), + + filter_clause: ($) => seq("[", $.expression, "]"), + + count_filter: ($) => + choice( + $.number, + seq($.number, "to", $.number), + seq($.number, "and", "on"), + ), + + rate_limit: ($) => + seq(choice("debounced", "throttled"), "at", $.duration), + + having_clause: ($) => + seq( + "having", + field("property", $.identifier), + field("value", $.expression), + ), + + if_statement: ($) => + prec.right( + seq( + "if", + field("condition", $.expression), + repeat($._cmd_sep), + field("consequence", $.command_sequence), + repeat(field("else_if", $.else_if_clause)), + optional(field("alternative", $.else_clause)), + optional("end"), + ), + ), + + else_if_clause: ($) => + prec(1, + seq( + "else", + "if", + field("condition", $.expression), + repeat($._cmd_sep), + field("consequence", $.command_sequence), + ), + ), + + else_clause: ($) => + seq( + choice("else", "otherwise"), + repeat($._cmd_sep), + field("alternative", $.command_sequence), + ), + + repeat_statement: ($) => + choice( + seq( + "repeat", + optional(field("clause", $.repeat_clause)), + repeat1($._cmd_sep), + field("body", $.command_sequence), + "end", + ), + seq( + "for", + field("variable", $.identifier), + "in", + field("iterable", $.expression), + optional(field("index", $.repeat_index)), + repeat1($._cmd_sep), + field("body", $.command_sequence), + "end", + ), + ), + + repeat_clause: ($) => + choice( + seq("forever", optional(field("index", $.repeat_index))), + seq( + "while", + field("condition", choice($.event_predicate, $.expression)), + optional(field("index", $.repeat_index)), + ), + seq( + "until", + field("condition", choice($.event_predicate, $.expression)), + optional(field("index", $.repeat_index)), + ), + seq( + "for", + field("variable", $.identifier), + "in", + field("iterable", $.expression), + optional(field("index", $.repeat_index)), + ), + seq( + "in", + field("iterable", $.expression), + optional(field("index", $.repeat_index)), + ), + seq( + field("times", $.number), + "times", + optional(field("index", $.repeat_index)), + ), + ), + + repeat_index: ($) => seq("index", field("name", $.identifier)), + + command: ($) => + choice( + $.js_command, + $.set_command, + $.put_command, + $.add_command, + $.remove_command, + $.toggle_command, + $.tell_command, + $.hide_command, + $.show_command, + $.make_command, + $.call_command, + $.settle_command, + $.wait_command, + $.send_command, + $.return_command, + $.throw_command, + $.halt_command, + $.break_command, + $.continue_command, + $.transition_command, + $.fetch_command, + $.log_command, + $.beep_command, + $.install_command, + $.invocation_command, + $.generic_command, + ), + + beep_command: ($) => + seq("beep", token.immediate("!"), optional(field("value", $.expression))), + + install_command: ($) => + seq( + "install", + field("behavior", choice($.dotted_identifier, $.identifier)), + optional(field("args", $.named_argument_list)), + ), + + named_argument_list: ($) => + seq( + "(", + optional( + seq( + $.named_argument, + repeat(seq(",", $.named_argument)), + ), + ), + ")", + ), + + named_argument: ($) => + seq( + field("name", $.identifier), + ":", + field("value", $.expression), + ), + + js_command: ($) => + seq( + "js", + optional(field("params", $.param_list)), + field("body", $.js_body), + "end", + ), + + set_command: ($) => + choice( + seq( + "set", + optional($.scope_modifier), + field("target", $.expression), + "to", + field("value", $.expression), + ), + seq( + "set", + field("value", $.object_literal), + "on", + field("target", $.expression), + ), + ), + + scope_modifier: (_) => choice("global", "element", "local"), + + put_command: ($) => + prec.right( + PREC.POSTFIX + 1, + seq( + "put", + field("value", $.expression), + field("position", choice( + "into", + "in", + "before", + "after", + seq("at", optional("the"), "start", "of"), + seq("at", optional("the"), "end", "of"), + )), + field("target", $.expression), + ), + ), + + _dom_list_item: ($) => + choice( + $.class_literal, + $.id_literal, + $.attribute_literal, + $.query_literal, + $.attribute_query_literal, + $.style_property, + ), + + add_command: ($) => + seq( + "add", + field("what", $.expression), + repeat(field("what", $._dom_list_item)), + optional(seq("to", field("target", $.expression))), + optional(seq("where", field("where", $.expression))), + ), + + remove_command: ($) => + seq( + "remove", + field("what", $.expression), + repeat(field("what", $._dom_list_item)), + optional(seq("from", field("target", $.expression))), + ), + + toggle_command: ($) => + seq( + "toggle", + choice( + seq("between", field("from", $._compare), "and", field("to", $._compare)), + field("what", $.expression), + ), + optional(seq(choice("on", "in"), field("target", $.expression))), + optional(choice( + seq("for", field("duration", $.time_literal)), + seq("until", field("until_event", $.event_name), + optional(seq("from", field("until_source", $.expression)))), + )), + ), + + hide_command: ($) => seq("hide", optional($._visibility_command_body)), + show_command: ($) => seq("show", optional($._visibility_command_body)), + + _visibility_command_body: ($) => + choice( + seq( + field("target", $.expression), + optional(seq("in", field("in", $.expression))), + optional(seq(alias($._show_when, "when"), field("when", $.expression))), + optional(seq("with", field("strategy", $.expression))), + ), + seq( + seq("in", field("in", $.expression)), + optional(seq(alias($._show_when, "when"), field("when", $.expression))), + optional(seq("with", field("strategy", $.expression))), + ), + seq( + seq(alias($._show_when, "when"), field("when", $.expression)), + optional(seq("with", field("strategy", $.expression))), + ), + seq("with", field("strategy", $.expression)), + ), + + make_command: ($) => + seq( + "make", + optional(choice("a", "an")), + field("type", choice($.identifier, $.query_literal)), + optional( + seq( + choice("from", "with"), + field("args", seq($.expression, repeat(seq(",", $.expression)))), + ), + ), + optional(seq("called", field("name", $.identifier))), + ), + + call_command: ($) => seq(choice("call", "get"), field("fn", $.expression)), + + tell_command: ($) => + prec.right( + seq( + "tell", + field("target", $.expression), + repeat($._cmd_sep), + field("body", $.command_sequence), + optional("end"), + ), + ), + + settle_command: (_) => "settle", + + wait_command: ($) => + seq( + "wait", + choice( + field("time", $.time_literal), + seq( + "for", + field("event", $.wait_event), + repeat(seq("or", field("event", $.wait_event))), + optional(seq("or", field("timeout", $.time_literal))), + ), + ), + ), + + wait_event: ($) => + seq( + optional(choice("a", "an", "the")), + optional("event"), + field("name", $.event_name), + optional(seq("from", field("source", $.expression))), + ), + + send_command: ($) => + seq( + choice("send", "trigger"), + field("event", $.event_name), + optional(field("args", $.named_argument_list)), + optional( + seq(choice("to", "on"), field("target", $.expression)), + ), + ), + + fetch_command: ($) => + seq( + "fetch", + field("url", choice($.naked_string, $.string, $.backtick_string)), + optional(seq("as", optional(choice("a", "an")), + field("as", choice( + alias("json", $.identifier), + alias("Object", $.identifier), + alias("html", $.identifier), + alias("response", $.identifier), + $.identifier, + )))), + optional(choice( + field("options", $.object_literal), + seq("with", field("options", $.fetch_options)), + )), + ), + + fetch_options: ($) => + seq( + $.named_argument, + repeat(seq(",", $.named_argument)), + ), + + log_command: ($) => + seq( + "log", + optional( + seq( + field("value", $.expression), + repeat(seq(",", field("value", $.expression))), + optional(seq("with", field("logger", $.expression))), + ), + ), + ), + + return_command: ($) => seq("return", optional($.expression)), + throw_command: ($) => seq("throw", $.expression), + + halt_command: ($) => + seq("halt", optional(choice("a", "an")), optional($.expression)), + + break_command: (_) => "break", + continue_command: (_) => "continue", + + invocation_command: ($) => + prec( + 1, + seq( + field("callee", $.identifier), + field("args", $.argument_list), + optional( + seq( + optional( + field( + "prep", + choice("to", "on", "with", "into", "from", "at"), + ), + ), + field("receiver", $.expression), + ), + ), + ), + ), + + generic_command: ($) => + prec.right( + -1, + seq( + field("name", $.identifier), + repeat1(field("arg", $._primary_no_parens)), + ), + ), + + _primary_no_parens: ($) => + choice( + $.literal, + $.special_identifier, + $.identifier, + $.dom_literal, + $._keyword_as_arg, + ), + + _keyword_as_arg: (_) => + choice( + "to", "from", "the", "a", "an", "of", "on", "in", "at", "with", + "into", "before", "after", "as", "for", + + "match", + ), + + transition_command: ($) => + seq( + "transition", + optional(field("target", choice("my", "its", "the"))), + choice( + $.transition_inline, + seq($._sep, field("body", $.transition_block)), + ), + ), + + transition_inline: ($) => + seq( + field("property", $.style_property_name), + optional(seq("from", field("from", $.expression))), + "to", + field("to", $.expression), + optional( + choice( + seq("over", field("duration", $.expression)), + seq("using", field("using", $.expression)), + ), + ), + ), + + transition_block: ($) => + seq( + field("property", $.style_property_name), + $._sep, + optional(seq("from", field("from", $.expression), $._sep)), + "to", + field("to", $.expression), + optional( + choice( + seq(alias($._transition_over, "over"), field("duration", $.expression)), + seq(alias($._transition_using, "using"), field("using", $.expression)), + ), + ), + ), + + style_property_name: ($) => choice($.style_property, $.identifier), + style_property: (_) => token(seq("*", /[A-Za-z_-][A-Za-z0-9_-]*/)), + + expression: ($) => $._logical, + + _logical: ($) => choice($.logical_expression, $._compare), + + logical_expression: ($) => + prec.left( + PREC.OR, + seq($._compare, field("operator", choice("or", "and")), $._compare), + ), + + _compare: ($) => choice($.comparison_expression, $.not_exists_expression, $._math), + + comparison_expression: ($) => + prec.left( + PREC.COMPARE, + seq( + $._math, + field("operator", $.comparison_operator), + $._math, + ), + ), + + comparison_operator: ($) => + prec.right( + choice( + "==", + "!=", + "===", + alias($._lt, "<"), + alias($._lte, "<="), + ">", + ">=", + prec(2, seq("is", "greater", "than", "or", "equal", "to")), + prec(2, seq("is", "less", "than", "or", "equal", "to")), + prec(1, seq("is", "greater", "than")), + prec(1, seq("is", "less", "than")), + prec(1, seq("is", "equal", "to")), + prec(1, seq("is", "not", "equal", "to")), + prec(1, seq("is", "really", "equal", "to")), + prec(1, seq("is", "not", "really", "equal", "to")), + prec(1, seq("is", choice("a", "an"))), + prec(1, seq("is", "not", choice("a", "an"))), + prec(1, seq("is", "in")), + seq("is", optional("not")), + seq("am", optional("not")), + "equals", + seq("really", "equals"), + "matches", + "match", + "contains", + "contain", + "includes", + seq(choice("do", "does"), "not", choice("match", "contain", "include")), + ), + ), + + not_exists_expression: ($) => + prec.left( + PREC.COMPARE, + seq($._math, choice("does", "do"), "not", "exist"), + ), + + _math: ($) => choice($.math_expression, $._unary), + + math_expression: ($) => + prec.left( + PREC.MATH, + seq($._math, field("operator", choice("+", "-", "*", "/", "mod")), $._unary), + ), + + _unary: ($) => choice($.unary_expression, $.block_literal, $._of), + + unary_expression: ($) => + choice( + prec(PREC.UNARY, seq(field("operator", "beep"), token.immediate("!"), $._unary)), + prec(PREC.UNARY, seq(field("operator", choice("not", "no", "!", "-", "+", "async")), $._unary)), + ), + + _of: ($) => choice($.of_expression, $._postfix), + + of_expression: ($) => + prec.left( + PREC.POSTFIX, + seq(field("property", $._postfix), "of", field("owner", $._postfix)), + ), + + _postfix: ($) => + prec.left( + PREC.POSTFIX, + seq( + $._primary, + repeat( + choice( + $.member_access, + $.attr_access, + $.index_access, + field("args", $.argument_list), + $.as_expression, + $.in_expression, + $.exists_expression, + $.possessive_access, + ), + ), + ), + ), + + member_access: ($) => + prec.left(PREC.MEMBER, seq(token.immediate("."), field("property", $.identifier))), + + attr_access: ($) => + prec.left(PREC.MEMBER, seq("@", field("attribute", $.attribute_name))), + + index_access: ($) => + prec.left( + PREC.MEMBER, + seq(token.immediate("["), field("index", $.expression), "]"), + ), + + as_expression: ($) => + prec.left(PREC.MEMBER, seq("as", optional(choice("a", "an")), field("type", $.identifier))), + + in_expression: ($) => + prec.left(PREC.MEMBER, seq("in", field("context", $._primary))), + + exists_expression: (_) => prec.left(PREC.MEMBER, "exists"), + + possessive_access: ($) => + prec.left( + PREC.MEMBER, + seq( + alias($._possessive_s, "'s"), + choice( + field("property", $.identifier), + seq("@", field("attribute", $.attribute_name)), + seq("attribute", field("attribute", $.string_like)), + ), + ), + ), + + _primary: ($) => + choice( + $.literal, + $.special_identifier, + $.identifier, + $.dom_literal, + $.pronoun_possessive_primary, + $.parenthesized_expression, + $.the_expression, + $.closest_expression, + $.positional_expression, + $.object_literal, + $.array_literal, + ), + + parenthesized_expression: ($) => seq("(", $.expression, ")"), + + the_expression: ($) => + seq( + "the", + choice( + $.closest_expression, + $.positional_expression, + prec.right(seq( + field("modifier", choice("next", "previous")), + field("value", choice($.identifier, $.special_identifier, $.dom_literal)), + optional(seq("from", field("from", $.expression))), + optional(seq("within", field("within", $.expression))), + optional(seq("with", "wrapping")), + )), + field("value", $.parenthesized_expression), + field( + "value", + choice($.identifier, $.special_identifier, $.dom_literal), + ), + ), + ), + + closest_expression: ($) => + seq( + "closest", + optional("parent"), + field("value", $.dom_literal), + ), + + positional_expression: ($) => + seq( + choice("first", "last", "random"), + choice( + alias($._positional_in, $.in_expression), + seq( + optional(choice("of", "from")), + choice($.identifier, $.special_identifier, $.dom_literal), + ), + ), + ), + + _positional_in: ($) => + seq("in", choice($.identifier, $.special_identifier, $.dom_literal)), + + pronoun_possessive_primary: ($) => + prec.right( + seq( + field("pronoun", choice("my", "its", "your")), + choice( + seq( + field("property", choice($.identifier, $.style_property)), + repeat(seq(token.immediate("."), field("property", $.identifier))), + ), + seq( + token.immediate("."), + field("property", $.identifier), + repeat(seq(token.immediate("."), field("property", $.identifier))), + ), + ), + ), + ), + + block_literal: ($) => + seq( + "\\", + optional( + seq( + field("param", $.identifier), + repeat(seq(",", field("param", $.identifier))), + ), + ), + "->", + field("body", $.expression), + ), + + argument_list: ($) => + seq( + "(", + optional(seq($.expression, repeat(seq(",", $.expression)))), + ")", + ), + + object_literal: ($) => + seq( + "{", + optional( + seq($.object_pair, repeat(seq(",", $.object_pair)), optional(",")), + ), + "}", + ), + + object_pair: ($) => + seq( + field("key", choice($.identifier, $.hyphenated_identifier, $.string, $.computed_key)), + ":", + field("value", $.expression), + ), + + hyphenated_identifier: (_) => + token(seq(/[A-Za-z_]/, repeat(/[A-Za-z0-9_]/), repeat1(seq("-", repeat1(/[A-Za-z0-9_]/))))), + + computed_key: ($) => seq("[", $.expression, "]"), + + array_literal: ($) => + seq( + "[", + optional( + seq($.expression, repeat(seq(",", $.expression)), optional(",")), + ), + "]", + ), + + dom_literal: ($) => + choice( + $.class_literal, + $.id_literal, + $.query_literal, + $.attribute_literal, + $.attribute_query_literal, + $.style_property, + $.templated_id_literal, + $.templated_class_literal, + ), + + id_literal: (_) => token(seq("#", /[A-Za-z_][A-Za-z0-9_-]*/)), + + templated_id_literal: ($) => + seq(token(prec(1, "#{")), field("value", $.expression), "}"), + + templated_class_literal: ($) => + seq(token(prec(1, ".{")), field("value", $.expression), "}"), + + attribute_name: (_) => /[A-Za-z_][A-Za-z0-9_-]*/, + + attribute_literal: (_) => token(seq("@", /[A-Za-z_][A-Za-z0-9_-]*/)), + + attribute_query_literal: ($) => + seq( + $._attr_query_open, + field("name", $.attribute_name), + optional( + seq( + choice("=", "==", "!=", "is", seq("is", "not")), + field("value", $.expression), + ), + ), + "]", + ), + + identifier: (_) => token(prec(-1, /[A-Za-z_:$][A-Za-z0-9_$]*/)), + + special_identifier: (_) => + choice("it", "result", "me", "you", "yourself", "I", "event"), + + time_literal: ($) => choice($.duration, $.number), + + literal: ($) => + choice( + $.duration, + $.css_measurement, + $.number, + $.string, + $.backtick_string, + $.boolean, + $.null, + $.empty_value, + $.naked_string, + ), + + duration: ($) => choice( + token(/\d+(?:\.\d+)?(?:ms|s)/), + seq($.number, choice("milliseconds", "seconds")), + ), + css_measurement: (_) => token(/\d+(?:\.\d+)?(?:px|em|rem|vh|vw|pt|cm|mm|ch|ex|%)/), + number: (_) => token(choice(/\d+\.\d+/, /\d+/)), + + string: ($) => + choice( + token(seq('"', repeat(choice(/[^"\\\n]/, /\\./)), '"')), + $._single_string, + ), + + backtick_string: ($) => + seq( + "`", + repeat( + choice( + $._template_chars, + $.template_substitution, + ), + ), + "`", + ), + + template_substitution: ($) => + seq(alias("${", $.interpolation_start), $.expression, alias("}", $.interpolation_end)), + + naked_string: (_) => + token(choice(/\/[^\s)]+/, /(?:https?|wss?):\/\/[^\s)]+/)), + + string_like: ($) => + choice($.string, $.backtick_string, $.naked_string, $.identifier), + + boolean: (_) => choice("true", "false"), + null: (_) => "null", + empty_value: (_) => "empty", + + comment: (_) => token(seq("--", /[^\n]*/)), + }, }); diff --git a/tools/common/tree-sitter-hyperscript/hyperscript.so b/tools/common/tree-sitter-hyperscript/hyperscript.so new file mode 100755 index 000000000..dafc2b580 Binary files /dev/null and b/tools/common/tree-sitter-hyperscript/hyperscript.so differ diff --git a/tools/common/tree-sitter-hyperscript/queries/highlights.scm b/tools/common/tree-sitter-hyperscript/queries/highlights.scm index eb4ec62de..2c519f166 100644 --- a/tools/common/tree-sitter-hyperscript/queries/highlights.scm +++ b/tools/common/tree-sitter-hyperscript/queries/highlights.scm @@ -1,45 +1,249 @@ -; Feature keywords (bold in most themes) -(feature_keyword) @keyword +(comment) @comment -; Command keywords -(command_keyword) @keyword +(toggle_command "on" @keyword.operator) +(send_command "on" @keyword.operator) +(invocation_command "on" @keyword.operator) -; Control flow -(control_keyword) @keyword +(toggle_command "for" @keyword.operator) -; Modifiers (contextual keywords - slightly muted) -(modifier) @keyword.modifier +(repeat_statement "in" @keyword.repeat) +(repeat_clause "in" @keyword.repeat) -; Type names -(type_name) @type.builtin +"on" @keyword +"init" @keyword +"def" @keyword.function +"behavior" @keyword +"eventsource" @keyword +"socket" @keyword +"worker" @keyword +"end" @keyword -; Strings -(string) @string -(template_string) @string +(def_feature + name: (identifier) @function) +(def_feature + name: (dotted_identifier) @function) +(behavior_feature + name: (identifier) @type) +(eventsource_feature + name: (identifier) @type) +(socket_feature + name: (identifier) @type) +(worker_feature + name: (identifier) @type) +(install_command + behavior: (identifier) @type) +(install_command + behavior: (dotted_identifier) @type) +(make_command + type: (identifier) @type) + +"set" @keyword +"put" @keyword +"add" @keyword +"remove" @keyword +"toggle" @keyword +"call" @keyword +"get" @keyword +"send" @keyword +"trigger" @keyword +"fetch" @keyword +"log" @keyword +"install" @keyword +"hide" @keyword +"show" @keyword +"make" @keyword +"tell" @keyword +"transition" @keyword +"halt" @keyword +"wait" @keyword +"js" @keyword +"beep" @keyword +"async" @keyword +(settle_command) @keyword +(break_command) @keyword +(continue_command) @keyword + +(generic_command + name: (identifier) @keyword) + +"if" @keyword.conditional +"else" @keyword.conditional +"otherwise" @keyword.conditional +"then" @keyword.conditional +"unless" @keyword.conditional +"when" @keyword.conditional + +"repeat" @keyword.repeat +"for" @keyword.repeat +"while" @keyword.repeat +"until" @keyword.repeat +"forever" @keyword.repeat +"times" @keyword.repeat + +"return" @keyword.return +"throw" @keyword.exception +"catch" @keyword.exception +"finally" @keyword + +"and" @keyword.operator +"or" @keyword.operator +"not" @keyword.operator +"no" @keyword.operator +"is" @keyword.operator +"am" @keyword.operator +"match" @keyword.operator +"matches" @keyword.operator +"contain" @keyword.operator +"contains" @keyword.operator +"includes" @keyword.operator +"equals" @keyword.operator +"really" @keyword.operator +"do" @keyword.operator +"does" @keyword.operator +"mod" @keyword.operator +"greater" @keyword.operator +"less" @keyword.operator +"than" @keyword.operator +"equal" @keyword.operator +(exists_expression) @keyword.operator + +"+" @operator +"-" @operator +"*" @operator +"/" @operator +"=" @operator +"==" @operator +"!=" @operator +"<" @operator +"<=" @operator +">" @operator +">=" @operator +"!" @operator + +"to" @keyword.operator +"from" @keyword.operator +"into" @keyword.operator +(put_command "in" @keyword.operator) +"before" @keyword.operator +"after" @keyword.operator +"at" @keyword.operator +"in" @keyword.operator +"of" @keyword.operator +"with" @keyword.operator +"between" @keyword.operator +"over" @keyword.operator +"using" @keyword.operator +"as" @keyword.operator +"start" @keyword.operator + +"a" @keyword +"an" @keyword + +"every" @keyword +"queue" @keyword +"debounced" @keyword +"throttled" @keyword +"having" @keyword + +"immediately" @keyword +"index" @keyword +"timeout" @keyword +"all" @keyword +"none" @keyword + +"global" @keyword +"element" @keyword +"local" @keyword + +(fetch_command + as: (identifier) @type) + +(as_expression + type: (identifier) @type) +(event_handler + encoding: (identifier) @type) + +(special_identifier) @variable.builtin + +(pronoun_possessive_primary + pronoun: _ @variable.builtin) +"the" @keyword +"closest" @keyword +"parent" @keyword +"first" @keyword +"last" @keyword +"next" @keyword +"previous" @keyword -; Numbers (number) @number +(duration) @number +(css_measurement) @number +(boolean) @boolean +(null) @constant.builtin +(empty_value) @constant.builtin +(string) @string +(backtick_string) @string +(interpolation_start) @punctuation.special +(interpolation_end) @punctuation.special +(naked_string) @string.special -; Booleans and null -(boolean) @constant.builtin +(class_literal) @tag +(id_literal) @tag +(query_literal) @tag +(attribute_literal) @attribute +(style_property) @property +(templated_id_literal) @tag +(templated_class_literal) @tag +(attribute_query_literal + name: (attribute_name) @attribute) -; Comments -(comment) @comment +(js_body) @string.special + +(named_argument + name: (identifier) @property) +(object_pair + key: (identifier) @property) +(object_pair + key: (hyphenated_identifier) @property) +(member_access + property: (identifier) @property) +(attr_access + attribute: (attribute_name) @attribute) +(possessive_access + property: (identifier) @property) +(possessive_access + "attribute" @keyword) +(of_expression + property: (the_expression + value: (identifier) @property)) +(of_expression + property: (identifier) @property) -; CSS references -(css_class) @tag -(css_id) @tag -(css_selector) @tag +(event_binding + name: (event_name) @string.special) +(event_handler + event: (event_name) @string.special) -; Attribute and style references -(attribute_ref) @attribute -(style_ref) @attribute +(param_list + (identifier) @variable.parameter) -; Operators -(operator) @operator +(expression + (identifier) @function.call + (argument_list)) +(invocation_command + callee: (identifier) @function.call) -; Punctuation -(punctuation) @punctuation.bracket +"(" @punctuation.bracket +")" @punctuation.bracket +"[" @punctuation.bracket +"]" @punctuation.bracket +"{" @punctuation.bracket +"}" @punctuation.bracket -; Identifiers -(identifier) @variable +"," @punctuation.delimiter +"." @punctuation.delimiter +":" @punctuation.delimiter +"@" @punctuation.delimiter +"'s" @punctuation.delimiter +"\\" @punctuation.delimiter +"->" @punctuation.delimiter diff --git a/tools/common/tree-sitter-hyperscript/queries/indents.scm b/tools/common/tree-sitter-hyperscript/queries/indents.scm new file mode 100644 index 000000000..925b0b40d --- /dev/null +++ b/tools/common/tree-sitter-hyperscript/queries/indents.scm @@ -0,0 +1,21 @@ +(on_feature) @indent.begin +(init_feature) @indent.begin +(def_feature) @indent.begin +(behavior_feature) @indent.begin +(eventsource_feature) @indent.begin +(socket_feature) @indent.begin +(worker_feature) @indent.begin + +(event_handler) @indent.begin + +(if_statement) @indent.begin +(repeat_statement) @indent.begin + +(else_if_clause) @indent.branch +(else_clause) @indent.branch +(catch_block) @indent.branch +(finally_block) @indent.branch + +(js_command) @indent.begin + +"end" @indent.branch @indent.end diff --git a/tools/common/tree-sitter-hyperscript/queries/injections.scm b/tools/common/tree-sitter-hyperscript/queries/injections.scm index 8a929cbed..d621ce0bd 100644 --- a/tools/common/tree-sitter-hyperscript/queries/injections.scm +++ b/tools/common/tree-sitter-hyperscript/queries/injections.scm @@ -1,19 +1,2 @@ -; Inject hyperscript into HTML attributes: _="...", hs="...", data-hs="..." -; This file is used by editors (Neovim, Helix, Zed) that support Tree-sitter injections. -; The actual injection configuration depends on the HTML tree-sitter grammar. - -; For use with tree-sitter-html, add to your html injections.scm: -; ((attribute -; (attribute_name) @_attr -; (quoted_attribute_value (attribute_value) @injection.content)) -; (#any-of? @_attr "_" "hs" "data-hs") -; (#set! injection.language "hyperscript")) -; -; ((script_element -; (start_tag (attribute -; (attribute_name) @_type -; (quoted_attribute_value (attribute_value) @_val)) -; (#eq? @_type "type") -; (#eq? @_val "text/hyperscript")) -; (raw_text) @injection.content) -; (#set! injection.language "hyperscript")) +((js_body) @injection.content + (#set! injection.language "javascript")) diff --git a/tools/common/tree-sitter-hyperscript/test/corpus/00_smoke.txt b/tools/common/tree-sitter-hyperscript/test/corpus/00_smoke.txt new file mode 100644 index 000000000..2ea03b76c --- /dev/null +++ b/tools/common/tree-sitter-hyperscript/test/corpus/00_smoke.txt @@ -0,0 +1,184 @@ +================== +empty input +================== + + + +--- + +(source_file) + +================== +semicolons only +================== + +;; + +--- + +(source_file) + +================== +comment (docs: --) +================== + +-- this is a comment +log "Yep, that was a comment" + +--- + +(source_file + (comment) + (command_script + (command_sequence_top + (command_statement + (log_command + (expression + (string))))))) + +================== +blank lines and semicolons +================== + +set x to 1 + +; +log x + +--- + +(source_file + (command_script + (command_sequence_top + (command_statement + (set_command + (expression + (identifier)) + (expression + (number)))) + (command_statement + (log_command + (expression + (identifier))))))) + +================== +leading separators +================== + +; +;set x to 1 + +--- + +(source_file + (command_script + (command_sequence_top + (command_statement + (set_command + (expression + (identifier)) + (expression + (number))))))) + +================== +multi-feature (on: click + mouseleave) +================== + +on click add .clicked end +on mouseenter log "entered" + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier)))) + (feature_body + (command_sequence + (command_statement + (add_command + (expression + (class_literal))))))) + (on_feature + (event_spec + (event_binding + (event_name + (identifier)))) + (feature_body + (command_sequence + (command_statement + (log_command + (expression + (string)))))))))) + +================== +multi-command (docs: set and log) +================== + +set x to 10 +log x + +--- + +(source_file + (command_script + (command_sequence_top + (command_statement + (set_command + (expression + (identifier)) + (expression + (number)))) + (command_statement + (log_command + (expression + (identifier))))))) + +================== +then separator (docs: log then log) +================== + +log "Hello" then log "World" + +--- + +(source_file + (command_script + (command_sequence_top + (command_statement + (log_command + (expression + (string)))) + (command_statement + (log_command + (expression + (string))))))) + +================== +feature with end (docs: on click...end) +================== + +on click + log "Clicked!" +end + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier)))) + (feature_body + (command_sequence + (command_statement + (log_command + (expression + (string)))))))))) diff --git a/tools/common/tree-sitter-hyperscript/test/corpus/01_features.txt b/tools/common/tree-sitter-hyperscript/test/corpus/01_features.txt new file mode 100644 index 000000000..0571ab9d8 --- /dev/null +++ b/tools/common/tree-sitter-hyperscript/test/corpus/01_features.txt @@ -0,0 +1,1318 @@ +================== +on click (docs intro: toggle .red on me) +================== + +on click toggle .red on me + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier)))) + (feature_body + (command_sequence + (command_statement + (toggle_command + (expression + (class_literal)) + (expression + (special_identifier)))))))))) + +================== +on click call alert (on: examples) +================== + +on click call alert('You clicked me!') + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier)))) + (feature_body + (command_sequence + (command_statement + (call_command + (expression + (identifier) + (argument_list + (expression + (string)))))))))))) + +================== +on event with params (on: on anEvent(foo) log foo) +================== + +on anEvent(foo) log foo + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier)) + (param_list + (identifier)))) + (feature_body + (command_sequence + (command_statement + (log_command + (expression + (identifier)))))))))) + +================== +on with from (on: from #inc) +================== + +on click from #inc + increment my textContent + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier)) + (expression + (id_literal)))) + (feature_body + (command_sequence + (command_statement + (generic_command + (identifier) + (identifier) + (identifier))))))))) + +================== +on every (docs: every modifier) +================== + +on every click add .clicked + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier)))) + (feature_body + (command_sequence + (command_statement + (add_command + (expression + (class_literal)))))))))) + +================== +on with queue all (docs: event queueing) +================== + +on click queue all + increment :count + wait 1s then put it into the next + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier)))) + (feature_body + (command_sequence + (command_statement + (generic_command + (identifier) + (identifier))) + (command_statement + (wait_command + (time_literal + (duration)))) + (command_statement + (put_command + (expression + (special_identifier)) + (expression + (the_expression + (query_literal))))))))))) + +================== +on with queue none (docs: queue none) +================== + +on click queue none + log 'clicked' + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier)))) + (feature_body + (command_sequence + (command_statement + (log_command + (expression + (string)))))))))) + +================== +on with filter (on: mousedown[button==1]) +================== + +on mousedown[button==1] add .clicked + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier)) + (filter_clause + (expression + (comparison_expression + (identifier) + (comparison_operator) + (number)))))) + (feature_body + (command_sequence + (command_statement + (add_command + (expression + (class_literal)))))))))) + +================== +on with count (on: on click 1) +================== + +on click 1 + log 'first click only' + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier)) + (count_filter + (number)))) + (feature_body + (command_sequence + (command_statement + (log_command + (expression + (string)))))))))) + +================== +on with count range (on: on click 2 to 10) +================== + +on click 2 to 10 + log 'click' + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier)) + (count_filter + (number) + (number)))) + (feature_body + (command_sequence + (command_statement + (log_command + (expression + (string)))))))))) + +================== +on with count unbounded (on: 11 and on) +================== + +on click 11 and on + log 'click' + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier)) + (count_filter + (number)))) + (feature_body + (command_sequence + (command_statement + (log_command + (expression + (string)))))))))) + +================== +on debounced (on: debounced at 500ms) +================== + +on keyup debounced at 500ms + log 'debounced' + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier)) + (rate_limit + (duration)))) + (feature_body + (command_sequence + (command_statement + (log_command + (expression + (string)))))))))) + +================== +on throttled (on: throttled at 500ms) +================== + +on mousemove throttled at 500ms + log 'throttled' + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier)) + (rate_limit + (duration)))) + (feature_body + (command_sequence + (command_statement + (log_command + (expression + (string)))))))))) + +================== +on click or touchstart (on: multiple events) +================== + +on click or touchstart + fetch /example then put it into my innerHTML + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier))) + (event_binding + (event_name + (identifier)))) + (feature_body + (command_sequence + (command_statement + (fetch_command + (naked_string))) + (command_statement + (put_command + (expression + (special_identifier)) + (expression + (pronoun_possessive_primary + (identifier))))))))))) + +================== +on from elsewhere (on: from elsewhere) +================== + +on click from elsewhere + remove .active + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier)) + (expression + (identifier)))) + (feature_body + (command_sequence + (command_statement + (remove_command + (expression + (class_literal)))))))))) + +================== +on exception handler (on: exception) +================== + +on click call mightThrow() +on exception(error) log error + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier)))) + (feature_body + (command_sequence + (command_statement + (call_command + (expression + (identifier) + (argument_list))))))) + (on_feature + (event_spec + (event_binding + (event_name + (identifier)) + (param_list + (identifier)))) + (feature_body + (command_sequence + (command_statement + (log_command + (expression + (identifier)))))))))) + +================== +on mutation (on: synthetic mutation) +================== + +on mutation of @foo put "Mutated" into me + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier)) + (attribute_literal))) + (feature_body + (command_sequence + (command_statement + (put_command + (expression + (string)) + (expression + (special_identifier)))))))))) + +================== +on intersection (on: synthetic intersection) +================== + +on intersection(intersecting) having threshold 0.5 + if intersecting transition opacity to 1 + else transition opacity to 0 + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier)) + (param_list + (identifier)) + (having_clause + (identifier) + (expression + (number))))) + (feature_body + (command_sequence + (command_statement + (if_statement + (expression + (identifier)) + (command_sequence + (command_statement + (transition_command + (transition_inline + (style_property_name + (identifier)) + (expression + (number)))))) + (else_clause + (command_sequence + (command_statement + (transition_command + (transition_inline + (style_property_name + (identifier)) + (expression + (number))))))))))))))) + +================== +on mouseenter and mouseleave (on: examples) +================== + +on mouseenter add .visible to #help end +on mouseleave remove .visible from #help end + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier)))) + (feature_body + (command_sequence + (command_statement + (add_command + (expression + (class_literal)) + (expression + (id_literal))))))) + (on_feature + (event_spec + (event_binding + (event_name + (identifier)))) + (feature_body + (command_sequence + (command_statement + (remove_command + (expression + (class_literal)) + (expression + (id_literal)))))))))) + +================== +on with in modifier (cook: disable all buttons) +================== + +on every htmx:beforeSend in + tell it + toggle [@disabled='true'] until htmx:afterOnLoad + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (colon_identifier + (identifier) + (identifier))) + (expression + (query_literal)))) + (feature_body + (command_sequence + (command_statement + (tell_command + (expression + (special_identifier)) + (command_sequence + (command_statement + (toggle_command + (expression + (attribute_query_literal + (attribute_name) + (expression + (string)))) + (event_name + (colon_identifier + (identifier) + (identifier)))))))))))))) + +================== +init block (docs: init blocks) +================== + +init transition my opacity to 100% over 3s + +--- + +(source_file + (feature_script + (feature_sequence + (init_feature + (feature_body + (command_sequence + (command_statement + (transition_command + (transition_inline + (style_property_name + (identifier)) + (expression + (css_measurement)) + (expression + (duration))))))))))) + +================== +init remove (on: init remove me) +================== + +on click from #inc + increment my textContent +init + remove me + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier)) + (expression + (id_literal)))) + (feature_body + (command_sequence + (command_statement + (generic_command + (identifier) + (identifier) + (identifier)))))) + (init_feature + (feature_body + (command_sequence + (command_statement + (remove_command + (expression + (special_identifier)))))))))) + +================== +def function (docs: functions) +================== + +def waitAndReturn() + wait 2s + return "I waited..." +end + +--- + +(source_file + (feature_script + (feature_sequence + (def_feature + (identifier) + (param_list) + (feature_body + (command_sequence + (command_statement + (wait_command + (time_literal + (duration)))) + (command_statement + (return_command + (expression + (string)))))))))) + +================== +def with params (docs: def increment) +================== + +def increment(i) + return i + 1 +end + +--- + +(source_file + (feature_script + (feature_sequence + (def_feature + (identifier) + (param_list + (identifier)) + (feature_body + (command_sequence + (command_statement + (return_command + (expression + (math_expression + (identifier) + (number))))))))))) + +================== +def namespaced (docs: function namespacing) +================== + +def utils.increment(i) + return i + 1 +end + +--- + +(source_file + (feature_script + (feature_sequence + (def_feature + (dotted_identifier + (identifier) + (identifier)) + (param_list + (identifier)) + (feature_body + (command_sequence + (command_statement + (return_command + (expression + (math_expression + (identifier) + (number))))))))))) + +================== +def with catch (docs: exception handling) +================== + +def example + call mightThrowAnException() +catch e + log e +end + +--- + +(source_file + (feature_script + (feature_sequence + (def_feature + (identifier) + (feature_body + (command_sequence + (command_statement + (call_command + (expression + (identifier) + (argument_list)))))) + (catch_block + (identifier) + (feature_body + (command_sequence + (command_statement + (log_command + (expression + (identifier))))))))))) + +================== +on with finally (docs: finally blocks) +================== + +on click + add @disabled to me + fetch /example + put the result after me +finally + remove @disabled from me + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier)))) + (feature_body + (command_sequence + (command_statement + (add_command + (expression + (attribute_literal)) + (expression + (special_identifier)))) + (command_statement + (fetch_command + (naked_string))) + (command_statement + (put_command + (expression + (the_expression + (special_identifier))) + (expression + (special_identifier)))))) + (finally_block + (feature_body + (command_sequence + (command_statement + (remove_command + (expression + (attribute_literal)) + (expression + (special_identifier))))))))))) + +================== +behavior (beh: Removable) +================== + +behavior Removable(removeButton) + on click from removeButton + remove me + end +end + +--- + +(source_file + (feature_script + (feature_sequence + (behavior_feature + (identifier) + (param_list + (identifier)) + (behavior_body + (on_feature + (event_spec + (event_binding + (event_name + (identifier)) + (expression + (identifier)))) + (feature_body + (command_sequence + (command_statement + (remove_command + (expression + (special_identifier)))))))))))) + +================== +behavior with init (beh: with init guard) +================== + +behavior Removable(removeButton) + init + if no removeButton set the removeButton to me + end + on click from removeButton + remove me + end +end + +--- + +(source_file + (feature_script + (feature_sequence + (behavior_feature + (identifier) + (param_list + (identifier)) + (behavior_body + (init_feature + (feature_body + (command_sequence + (command_statement + (if_statement + (expression + (unary_expression + (identifier))) + (command_sequence + (command_statement + (set_command + (expression + (the_expression + (identifier))) + (expression + (special_identifier)))))))))) + (on_feature + (event_spec + (event_binding + (event_name + (identifier)) + (expression + (identifier)))) + (feature_body + (command_sequence + (command_statement + (remove_command + (expression + (special_identifier)))))))))))) + +================== +install behavior (beh: install) +================== + +install Removable(removeButton: #close-banner) + +--- + +(source_file + (command_script + (command_sequence_top + (command_statement + (install_command + (identifier) + (named_argument_list + (named_argument + (identifier) + (expression + (id_literal))))))))) + +================== +js feature top-level (js-f: function exposure) +================== + +js + function regexFind(re, group, str) { + return new RegExp(re).exec(str)[group]; + } + return { regexFind }; +end + +--- + +(source_file + (command_script + (command_sequence_top + (command_statement + (js_command + (js_body)))))) + +================== +set feature element-scoped (ref: set feature) +================== + +set :count to 0 + +--- + +(source_file + (command_script + (command_sequence_top + (command_statement + (set_command + (expression + (identifier)) + (expression + (number))))))) + +================== +eventsource (es: ChatUpdates) +================== + +eventsource ChatUpdates from http://myserver.com/chat-updates + on newMessage as json + log it + end +end + +--- + +(source_file + (feature_script + (feature_sequence + (eventsource_feature + (identifier) + (naked_string) + (event_handler + (event_name + (identifier)) + (identifier) + (feature_body + (command_sequence + (command_statement + (log_command + (expression + (special_identifier))))))))))) + +================== +socket (sock: MySocket) +================== + +socket MySocket ws://myserver.com/example + on message as json + log message + end +end + +--- + +(source_file + (feature_script + (feature_sequence + (socket_feature + (identifier) + (naked_string) + (event_handler + (event_name + (identifier)) + (identifier) + (feature_body + (command_sequence + (command_statement + (log_command + (expression + (identifier))))))))))) + +================== +worker (docs: workers) +================== + +worker Incrementer + js + self.onmessage = function(e) { + postMessage(e.data + 1); + } + end +end + +--- + +(source_file + (feature_script + (feature_sequence + (worker_feature + (identifier) + (js_command + (js_body)))))) + +================== +combined on features (docs: multiple features) +================== + +on click if true log 'Clicked!' +on mouseenter log 'Mouse entered!' + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier)))) + (feature_body + (command_sequence + (command_statement + (if_statement + (expression + (boolean)) + (command_sequence + (command_statement + (log_command + (expression + (string)))))))))) + (on_feature + (event_spec + (event_binding + (event_name + (identifier)))) + (feature_body + (command_sequence + (command_statement + (log_command + (expression + (string)))))))))) + +================== +on with catch (docs: exception handling) +================== + +on click + call mightThrowAnException() +catch e + log e +end + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier)))) + (feature_body + (command_sequence + (command_statement + (call_command + (expression + (identifier) + (argument_list)))))) + (catch_block + (identifier) + (feature_body + (command_sequence + (command_statement + (log_command + (expression + (identifier))))))))))) + +================== +init immediately (init: immediately modifier) +================== + +init immediately + log "ready" + +--- + +(source_file + (feature_script + (feature_sequence + (init_feature + (feature_body + (command_sequence + (command_statement + (log_command + (expression + (string)))))))))) + +================== +def with finally (def: finally block) +================== + +def loadExample () + add @disabled to me + fetch /example + put the result after me +finally + remove @disabled from me +end + +--- + +(source_file + (feature_script + (feature_sequence + (def_feature + (identifier) + (param_list) + (feature_body + (command_sequence + (command_statement + (add_command + (expression + (attribute_literal)) + (expression + (special_identifier)))) + (command_statement + (fetch_command + (naked_string))) + (command_statement + (put_command + (expression + (the_expression + (special_identifier))) + (expression + (special_identifier)))))) + (finally_block + (feature_body + (command_sequence + (command_statement + (remove_command + (expression + (attribute_literal)) + (expression + (special_identifier))))))))))) + +================== +behavior without params (beh: no params) +================== + +behavior Pulsing + on click + add .pulse + end +end + +--- + +(source_file + (feature_script + (feature_sequence + (behavior_feature + (identifier) + (behavior_body + (on_feature + (event_spec + (event_binding + (event_name + (identifier)))) + (feature_body + (command_sequence + (command_statement + (add_command + (expression + (class_literal)))))))))))) + +================== +eventsource multiple handlers (es: two on blocks) +================== + +eventsource ChatUpdates from http://myserver.com/chat-updates + on newMessage as json + log it + end + + on updateMessage as json + log it + end + +end + +--- + +(source_file + (feature_script + (feature_sequence + (eventsource_feature + (identifier) + (naked_string) + (event_handler + (event_name + (identifier)) + (identifier) + (feature_body + (command_sequence + (command_statement + (log_command + (expression + (special_identifier))))))) + (event_handler + (event_name + (identifier)) + (identifier) + (feature_body + (command_sequence + (command_statement + (log_command + (expression + (special_identifier))))))))))) + +================== +socket with timeout (sock: with timeout) +================== + +socket MySocket ws://myserver.com/example with timeout 5s + on message as json + log message + end +end + +--- + +(source_file + (feature_script + (feature_sequence + (socket_feature + (identifier) + (naked_string) + (time_literal + (duration)) + (event_handler + (event_name + (identifier)) + (identifier) + (feature_body + (command_sequence + (command_statement + (log_command + (expression + (identifier))))))))))) + +================== +worker with external scripts (work: external scripts) +================== + +worker Miner("/scripts/mine-crypto.js") + js + var miner = new CryptoMiner(); + return { miner } + end +end + +--- + +(source_file + (feature_script + (feature_sequence + (worker_feature + (identifier) + (argument_list + (expression + (string))) + (js_command + (js_body)))))) diff --git a/tools/common/tree-sitter-hyperscript/test/corpus/02_commands.txt b/tools/common/tree-sitter-hyperscript/test/corpus/02_commands.txt new file mode 100644 index 000000000..ae1a5adac --- /dev/null +++ b/tools/common/tree-sitter-hyperscript/test/corpus/02_commands.txt @@ -0,0 +1,3717 @@ +================== +add class (add: add .clicked) +================== + +on click add .clicked + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier)))) + (feature_body + (command_sequence + (command_statement + (add_command + (expression + (class_literal)))))))))) + +================== +add class to target (add: add .clicked to #another-div) +================== + +on click add .clicked to #another-div + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier)))) + (feature_body + (command_sequence + (command_statement + (add_command + (expression + (class_literal)) + (expression + (id_literal)))))))))) + +================== +add attribute (add: add @disabled) +================== + +on click add @disabled + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier)))) + (feature_body + (command_sequence + (command_statement + (add_command + (expression + (attribute_literal)))))))))) + +================== +add multiple classes (docs: add .foo to .bar) +================== + +add .foo to .bar + +--- + +(source_file + (command_script + (command_sequence_top + (command_statement + (add_command + (expression + (class_literal)) + (expression + (class_literal))))))) + +================== +remove me (rem: remove me) +================== + +on click remove me + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier)))) + (feature_body + (command_sequence + (command_statement + (remove_command + (expression + (special_identifier)))))))))) + +================== +remove class (rem: remove .not-clicked) +================== + +on click remove .not-clicked + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier)))) + (feature_body + (command_sequence + (command_statement + (remove_command + (expression + (class_literal)))))))))) + +================== +remove class from (rem: remove from #another-div) +================== + +on click remove .not-clacked from #another-div + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier)))) + (feature_body + (command_sequence + (command_statement + (remove_command + (expression + (class_literal)) + (expression + (id_literal)))))))))) + +================== +remove multiple classes (rem: .foo .bar) +================== + +on click remove .foo .bar from #another-div + +--- + +(source_file + (feature_script + (feature_sequence + (on_feature + (event_spec + (event_binding + (event_name + (identifier)))) + (feature_body + (command_sequence + (command_statement + (remove_command + (expression + (class_literal)) + (class_literal) + (expression + (id_literal)))))))))) + +================== +remove attribute (rem: remove @disabled) +================== + +on click remove @disabled from the next