const PREC = [ 'assign', 'pair', 'conditional', 'lazy_or', 'lazy_and', 'arrow', 'comparison', 'pipe_left', 'pipe_right', 'colon_quote', 'colon_range', 'plus', 'times', 'rational', 'bitshift', 'power', 'call', 'decl', 'dot', 'postfix', 'prefix', ].reduce((result, name, index) => { result[name] = index + 10; return result; }, {}); const ASSIGN_OPERATORS = ` = += -= *= /= //= \\= ^= ÷= %= <<= >>= >>>= |= &= ⊻= ≔ ⩴ ≕ `; const ARROW_OPERATORS = ` ← → ↔ ↚ ↛ ↞ ↠ ↢ ↣ ↦ ↤ ↮ ⇎ ⇍ ⇏ ⇐ ⇒ ⇔ ⇴ ⇶ ⇷ ⇸ ⇹ ⇺ ⇻ ⇼ ⇽ ⇾ ⇿ ⟵ ⟶ ⟷ ⟹ ⟺ ⟻ ⟼ ⟽ ⟾ ⟿ ⤀ ⤁ ⤂ ⤃ ⤄ ⤅ ⤆ ⤇ ⤌ ⤍ ⤎ ⤏ ⤐ ⤑ ⤔ ⤕ ⤖ ⤗ ⤘ ⤝ ⤞ ⤟ ⤠ ⥄ ⥅ ⥆ ⥇ ⥈ ⥊ ⥋ ⥎ ⥐ ⥒ ⥓ ⥖ ⥗ ⥚ ⥛ ⥞ ⥟ ⥢ ⥤ ⥦ ⥧ ⥨ ⥩ ⥪ ⥫ ⥬ ⥭ ⥰ ⧴ ⬱ ⬰ ⬲ ⬳ ⬴ ⬵ ⬶ ⬷ ⬸ ⬹ ⬺ ⬻ ⬼ ⬽ ⬾ ⬿ ⭀ ⭁ ⭂ ⭃ ⭄ ⭇ ⭈ ⭉ ⭊ ⭋ ⭌ ← → ⇜ ⇝ ↜ ↝ ↩ ↪ ↫ ↬ ↼ ↽ ⇀ ⇁ ⇄ ⇆ ⇇ ⇉ ⇋ ⇌ ⇚ ⇛ ⇠ ⇢ `; const COMPARISON_OPERATORS = ` > < >= ≥ <= ≤ == === ≡ != ≠ !== ≢ ∈ ∉ ∋ ∌ ⊆ ⊈ ⊂ ⊄ ⊊ ∝ ∊ ∍ ∥ ∦ ∷ ∺ ∻ ∽ ∾ ≁ ≃ ≂ ≄ ≅ ≆ ≇ ≈ ≉ ≊ ≋ ≌ ≍ ≎ ≐ ≑ ≒ ≓ ≖ ≗ ≘ ≙ ≚ ≛ ≜ ≝ ≞ ≟ ≣ ≦ ≧ ≨ ≩ ≪ ≫ ≬ ≭ ≮ ≯ ≰ ≱ ≲ ≳ ≴ ≵ ≶ ≷ ≸ ≹ ≺ ≻ ≼ ≽ ≾ ≿ ⊀ ⊁ ⊃ ⊅ ⊇ ⊉ ⊋ ⊏ ⊐ ⊑ ⊒ ⊜ ⊩ ⊬ ⊮ ⊰ ⊱ ⊲ ⊳ ⊴ ⊵ ⊶ ⊷ ⋍ ⋐ ⋑ ⋕ ⋖ ⋗ ⋘ ⋙ ⋚ ⋛ ⋜ ⋝ ⋞ ⋟ ⋠ ⋡ ⋢ ⋣ ⋤ ⋥ ⋦ ⋧ ⋨ ⋩ ⋪ ⋫ ⋬ ⋭ ⋲ ⋳ ⋴ ⋵ ⋶ ⋷ ⋸ ⋹ ⋺ ⋻ ⋼ ⋽ ⋾ ⋿ ⟈ ⟉ ⟒ ⦷ ⧀ ⧁ ⧡ ⧣ ⧤ ⧥ ⩦ ⩧ ⩪ ⩫ ⩬ ⩭ ⩮ ⩯ ⩰ ⩱ ⩲ ⩳ ⩵ ⩶ ⩷ ⩸ ⩹ ⩺ ⩻ ⩼ ⩽ ⩾ ⩿ ⪀ ⪁ ⪂ ⪃ ⪄ ⪅ ⪆ ⪇ ⪈ ⪉ ⪊ ⪋ ⪌ ⪍ ⪎ ⪏ ⪐ ⪑ ⪒ ⪓ ⪔ ⪕ ⪖ ⪗ ⪘ ⪙ ⪚ ⪛ ⪜ ⪝ ⪞ ⪟ ⪠ ⪡ ⪢ ⪣ ⪤ ⪥ ⪦ ⪧ ⪨ ⪩ ⪪ ⪫ ⪬ ⪭ ⪮ ⪯ ⪰ ⪱ ⪲ ⪳ ⪴ ⪵ ⪶ ⪷ ⪸ ⪹ ⪺ ⪻ ⪼ ⪽ ⪾ ⪿ ⫀ ⫁ ⫂ ⫃ ⫄ ⫅ ⫆ ⫇ ⫈ ⫉ ⫊ ⫋ ⫌ ⫍ ⫎ ⫏ ⫐ ⫑ ⫒ ⫓ ⫔ ⫕ ⫖ ⫗ ⫘ ⫙ ⫷ ⫸ ⫹ ⫺ ⊢ ⊣ ⟂ `; const DOTTY_OPERATORS = '… ⁝ ⋮ ⋱ ⋰ ⋯'; const PLUS_OPERATORS = ` + - | ⊕ ⊖ ⊞ ⊟ ++ ∪ ∨ ⊔ ± ∓ ∔ ∸ ≂ ≏ ⊎ ⊻ ⊽ ⋎ ⋓ ⧺ ⧻ ⨈ ⨢ ⨣ ⨤ ⨥ ⨦ ⨧ ⨨ ⨩ ⨪ ⨫ ⨬ ⨭ ⨮ ⨹ ⨺ ⩁ ⩂ ⩅ ⩊ ⩌ ⩏ ⩐ ⩒ ⩔ ⩖ ⩗ ⩛ ⩝ ⩡ ⩢ ⩣ `; const TIMES_OPERATORS = ` * / ÷ % & ⋅ ∘ × \\ ∩ ∧ ⊗ ⊘ ⊙ ⊚ ⊛ ⊠ ⊡ ⊓ ∗ ∙ ∤ ⅋ ≀ ⊼ ⋄ ⋆ ⋇ ⋉ ⋊ ⋋ ⋌ ⋏ ⋒ ⟑ ⦸ ⦼ ⦾ ⦿ ⧶ ⧷ ⨇ ⨰ ⨱ ⨲ ⨳ ⨴ ⨵ ⨶ ⨷ ⨸ ⨻ ⨼ ⨽ ⩀ ⩃ ⩄ ⩋ ⩍ ⩎ ⩑ ⩓ ⩕ ⩘ ⩚ ⩜ ⩞ ⩟ ⩠ ⫛ ⊍ ▷ ⨝ ⟕ ⟖ ⟗ `; const BITSHIFT_OPERATORS = '<< >> >>>'; const POWER_OPERATORS = ` ^ ↑ ↓ ⇵ ⟰ ⟱ ⤈ ⤉ ⤊ ⤋ ⤒ ⤓ ⥉ ⥌ ⥍ ⥏ ⥑ ⥔ ⥕ ⥘ ⥙ ⥜ ⥝ ⥠ ⥡ ⥣ ⥥ ⥮ ⥯ ↑ ↓ `; module.exports = grammar({ name: 'julia', word: $ => $.identifier, inline: $ => [ $._terminator, $._definition, $._statement, ], supertypes: $ => [ $._statement, $._definition, $._expression, $._primary_expression, ], externals: $ => [ $.block_comment, $._immediate_paren, $._string_start, $._command_start, $._immediate_string_start, $._immediate_command_start, $._string_end, $._command_end, $._string_content, $._string_content_no_interp, ], conflicts: $ => [ // Arrow functions vs tuples [$._primary_expression, $.parameter_list], [$._primary_expression, $.spread_parameter], [$._primary_expression, $.typed_parameter], [$._primary_expression, $.named_field], [$._primary_expression, $.named_field, $.optional_parameter], [$.named_field, $.optional_parameter], ], supertypes: $ => [ $._expression, $._statement, $._definition, ], extras: $ => [ /\s/, $.line_comment, $.block_comment, ], rules: { source_file: $ => optional($._expression_list), _expression_list: $ => seq( sep1($._terminator, choice( $._expression, $.assignment_expression, $.bare_tuple_expression )), optional($._terminator) ), // Definitions _definition: $ => choice( $.abstract_definition, $.primitive_definition, $.struct_definition, $.module_definition, $.function_definition, $.macro_definition ), function_definition: $ => seq( 'function', field('name', $.identifier), field('type_parameters', optional($.type_parameter_list)), field('parameters', $.parameter_list), optional($._expression_list), 'end' ), abstract_definition: $ => seq( 'abstract', 'type', field('name', $.identifier), field('type_parameters', optional($.type_parameter_list)), optional($.subtype_clause), 'end' ), primitive_definition: $ => seq( 'primitive', 'type', field('name', $.identifier), field('type_parameters', optional($.type_parameter_list)), optional($.subtype_clause), alias(numeral('0-9'), $.integer_literal), 'end' ), struct_definition: $ => seq( optional('mutable'), 'struct', field('name', $.identifier), field('type_parameters', optional($.type_parameter_list)), optional($.subtype_clause), optional($._expression_list), 'end' ), module_definition: $ => seq( 'module', field('name', $.identifier), optional($._expression_list), 'end' ), macro_definition: $ => seq( 'macro', field('name', choice($.identifier, $.operator)), field('parameters', $.parameter_list), optional($._expression_list), 'end' ), parameter_list: $ => seq( '(', sep(',', choice( $.identifier, $.spread_parameter, $.optional_parameter, $.typed_parameter )), optional($.keyword_parameters), ')' ), keyword_parameters: $ => seq( ';', sep1(',', choice( $.identifier, $.spread_parameter, $.optional_parameter, $.typed_parameter )) ), optional_parameter: $ => seq( choice($.identifier, $.typed_parameter), '=', $._expression ), spread_parameter: $ => seq($.identifier, '...'), typed_parameter: $ => seq( $.identifier, '::', choice($.identifier, $.parameterized_identifier) ), type_parameter_list: $ => seq( '{', sep1(',', choice($.identifier, $.constrained_parameter)), '}' ), constrained_parameter: $ => seq( field('name', $.identifier), '<:', field('value', $._expression) ), subtype_clause: $ => seq( '<:', $._expression ), // Statements _statement: $ => choice( $.if_statement, $.try_statement, $.for_statement, $.while_statement, $.let_statement, $.const_statement, $.quote_statement, $.break_statement, $.continue_statement, $.return_statement, $.import_statement, $.export_statement ), if_statement: $ => seq( 'if', field('condition', $._expression), optional($._terminator), optional($._expression_list), field('alternative', repeat($.elseif_clause)), field('alternative', optional($.else_clause)), 'end' ), elseif_clause: $ => seq( 'elseif', field('condition', $._expression), optional($._terminator), optional($._expression_list) ), else_clause: $ => seq( 'else', optional($._expression_list) ), try_statement: $ => seq( 'try', optional($._expression_list), optional($.catch_clause), optional($.finally_clause), 'end' ), catch_clause: $ => prec(1, seq( 'catch', optional($.identifier), optional($._terminator), optional($._expression_list), )), finally_clause: $ => seq( 'finally', optional($._terminator), optional($._expression_list), ), for_statement: $ => seq( 'for', sep1(',', $.for_binding), optional($._terminator), optional($._expression_list), 'end' ), while_statement: $ => seq( 'while', field('condition', $._expression), optional($._terminator), optional($._expression_list), 'end' ), break_statement: $ => 'break', continue_statement: $ => 'continue', return_statement: $ => prec.right(-2, seq( 'return', optional(choice( $._expression, $.bare_tuple_expression )) )), let_statement: $ => seq( 'let', sep1(',', $.variable_declaration), optional($._terminator), optional($._expression_list), 'end' ), const_statement: $ => seq( 'const', prec.right(sep1(',', $.variable_declaration)) ), variable_declaration: $ => prec.right(seq( $.identifier, optional(seq('=', $._expression)) )), quote_statement: $ => seq( 'quote', optional($._expression_list), 'end' ), import_statement: $ => prec.right(seq( choice('using', 'import'), sep1(',', choice( $.identifier, $.scoped_identifier, $.selected_import )) )), selected_import: $ => seq( choice($.identifier, $.scoped_identifier), token.immediate(':'), prec.right(sep1(',', choice( $.identifier, $.macro_identifier ))) ), scoped_identifier: $ => prec(PREC.dot, seq( optional(choice($.identifier, $.scoped_identifier)), '.', $.identifier )), export_statement: $ => prec.right(seq( 'export', sep1(',', $.identifier) )), // Expressions _expression: $ => choice( $._statement, $._definition, $.typed_expression, $.compound_expression, $.pair_expression, alias(':', $.operator), $.macro_expression, $.unary_expression, $.binary_expression, $.ternary_expression, $.generator_expression, $.function_expression, $.coefficient_expression, $.spread_expression, $.range_expression, $.quote_expression, $.interpolation_expression, $._primary_expression, $._literal, $.operator, ), _primary_expression: $ => choice( $.identifier, $.array_expression, $.array_comprehension_expression, $.matrix_expression, $.call_expression, $.field_expression, $.parenthesized_expression, $.subscript_expression, $.parameterized_identifier, $.tuple_expression, $.broadcast_call_expression, ), bare_tuple_expression: $ => prec(-1, seq( $._expression, repeat1(prec(-1, seq(',', $._expression))) )), operator: $ => choice( $._comparison_operator, $._dotty_operator, $._plus_operator, $._times_operator, $._rational_operator, $._bitshift_operator, $._power_operator, $._unary_operator, ), parenthesized_expression: $ => prec(1, seq( '(', choice($._expression_list, $.spread_expression), ')' )), field_expression: $ => prec(PREC.dot, seq( $._primary_expression, '.', $.identifier )), subscript_expression: $ => seq( choice( $._primary_expression, $._literal, ), token.immediate('['), sep(',', $._expression), optional(','), ']' ), typed_expression: $ => prec(PREC.decl, seq( $._expression, choice('::', '<:'), choice($.identifier, $.parameterized_identifier) )), parameterized_identifier: $ => seq( choice($.identifier, $.field_expression), $.type_argument_list ), type_argument_list: $ => seq( '{', sep1(',', choice($._expression)), '}' ), compound_expression: $ => seq( 'begin', $._expression_list, 'end' ), call_expression: $ => prec(PREC.call, seq( choice($._primary_expression, $.operator), $._immediate_paren, choice($.argument_list, $.generator_expression), optional($.do_clause) )), broadcast_call_expression: $ => prec(PREC.call, seq( $._primary_expression, '.', $._immediate_paren, choice($.argument_list, $.generator_expression), optional($.do_clause) )), macro_expression: $ => prec.right(seq( $.macro_identifier, optional(choice( seq($._immediate_paren, $.argument_list), $.macro_argument_list )) )), macro_argument_list: $ => prec(-1, repeat1(prec(-1, $._expression))), argument_list: $ => seq( '(', sep(',', choice( $._expression, alias($.named_field, $.named_argument) )), optional(seq( ';', sep1(',', alias($.named_field, $.named_argument)) )), optional(','), ')' ), do_clause: $ => seq( 'do', $._expression_list, 'end' ), named_field: $ => seq( $.identifier, '=', $._expression ), spread_expression: $ => prec(PREC.dot, seq($._expression, '...')), assignment_expression: $ => prec.right(PREC.assign, seq( choice( $._expression, $.bare_tuple_expression ), alias(choice($._assign_operator, '='), $.operator), choice( $._expression, $.assignment_expression, $.bare_tuple_expression ) )), unary_expression: $ => choice( prec(PREC.prefix, seq( alias($._unary_operator, $.operator), $._expression, )), prec(PREC.postfix, seq($._expression, alias("'", $.operator))), ), binary_expression: $ => { const table = [ [prec.left, PREC.power, $._power_operator], [prec.left, PREC.rational, $._rational_operator], [prec.left, PREC.bitshift, $._bitshift_operator], [prec.left, PREC.times, $._times_operator], [prec.left, PREC.plus, choice('+', $._plus_operator)], [prec.left, PREC.colon_range, $._dotty_operator], [prec.right, PREC.arrow, $._arrow_operator], [prec.right, PREC.pipe_left, '<|'], [prec.left, PREC.pipe_right, '|>'], [prec.left, PREC.comparison, choice('in', 'isa', $._comparison_operator)], [prec.left, PREC.lazy_or, '||'], [prec.left, PREC.lazy_and, '&&'], ]; return choice(...table.map(([fn, prec, op]) => fn(prec, seq( $._expression, alias(op, $.operator), $._expression, )))); }, ternary_expression: $ => prec.right(PREC.conditional, seq( $._expression, '?', $._expression, ':', $._expression )), pair_expression: $ => prec.right(PREC.pair, seq( $._expression, '=>', $._expression )), tuple_expression: $ => seq( '(', choice( optional(','), seq( choice($._expression, $.named_field), ',' ), seq( choice($._expression, $.named_field), repeat1(seq(',', choice($._expression, $.named_field))), optional(',') ) ), ')' ), array_expression: $ => seq( '[', sep(',', $._expression), optional(','), ']' ), matrix_expression: $ => prec(-1, seq( '[', sep(';', $.matrix_row), optional(';'), ']' )), matrix_row: $ => repeat1(prec(-1, $._expression)), generator_expression: $ => seq( '(', $._expression, $._comprehension_clause, ')' ), array_comprehension_expression: $ => seq( '[', $._expression, $._comprehension_clause, ']' ), _comprehension_clause: $ => seq( $.for_clause, repeat(choice( $.for_clause, $.if_clause )) ), if_clause: $ => seq( 'if', $._expression ), for_clause: $ => seq( 'for', sep1(',', $.for_binding) ), for_binding: $ => seq( choice($.identifier, $.tuple_expression), choice('in', '=', '∈'), $._expression ), function_expression: $ => prec.right(PREC.arrow, choice( seq( 'function', $.parameter_list, choice( $._expression, $.assignment_expression ), 'end' ), seq( choice( $.identifier, $.parameter_list, ), '->', choice( $._expression, $.assignment_expression )))), range_expression: $ => prec.left(PREC.colon_range, seq( $._expression, ':', $._expression )), coefficient_expression: $ => prec(PREC.call, seq( choice( alias(numeral('0-9'), $.integer_literal), $.float_literal, ), choice( $.parenthesized_expression, $.identifier ) )), quote_expression: $ => prec.left(PREC.colon_quote, seq( ':', $._expression )), interpolation_expression: $ => prec.left(PREC.colon_quote, seq( '$', $._expression )), // Tokens macro_identifier: $ => seq('@', choice( $.identifier, $.operator, alias('.', $.operator) )), identifier: $ => { const operators = [ ',', ';', ':', '(', ')', '{', '}', '&', '$', ASSIGN_OPERATORS, ARROW_OPERATORS, COMPARISON_OPERATORS, DOTTY_OPERATORS, PLUS_OPERATORS, TIMES_OPERATORS, BITSHIFT_OPERATORS, POWER_OPERATORS ]; const operatorCharacters = operators .join(' ') .trim() .replace(/\s+/g, '') .replace(/-/g, '') .replace(/\\/g, '\\\\') .replace(/!/g, ''); const start = "[_\\p{L}\\p{Nl}∇]" const rest = `[^"'\`\\s\\.\\-\\[\\]${operatorCharacters}]*` return new RegExp(start + rest) }, // Literals _literal: $ => choice( $.integer_literal, $.float_literal, $.character_literal, $.string_literal, $.command_literal, $.prefixed_string_literal, $.prefixed_command_literal, ), integer_literal: $ => choice( token(seq('0b', numeral('01'))), token(seq('0o', numeral('0-7'))), token(seq('0x', numeral('0-9a-fA-F'))), numeral('0-9'), ), float_literal: $ => { const dec = numeral('0-9'); const hex = numeral('0-9a-fA-F'); const float = seq( choice( seq(dec, optional('.'), optional(dec)), seq('.', dec), ), optional(/[eEf][+-]?\d+/), // the exponent doesn't allow underscores ) const hex_float = seq( choice( seq('0x', hex, optional('.'), optional(hex)), seq('0x.', hex), ), /p[+-]?\d+/, // hex floats must always have an exponent ) return token(choice(float, hex_float)) }, escape_sequence: $ => token(seq( '\\', token.immediate(choice( /[uU][0-9a-fA-F]{1,6}/, // unicode codepoints /x[0-9a-fA-F]{2}/, /["'`$\\abfnrtv]/, /[0-7]{1,3}/, )), )), character_literal: $ => seq( "'", choice( $.escape_sequence, /[^'\\]/, ), "'", ), string_literal: $ => seq( $._string_start, repeat(choice($._string_content, $.string_interpolation, $.escape_sequence)), $._string_end, ), command_literal: $ => seq( $._command_start, repeat(choice($._string_content, $.string_interpolation, $.escape_sequence)), $._command_end, ), prefixed_string_literal: $ => seq( field('prefix', $.identifier), $._immediate_string_start, repeat(choice($._string_content_no_interp, $.escape_sequence)), $._string_end, ), prefixed_command_literal: $ => seq( field('prefix', $.identifier), $._immediate_command_start, repeat(choice($._string_content_no_interp, $.escape_sequence)), $._command_end, ), string_interpolation: $ => seq( '$', choice( $.identifier, seq('(', $._expression, ')'), ), ), _unary_operator: $ => token(addDots('+ - ! ~ ¬ √ ∛ ∜')), _power_operator: $ => token(addDots(POWER_OPERATORS)), _bitshift_operator: $ => token(addDots(BITSHIFT_OPERATORS)), _rational_operator: $ => token(addDots('//')), _times_operator: $ => token(addDots(TIMES_OPERATORS)), _plus_operator: $ => token(choice('$', addDots(PLUS_OPERATORS))), _dotty_operator: $ => token(choice('..', addDots(DOTTY_OPERATORS))), _comparison_operator: $ => token(choice('<:', '>:', addDots(COMPARISON_OPERATORS))), _arrow_operator: $ => token(choice('<--', '-->', '<-->', addDots(ARROW_OPERATORS))), _assign_operator: $ => token(choice(':=', '~', '$=', addDots(ASSIGN_OPERATORS))), _terminator: $ => choice('\n', ';'), line_comment: $ => token(seq('#', /.*/)) } }); function sep(separator, rule) { return optional(sep1(separator, rule)); } function sep1(separator, rule) { return seq(rule, repeat(seq(separator, rule))); } function addDots(operatorString) { const operators = operatorString.trim().split(/\s+/) return seq(optional('.'), choice(...operators)) } function numeral(range) { return RegExp(`[${range}]|([${range}][${range}_]*[${range}])`) }