diff --git a/grammars/renpy.grammar.ebnf b/grammars/renpy.grammar.ebnf index f6755ad7..a1a587ee 100644 --- a/grammars/renpy.grammar.ebnf +++ b/grammars/renpy.grammar.ebnf @@ -1,4 +1,33 @@ -(* Recommended extension for EBNF syntax highlighting: https://marketplace.visualstudio.com/items?itemName=omkov.vscode-ebnf *) +(* + This document is speficied using extended EBNF syntax. + For EBNF see: https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form + For EBNF syntax highlighting see: https://marketplace.visualstudio.com/items?itemName=omkov.vscode-ebnf + + Symbol specification for this document: + = - definition + ; - end of definition + | - alternation (or) + , - concatenation (and) + - - except (not) + + "..." - literal + (...) - grouping + ? ... ? - special sequence (For anything that can't be (easily) expressed using other symbols) + [...] or ...? - optional (zero or one) + {...} or ...* - optionally repeating (zero or more) + ...+ - repeating (one or more, same as `e, {e}`) + s.e+ - join (Match one or more occurrences of e, separated by s. Same as `e, {s, e}`) + + &... - positive lookahead (fail if ... does not match) + !... - negative lookahead (fail if ... matches) + &<... - positive lookbehind (fail if ... was not matched in the current expression) + !<... - negative lookbehind (fail if ... was already matched in the current expression) + + Note that: + - Whitespaces between concatenations are implied, but can be specified for clarity. + - Expressions with alternations on a new line are considered grouped. + - Convention is to put the most significant (higher precedence) expressions first. +*) (*===Tokens with definition===*) (* @@ -21,25 +50,29 @@ INDENT = ? Block opening sequence of WHITESPACE ?; DEDENT = ? Block closing sequence of WHITESPACE less then INDENT ?; CHARACTER = "\p{Any}"; -IDENTIFIER = "\p{XID_Start}", { "\p{XID_Continue}" }; (* Based on Python specification. See: https://docs.python.org/3/reference/lexical_analysis.html#identifiers *) -RENPY_KEYWORD = "as" | "at" | "behind" | "call" | "expression" | "hide" | "if" | "in" | "image" | "init" | "jump" | "menu" | "onlayer" | "python" | "return" | "scene" | "show" | "with" | "while" | "zorder"; -OPERATOR = "<>" | "<<" | "<=" | "<" | ">>" | ">=" | ">" | "!=" | "==" | "|" | "^" | "&" | "+" | "-" | "**" | "*" | "//" | "/" | "%" | "~"; +HASH = HEX_DIGIT+; +INTEGER = ["+" | "-"], DIGIT+; +FLOAT = ["+" | "-"], (DIGIT+, [".", DIGIT*] | ".", DIGIT+), [("e" | "E"), INTEGER]; -HASH = HEX_DIGIT, { HEX_DIGIT }; -INTEGER = [ "+" | "-" ], DIGIT, { DIGIT }; -FLOAT = [ "+" | "-" ], ( DIGIT, { DIGIT }, [ ".", { DIGIT } ] | ".", DIGIT, { DIGIT } ), [ ( "e" | "E" ), [ "+" | "-" ], DIGIT, { DIGIT } ]; +OPERATOR = "<>" | "<<" | "<=" | "<" | ">>" | ">=" | ">" | "!=" | "==" + | "|" | "^" | "&" | "+" | "-" | "**" | "*" | "//" | "/" | "%" | "~" + | "\bor\b" | "\band\b" | "\bnot\b" | "\bin\b" | "\bis\b" + ; +RENPY_KEYWORD = "as" | "at" | "behind" | "call" | "expression" | "hide" | "if" | "in" + | "image" | "init" | "jump" | "menu" | "onlayer" | "python" | "return" | "scene" + | "show" | "with" | "while" | "zorder"; + +IDENTIFIER = "\p{XID_Start}", {"\p{XID_Continue}"}; (* Based on Python specification. See: https://docs.python.org/3/reference/lexical_analysis.html#identifiers *) WORD = IDENTIFIER; NAME = WORD - RENPY_KEYWORD; -DOTTED_NAME = NAME, { ".", NAME }; -LABEL_NAME = ["."], NAME, [ ".", NAME ]; -IMAGE_NAME_COMPONENT_NO_DASH = WORD_CHAR - "-", { WORD_CHAR - "-" }; +DOTTED_NAME = NAME, {".", NAME}; +LABEL_NAME = [NAME?, "."], NAME; IMAGE_NAME_COMPONENT = WORD_CHAR+; -IMAGE_NAME_NO_DASH = IMAGE_NAME_COMPONENT_NO_DASH, { WHITESPACE, IMAGE_NAME_COMPONENT_NO_DASH }; -IMAGE_NAME = IMAGE_NAME_COMPONENT, { WHITESPACE, IMAGE_NAME_COMPONENT }; +IMAGE_NAME = WHITESPACE.IMAGE_NAME_COMPONENT+; -STYLE_PROPERTY_PREFIX = [ "selected_" ], [ ("hover_" | "idle_" | "insensitive_" | "activate_") ]; -STYLE_PROPERTY = "activate_sound" | "adjust_spacing" | "aft_bar" | "aft_gutter" | "align" | "alt" | "altruby_style" | "anchor" | "antialias" | "area" | "background" | "bar_invert" +STYLE_PROPERTY_PREFIX = ["selected_"], [("hover_" | "idle_" | "insensitive_" | "activate_")]; +STYLE_PROPERTY = "activate_sound" | "adjust_spacing" | "aft_bar" | "aft_gutter" | "align" | "alt" | "altruby_style" | "anchor" | "antialias" | "area" | "background" | "bar_invert" | "bar_resizing" | "bar_vertical" | "base_bar" | "black_color" | "bold" | "bottom_bar" | "bottom_gutter" | "bottom_margin" | "bottom_padding" | "box_first_spacing" | "box_layout" | "box_reverse" | "box_spacing" | "box_wrap" | "box_wrap_spacing" | "caret" | "child" | "clipping" | "color" | "debug" | "drop_shadow" | "drop_shadow_color" | "enable_hover" | "first_indent" | "first_spacing" | "fit_first" | "focus_mask" | "focus_rect" | "font" | "fore_bar" | "fore_gutter" | "foreground" | "hinting" | "hover_sound" | "hyperlink_functions" | "italic" @@ -50,7 +83,7 @@ STYLE_PROPERTY = "activate_sound" | "adjust_spacing" | "aft_bar" | "aft_gutter" | "thumb" | "thumb_offset" | "thumb_shadow" | "time_policy" | "top_bar" | "top_gutter" | "top_margin" | "top_padding" | "underline" | "unscrollable" | "vertical" | "xalign" | "xanchor" | "xcenter" | "xfill" | "xfit" | "xmargin" | "xmaximum" | "xminimum" | "xoffset" | "xpadding" | "xpos" | "xsize" | "xspacing" | "xysize" | "yalign" | "yanchor" | "ycenter" | "yfill" | "yfit" | "ymargin" | "ymaximum" | "yminimum" | "yoffset" | "ypadding" | "ypos" | "ysize" | "yspacing"; -STYLE_PROPERTY_NAME = [STYLE_PROPERTY_PREFIX], STYLE_PROPERTY; +STYLE_PROPERTY_NAME = STYLE_PROPERTY_PREFIX?, STYLE_PROPERTY; PYTHON_EXPRESSION = ? any valid Python expression ?; PYTHON_STRING = ? python string ?; @@ -58,35 +91,40 @@ STRING = ? renpy string ?; (*===Renpy Expressions===*) +simple_expression = OPERATOR*, (PYTHON_STRING | NAME | FLOAT | parenthesized_python), [(".", NAME) | parenthesized_python] +simple_operator_expression = simple_expression, {OPERATOR, simple_expression}; +simple_expression_list = simple_expression, {",", simple_expression}; + +parenthesized_python = + | "(", PYTHON_EXPRESSION, ")" + | "[", PYTHON_EXPRESSION, "]" + | "{", PYTHON_EXPRESSION, "}" + ; +python_assignment_operation = "=", PYTHON_EXPRESSION; + +(* TODO: These 4 have some special rules that are not yet defined here *) +parameter = NAME, [python_assignment_operation]; +parameters = "(", [",".parameter+], ")"; +argument = [NAME, "="], PYTHON_EXPRESSION; +arguments = "(", [",".argument+], ")"; + +guard_expression = "if", PYTHON_EXPRESSION; +expression_clause = "expression", simple_expression; as_expression = "as", NAME; at_expression = "at", simple_expression_list; onlayer_expression = "onlayer", NAME; zorder_expression = "zorder", simple_expression; -behind_expression = "behind", NAME, {",", NAME}; -if_expression = "if", PYTHON_EXPRESSION; -expression_clause = "expression", simple_expression; +behind_expression = "behind", ",".NAME+; from_expression = "from", LABEL_NAME; with_expression = "with", simple_expression; -(* TODO: These 4 have some special rules that are not yet defined here *) -parameter = NAME, [ "=", PYTHON_EXPRESSION ]; -parameters = "(", [ parameter, { ",", parameter } ], ")"; -argument = [ NAME, "=" ], PYTHON_EXPRESSION; -arguments = "(", [ argument, { ",", argument } ], ")"; - -parenthesized_python = ( "(", PYTHON_EXPRESSION, ")" ) - | ( "[", PYTHON_EXPRESSION, "]" ) - | ( "{", PYTHON_EXPRESSION, "}" ) - ; + (* image specifier *) -image_specifier = [ expression_clause | IMAGE_NAME ], { image_specifier_clause }; +image_specifier_keywords = "as" | "at" | "onlayer" | "zorder" | "behind"; +image_specifier = [expression_clause | IMAGE_NAME], { !