-- Nomsu version 1
file:
    (ignored_line %nl)*
    (file_chunks / block / action / expression)?
    (%nl ignored_line)*
    (!. / (({} (.* -> "Parse error") %userdata) => error))

file_chunks (FileChunks):
    {| (block/action/expression) (nodent chunk_delimeter nodent (block/action/expression))+ |}
chunk_delimeter: "~~~" (("~")*)

inline_block (Block):
    {| inline_statement (%ws* ";" %ws*  inline_statement)+ |}
block (Block):
    {| statement (nodent !("~") (statement / (({} ([^%nl]* -> "Unexpected character while parsing block line") %userdata) => error)))+ |}

statement: (action / expression) (eol / (({} ([^%nl]* -> "Unexpected character while parsing line") %userdata) => error))
inline_statement: inline_action / inline_expression

noindex_inline_expression:
    number / variable / inline_text / inline_list / inline_dict / inline_nomsu
    / ( "("
            %ws* (inline_block / inline_action / inline_expression) %ws*
            (%ws* ',' %ws* (inline_block / inline_action / inline_expression) %ws*)*
        (")"
            / (({} ((!. / &%nl) -> 'Line ended without finding a closing )-parenthesis') %userdata) => error)
            / (({} ([^%nl]* -> 'Unexpected character while parsing subexpression') %userdata) => error)
        )
      )
inline_expression:
    index_chain / noindex_inline_expression 
indented_expression:
    indented_text / indented_nomsu / indented_list / indented_dict
    / ("(..)"? indent
        (block / action / expression)
        (dedent / (({} (non_dedent_error -> "Unexpected character while parsing indented expression") %userdata) => error))
      )
expression:
    inline_expression
    / (":" %ws* ((inline_block / inline_action / inline_expression) eol
        / (({} (eol -> "Missing expression after the ':'") %userdata) => error)))
    / indented_expression

inline_nomsu (EscapedNomsu): "\" {| inline_expression |}
indented_nomsu (EscapedNomsu):
    "\" {|
        noindex_inline_expression
        / (":" %ws* ((inline_block / inline_action / inline_expression) eol
             / (({} (eol -> "Missing expression after the ':'") %userdata) => error)))
        / indented_expression |}

index_chain (IndexChain):
    {| noindex_inline_expression ("." (text_word / noindex_inline_expression))+ |}

-- Actions need either at least 1 word, or at least 2 tokens
inline_action (Action):
    !chunk_delimeter
    {|
    (   (inline_expression (%ws* (inline_expression / word))+)
      / (word              (%ws* (inline_expression / word))*))
    (%ws* ":" %ws* (inline_block / inline_action / inline_expression
        / (({} ('' -> "Missing expression after the ':'") %userdata) => error)))?
    |}
action (Action):
    !chunk_delimeter
    {|
       (expression ((nodent "..")? %ws* (expression / word))+)
     / (word       ((nodent "..")? %ws* (expression / word))*)
    |}

word: !number { %operator_char+ / %ident_char+ }

text_word (Text): {| word |}

inline_text (Text):
    !('".."' eol)
    '"' {|
        ({~ (('\"' -> '"') / ('\\' -> '\') / %escaped_char / [^%nl\"])+ ~}
        / inline_text_interpolation)*
    |} ('"' / (
        (({} (eol->'Line ended before finding a closing double quotation mark') %userdata) => error)
       /(({} ([^%nl]*->'Unexpected character while parsing Text') %userdata) => error)
    ))

-- Have to use "%indent" instead of "indent" etc. to avoid messing up text lines that start with "#"
indented_text (Text):
    '".."' eol %nl {|
        {~ (%nl*) (%indent -> "") ~}
        ({~
            (("\\" -> "\") / (("\" nodent "..") -> "")/ (%nl+ {~ %nodent -> "" ~}) / [^%nl\] / (!text_interpolation "\"))+
        ~} / text_interpolation)*
    |} (((!.) %dedent) / (&(%nl %dedent)) / (({} (non_dedent_error -> "Unexpected character while parsing Text") %userdata) => error))
inline_text_interpolation:
    "\" (
        variable / inline_list / inline_dict / inline_text
        / ("("
            %ws* (inline_block / inline_action / inline_expression) %ws*
            (%ws* ',' %ws* (inline_block / inline_action / inline_expression) %ws*)*
        (")"
            / (({} (&%nl -> 'Line ended without finding a closing )-parenthesis') %userdata) => error)
            / (({} ([^%nl]* -> 'Unexpected character while parsing Text interpolation') %userdata) => error))
        )
    )
text_interpolation:
    inline_text_interpolation /
    ("\" indented_expression nodent "..")

number (Number): {| (("-"? (([0-9]+ "." [0-9]+) / ("." [0-9]+) / ([0-9]+)))-> tonumber) |}

-- Variables can be nameless (i.e. just %) and can't contain operators like apostrophe
-- which is a hack to allow %'s to parse as "%" and "' s" separately
variable (Var): "%" {| {(%ident_char+ ((!"'" %operator_char+) / %ident_char+)*)?} |}

inline_list (List):
    !('[..]')
    "[" %ws*
        {| (inline_list_item (%ws* ',' %ws* inline_list_item)* (%ws* ',')?)? |} %ws*
    ("]" / (","? (
        (({} (eol->"Line ended before finding a closing ]-bracket") %userdata) => error)
       /(({} ([^%nl]*->"Unexpected character while parsing List") %userdata) => error)
    )))
indented_list (List):
    "[..]" indent
        {| list_line (nodent list_line)* |}
    (dedent / ((","? {} (non_dedent_error -> "Unexpected character while parsing List") %userdata) => error))
list_line:
     ((action / expression) !(%ws* ','))
     / (inline_list_item ((%ws* ',' %ws*) list_line?)?)
inline_list_item: inline_block / inline_action / inline_expression

inline_dict (Dict):
    !('{..}')
    "{" %ws*
        {| (inline_dict_entry (%ws* ',' %ws* inline_dict_entry)*)? |} %ws*
    ("}" / (","? (
        (({} (%ws* eol->"Line ended before finding a closing }-brace") %userdata) => error)
      / (({} ([^%nl]*->"Unexpected character while parsing Dictionary") %userdata) => error)
    )))
indented_dict (Dict):
    "{..}" indent
        {| dict_line (nodent dict_line)* |}
    (dedent / ((","? {} (non_dedent_error -> "Unexpected character while parsing Dictionary") %userdata) => error))
dict_line:
    (dict_entry !(%ws* ',')) / (inline_dict_entry (%ws* ',' %ws dict_line?)?)
dict_entry(DictEntry):
    {| dict_key (%ws* ":" %ws* (action / expression))? |}
inline_dict_entry(DictEntry):
    {| dict_key (%ws* ":" %ws* (inline_block / inline_action / inline_expression)?)? |}
dict_key:
    text_word / inline_expression

comment: "#" [^%nl]* (%nl+ %indent [^%nl]* (%nl+ %nodent [^%nl]*)* %dedent)?
eol_comment: "#" [^%nl]*

eol: %ws* eol_comment? (!. / &%nl)
ignored_line: (%nodent comment) / (%ws* (!. / &%nl))
indent: eol (%nl ignored_line)* %nl %indent (comment (%nl ignored_line)* nodent)?
nodent: eol (%nl ignored_line)* %nl %nodent
dedent: eol (%nl ignored_line)* (((!.) %dedent) / (&(%nl %dedent)))
non_dedent_error: (!dedent .)* eol (%nl ignored_line)* (!. / &%nl)