@precedence { line_end @left, line_break @left }

@top Script { (newline | terminator)* line+ }

@skip { space | comment }

line { (expression | toplevel) !line_end (newline | terminator)+ }

toplevel { Import | Use | Ns | Test }

Import { silent<"import"> String silent<"as"> Word }

Use { silent<"use"> Word }

Ns {
	silent<"ns"> Word "{"
	separator*
	assoc_term (separator+ assoc_term)*
	separator*
	"}"
}

Test { silent<"test"> String non_binding }

expression { non_binding | binding }

binding { Let | Ref | Fn_Named | Fn_Compound }

Ref { silent<"ref"> Word "=" expression }

non_binding { simple | complex }

synth_root { Word | Keyword }

synth_term { Args | Keyword }

arg_term { Placeholder | simple }

Args {
	("(" (newline | separator)* ")")
	| ("("
	(newline | separator)*
	arg_term ((newline | separator)+ arg_term)*
	(newline | separator)*
	")")
}

Synthetic { synth_root synth_term+ }

complex {
	Block
	| If
	| If_Let
	| Match
	| When
	| Do
	| Loop
	| Repeat
}

Repeat { silent<"repeat"> (Word | Number) Block }

Recur { silent<"recur"> Args }

Loop { silent<"loop"> simple "with" (Fn_Clause | Fn_Clauses) }

simple { atom | collection | Synthetic | Fn_Lambda | Recur }

Fn_Clause { Tuple_Pattern "->" expression }

Fn_Clauses { 
	"{"
	(newline | terminator)*
	Fn_Clause ((newline | terminator)+ Fn_Clause)*
	(newline | terminator)*
	"}"
}

Fn_Compound { silent<"fn"> Word Fn_Clauses }

Fn_Named { silent<"fn"> Word Fn_Clause }

Fn_Lambda { silent<"fn"> Fn_Clause }

// TODO: figure out precedence with do/bind exprs
do_expr { Fn_Lambda | Synthetic | Word | Keyword }

Do {
	silent<"do"> simple !line_break (newline* ">" do_expr)+
 }

Pattern {
	Tuple_Pattern
	| List_Pattern
	| Dict_Pattern
	| atom
	| Placeholder
	| Ignored
}

Ignored { "_" Word }

Placeholder { "_" }

ellipsis { "..." }

Splattern { ellipsis (Word | Placeholder | Ignored) }

Tuple_Pattern {
	("(" (newline | separator)* ")")
	| ("("
	(newline | separator)*
	(Pattern (newline | separator)+)* (Pattern | Splattern)
	(newline | separator)*
	")")
}

List_Pattern {
	("[" (newline | separator)* "]")
	| ("["
	(newline | separator)*
	(Pattern (newline | separator)+)* (Pattern | Splattern)
	(newline | separator)*
	"]")
}

Assoc_Pattern { Word | (Keyword Pattern) }

Dict_Pattern {
	("#{" (newline | separator)* "}")
	| ("#{"
	(newline | separator)*
	(Assoc_Pattern (newline | separator)+)* (Assoc_Pattern | Splattern)
	(newline | separator)*
	"}")
}

Let { silent<"let"> Pattern "=" !line_break newline* non_binding }

Else { silent<"else"> }

Match_Clause {(Pattern | Else) "->" !line_break newline* expression}

match_body {
	Match_Clause
	| (
		"{"
		(newline | terminator)*
		Match_Clause ((newline | terminator)+ Match_Clause)*
		(newline | terminator)*
		"}"
	)
}

Match {
	silent<"match">
	simple
	silent<"with">
	match_body
}

When_Clause {
	(simple | Placeholder | Else) "->" !line_break newline* expression
}

When {
	silent<"when"> 
	"{"
	(newline | terminator)*
	When_Clause ((newline | terminator)+ When_Clause)*
	(newline | terminator)*
	"}"
}

If {
	silent<"if"> simple !line_break newline*
	silent<"then"> expression !line_break newline*
	silent<"else"> expression
}

If_Let {
	silent<"if"> silent<"let">
	Pattern "=" simple !line_break newline*
	silent<"then"> expression !line_break newline*
	silent<"else"> expression
}

Block {
	"{"
	(newline | terminator)*
	expression ((newline | terminator)+ expression)*
	(newline | terminator)*
	"}"
}

collection {
	Tuple
	| List
	| Set
	| Dict
}

Tuple {
	( "(" // non-empty
		(newline | separator)*
		non_binding ((newline | separator)+ non_binding)*
		(newline | separator)*
		")" )
	| "(" (newline | separator)* ")" // empty
}

Splat { ellipsis Word }

linear_term { Splat | non_binding }

List {
	("["
		(newline | separator)*
		linear_term ((newline | separator)+ linear_term)*
		(newline | separator)*
		"]" )
	| "[" (newline | separator)* "]"
}

Set {
	("${"
	(newline | separator)*
	linear_term ((newline | separator)+ linear_term)*
	(newline | separator)*
	"}")
	| "${" (newline | separator)* "}"
}

assoc_term { Word | (Keyword non_binding) }

dict_term { assoc_term | Splat }

Dict {
	("#{"
		(newline | separator)*
		dict_term ((newline | separator)+ dict_term)*
		(newline | separator)*
		"}" ) 
	| "#{" (newline | separator)* "}"
}

atom { Boolean | Nil | String | Number | Keyword | Word }

reserved<term> { @specialize[@name={term}]<Word, term> }

silent<term> { @specialize<Word, term> }

Keyword { ":" Word }

Boolean { reserved<"true"> | reserved<"false"> }

Nil { silent<"nil"> }

@tokens {
	Word { $[a-z] $[a-zA-Z_\-?/!]* }
	space { $[ \t\r]+ }
	comment { "&" ![\n]* }
	String { '"' (!["\\] | "\\" _)* '"' }
	int { $[1-9]$[0-9]* | "0" }
	float { ("0" | int ) "." $[0-9]+}
	Number { "-"? (int | float) }
	separator { "," }
	terminator { ";" }
	newline { "\n" }
}