#
# Define without tags.
#
parse
# This is a file header, and shouldn't be part of Lt's comment.

# This is a comment about Lt.
# And another information-packed line about it as well.
#
define Lt {
    Left  Expr
    Right Expr
}
----
(Root
	Defines=(DefineSet
		(Define
			Comments=(Comments # This is a comment about Lt. # And another information-packed line about it as well. #)
			Tags=(Tags)
			Name="Lt"
			Fields=(DefineFields
				(DefineField Name="Left" Type="Expr" Src=<test.opt:7:5>)
				(DefineField Name="Right" Type="Expr" Src=<test.opt:8:5>)
			)
			Src=<test.opt:6:1>
		)
	)
	Rules=(RuleSet)
)

#
# Define with tags.
#
parse
# Comment on definition with a tag. 
[Tag1, Tag2]
define Not {
    Input Expr
}
----
(Root
	Defines=(DefineSet
		(Define
			Comments=(Comments # Comment on definition with a tag. )
			Tags=(Tags Tag1 Tag2)
			Name="Not"
			Fields=(DefineFields
				(DefineField Name="Input" Type="Expr" Src=<test.opt:4:5>)
			)
			Src=<test.opt:2:1>
		)
	)
	Rules=(RuleSet)
)

#
# Define error cases + recovery cases.
#
parse
# Expected tag name
[...]
define Not {}

# Expected comma
[Tag1 Tag2]
define Not {}

# Expected define statement
[Tag1]
def Not {}

# Expected define name
define {}

# Expected '{'
}
define Not Unknown

# Expected field name
define Not {
    ()
}

# Expected field type
define Not {
    Input 123
}
----
test.opt:2:2: expected tag name, found '...'
test.opt:6:7: expected comma, found 'Tag2'
test.opt:11:1: expected define statement, found 'def'
test.opt:14:8: expected define name, found '{'
test.opt:18:12: expected '{', found 'Unknown'
test.opt:22:5: expected define field name, found '('
test.opt:27:11: expected define field type, found '123'

#
# Multiple rules with comments.
#
parse
# This is the One rule.
[One]
(One) => (One)

# This is an intermediate comment that shouldn't be included.

# This is the Two rule.
[Two]
(Two) => (Two)
----
(Root
	Defines=(DefineSet)
	Rules=(RuleSet
		(Rule
			Comments=(Comments # This is the One rule.)
			Name="One"
			Tags=(Tags)
			Match=(Func
				Name=(Names One)
				Args=(Slice)
				Src=<test.opt:3:1>
			)
			Replace=(Func
				Name=(Names One)
				Args=(Slice)
				Src=<test.opt:3:10>
			)
			Src=<test.opt:2:1>
		)
		(Rule
			Comments=(Comments # This is the Two rule.)
			Name="Two"
			Tags=(Tags)
			Match=(Func
				Name=(Names Two)
				Args=(Slice)
				Src=<test.opt:9:1>
			)
			Replace=(Func
				Name=(Names Two)
				Args=(Slice)
				Src=<test.opt:9:10>
			)
			Src=<test.opt:8:1>
		)
	)
)

#
# Match multiple op names.
#
parse
[Tag]
(One | Two) => (One)
----
(Root
	Defines=(DefineSet)
	Rules=(RuleSet
		(Rule
			Comments=(Comments)
			Name="Tag"
			Tags=(Tags)
			Match=(Func
				Name=(Names One Two)
				Args=(Slice)
				Src=<test.opt:2:1>
			)
			Replace=(Func
				Name=(Names One)
				Args=(Slice)
				Src=<test.opt:2:16>
			)
			Src=<test.opt:1:1>
		)
	)
)

#
# Use various match operators.
#
parse
[Tag]
(Op
    (SubOp *)     # Nested match
    "hello"       # String
    10            # Number
    ^(SubOp)      # Negation
    *             # Any
    [ ... * ... ] # List
    [ * ... ]
    [ ... * ]
    [ * ]
    []
)
=>
(Op)
----
(Root
	Defines=(DefineSet)
	Rules=(RuleSet
		(Rule
			Comments=(Comments)
			Name="Tag"
			Tags=(Tags)
			Match=(Func
				Name=(Names Op)
				Args=(Slice
					(Func
						Name=(Names SubOp)
						Args=(Slice (Any))
						Src=<test.opt:3:5>
					)
					"hello"
					10
					(Not
						Input=(Func
							Name=(Names SubOp)
							Args=(Slice)
							Src=<test.opt:6:6>
						)
						Src=<test.opt:6:5>
					)
					(Any)
					(List
						Items=(Slice (ListAny) (Any) (ListAny))
						Src=<test.opt:8:5>
					)
					(List
						Items=(Slice (Any) (ListAny))
						Src=<test.opt:9:5>
					)
					(List
						Items=(Slice (ListAny) (Any))
						Src=<test.opt:10:5>
					)
					(List
						Items=(Slice (Any))
						Src=<test.opt:11:5>
					)
					(List Items=(Slice) Src=<test.opt:12:5>)
				)
				Src=<test.opt:2:1>
			)
			Replace=(Func
				Name=(Names Op)
				Args=(Slice)
				Src=<test.opt:15:1>
			)
			Src=<test.opt:1:1>
		)
	)
)

#
# Bind different kinds of expressions.
#
parse
[Bind]
(Op
    $match:(SubOp *)
    $string:"hello"
    $not:^(SubOp)
    $any:*
    $list:[... * ...]
)
=>
(Op)
----
(Root
	Defines=(DefineSet)
	Rules=(RuleSet
		(Rule
			Comments=(Comments)
			Name="Bind"
			Tags=(Tags)
			Match=(Func
				Name=(Names Op)
				Args=(Slice
					(Bind
						Label="match"
						Target=(Func
							Name=(Names SubOp)
							Args=(Slice (Any))
							Src=<test.opt:3:12>
						)
						Src=<test.opt:3:5>
					)
					(Bind Label="string" Target="hello" Src=<test.opt:4:5>)
					(Bind
						Label="not"
						Target=(Not
							Input=(Func
								Name=(Names SubOp)
								Args=(Slice)
								Src=<test.opt:5:11>
							)
							Src=<test.opt:5:10>
						)
						Src=<test.opt:5:5>
					)
					(Bind Label="any" Target=(Any) Src=<test.opt:6:5>)
					(Bind
						Label="list"
						Target=(List
							Items=(Slice (ListAny) (Any) (ListAny))
							Src=<test.opt:7:11>
						)
						Src=<test.opt:7:5>
					)
				)
				Src=<test.opt:2:1>
			)
			Replace=(Func
				Name=(Names Op)
				Args=(Slice)
				Src=<test.opt:10:1>
			)
			Src=<test.opt:1:1>
		)
	)
)

#
# Match boolean expressions.
#
parse
[boolean]
(op * & ^^(func) & (func2)) => (op)
----
(Root
	Defines=(DefineSet)
	Rules=(RuleSet
		(Rule
			Comments=(Comments)
			Name="boolean"
			Tags=(Tags)
			Match=(Func
				Name=(Names op)
				Args=(Slice
					(And
						Left=(Any)
						Right=(And
							Left=(Not
								Input=(Not
									Input=(Func
										Name=(Names func)
										Args=(Slice)
										Src=<test.opt:2:11>
									)
									Src=<test.opt:2:10>
								)
								Src=<test.opt:2:9>
							)
							Right=(Func
								Name=(Names func2)
								Args=(Slice)
								Src=<test.opt:2:20>
							)
							Src=<test.opt:2:9>
						)
						Src=<test.opt:2:5>
					)
				)
				Src=<test.opt:2:1>
			)
			Replace=(Func
				Name=(Names op)
				Args=(Slice)
				Src=<test.opt:2:32>
			)
			Src=<test.opt:1:1>
		)
	)
)

#
# Match nested custom functions with literal name argument.
#
parse
[Invoke]
(Op $left:* $right:* & (Invoke $right (Invoke2 $left SomeOp))) => (Op)
----
(Root
	Defines=(DefineSet)
	Rules=(RuleSet
		(Rule
			Comments=(Comments)
			Name="Invoke"
			Tags=(Tags)
			Match=(Func
				Name=(Names Op)
				Args=(Slice
					(Bind Label="left" Target=(Any) Src=<test.opt:2:5>)
					(Bind
						Label="right"
						Target=(And
							Left=(Any)
							Right=(Func
								Name=(Names Invoke)
								Args=(Slice
									(Ref Label="right" Src=<test.opt:2:32>)
									(Func
										Name=(Names Invoke2)
										Args=(Slice
											(Ref Label="left" Src=<test.opt:2:48>)
											SomeOp
										)
										Src=<test.opt:2:39>
									)
								)
								Src=<test.opt:2:24>
							)
							Src=<test.opt:2:20>
						)
						Src=<test.opt:2:13>
					)
				)
				Src=<test.opt:2:1>
			)
			Replace=(Func
				Name=(Names Op)
				Args=(Slice)
				Src=<test.opt:2:67>
			)
			Src=<test.opt:1:1>
		)
	)
)

#
# Match list expressions.
#
parse
[List]
(Op
    $any:[ ... $first:[ $item:(SubOp) ... ] & (Func $first $item) ... ]
    $last:[ ... $item:* & ^(Func $item) ]
    $single:[ $item:(SubOp) & (Func $item) ]
    $empty:[]
)
=>
(Op)
----
(Root
	Defines=(DefineSet)
	Rules=(RuleSet
		(Rule
			Comments=(Comments)
			Name="List"
			Tags=(Tags)
			Match=(Func
				Name=(Names Op)
				Args=(Slice
					(Bind
						Label="any"
						Target=(List
							Items=(Slice
								(ListAny)
								(Bind
									Label="first"
									Target=(And
										Left=(List
											Items=(Slice
												(Bind
													Label="item"
													Target=(Func
														Name=(Names SubOp)
														Args=(Slice)
														Src=<test.opt:3:31>
													)
													Src=<test.opt:3:25>
												)
												(ListAny)
											)
											Src=<test.opt:3:23>
										)
										Right=(Func
											Name=(Names Func)
											Args=(Slice
												(Ref Label="first" Src=<test.opt:3:53>)
												(Ref Label="item" Src=<test.opt:3:60>)
											)
											Src=<test.opt:3:47>
										)
										Src=<test.opt:3:23>
									)
									Src=<test.opt:3:16>
								)
								(ListAny)
							)
							Src=<test.opt:3:10>
						)
						Src=<test.opt:3:5>
					)
					(Bind
						Label="last"
						Target=(List
							Items=(Slice
								(ListAny)
								(Bind
									Label="item"
									Target=(And
										Left=(Any)
										Right=(Not
											Input=(Func
												Name=(Names Func)
												Args=(Slice
													(Ref Label="item" Src=<test.opt:4:34>)
												)
												Src=<test.opt:4:28>
											)
											Src=<test.opt:4:27>
										)
										Src=<test.opt:4:23>
									)
									Src=<test.opt:4:17>
								)
							)
							Src=<test.opt:4:11>
						)
						Src=<test.opt:4:5>
					)
					(Bind
						Label="single"
						Target=(List
							Items=(Slice
								(Bind
									Label="item"
									Target=(And
										Left=(Func
											Name=(Names SubOp)
											Args=(Slice)
											Src=<test.opt:5:21>
										)
										Right=(Func
											Name=(Names Func)
											Args=(Slice
												(Ref Label="item" Src=<test.opt:5:37>)
											)
											Src=<test.opt:5:31>
										)
										Src=<test.opt:5:21>
									)
									Src=<test.opt:5:15>
								)
							)
							Src=<test.opt:5:13>
						)
						Src=<test.opt:5:5>
					)
					(Bind
						Label="empty"
						Target=(List Items=(Slice) Src=<test.opt:6:12>)
						Src=<test.opt:6:5>
					)
				)
				Src=<test.opt:2:1>
			)
			Replace=(Func
				Name=(Names Op)
				Args=(Slice)
				Src=<test.opt:9:1>
			)
			Src=<test.opt:1:1>
		)
	)
)

#
# Replace with bound expression.
#
parse
[ConstructBound]
(Op $input:*) => $input
----
(Root
	Defines=(DefineSet)
	Rules=(RuleSet
		(Rule
			Comments=(Comments)
			Name="ConstructBound"
			Tags=(Tags)
			Match=(Func
				Name=(Names Op)
				Args=(Slice
					(Bind Label="input" Target=(Any) Src=<test.opt:2:5>)
				)
				Src=<test.opt:2:1>
			)
			Replace=(Ref Label="input" Src=<test.opt:2:18>)
			Src=<test.opt:1:1>
		)
	)
)

#
# Replace with construct expression.
#
parse
[Construct]
(Op $input:*) => (Op $input (SubOp "foo" AnotherOp))
----
(Root
	Defines=(DefineSet)
	Rules=(RuleSet
		(Rule
			Comments=(Comments)
			Name="Construct"
			Tags=(Tags)
			Match=(Func
				Name=(Names Op)
				Args=(Slice
					(Bind Label="input" Target=(Any) Src=<test.opt:2:5>)
				)
				Src=<test.opt:2:1>
			)
			Replace=(Func
				Name=(Names Op)
				Args=(Slice
					(Ref Label="input" Src=<test.opt:2:22>)
					(Func
						Name=(Names SubOp)
						Args=(Slice "foo" AnotherOp)
						Src=<test.opt:2:29>
					)
				)
				Src=<test.opt:2:18>
			)
			Src=<test.opt:1:1>
		)
	)
)

#
# Replace with construct list expression.
#
parse
[ConstructList]
(Op $left:* $right:*)
=>
(Op [$left "foo" [] [$right] AnotherOp])
----
(Root
	Defines=(DefineSet)
	Rules=(RuleSet
		(Rule
			Comments=(Comments)
			Name="ConstructList"
			Tags=(Tags)
			Match=(Func
				Name=(Names Op)
				Args=(Slice
					(Bind Label="left" Target=(Any) Src=<test.opt:2:5>)
					(Bind Label="right" Target=(Any) Src=<test.opt:2:13>)
				)
				Src=<test.opt:2:1>
			)
			Replace=(Func
				Name=(Names Op)
				Args=(Slice
					(List
						Items=(Slice
							(Ref Label="left" Src=<test.opt:4:6>)
							"foo"
							(List Items=(Slice) Src=<test.opt:4:18>)
							(List
								Items=(Slice
									(Ref Label="right" Src=<test.opt:4:22>)
								)
								Src=<test.opt:4:21>
							)
							AnotherOp
						)
						Src=<test.opt:4:5>
					)
				)
				Src=<test.opt:4:1>
			)
			Src=<test.opt:1:1>
		)
	)
)

#
# Use dynamic construct name.
#
parse
[Construct]
(Op $input:*) => ((MakeOpName $input) (SubOp $input))
----
(Root
	Defines=(DefineSet)
	Rules=(RuleSet
		(Rule
			Comments=(Comments)
			Name="Construct"
			Tags=(Tags)
			Match=(Func
				Name=(Names Op)
				Args=(Slice
					(Bind Label="input" Target=(Any) Src=<test.opt:2:5>)
				)
				Src=<test.opt:2:1>
			)
			Replace=(Func
				Name=(Func
					Name=(Names MakeOpName)
					Args=(Slice
						(Ref Label="input" Src=<test.opt:2:31>)
					)
					Src=<test.opt:2:19>
				)
				Args=(Slice
					(Func
						Name=(Names SubOp)
						Args=(Slice
							(Ref Label="input" Src=<test.opt:2:46>)
						)
						Src=<test.opt:2:39>
					)
				)
				Src=<test.opt:2:18>
			)
			Src=<test.opt:1:1>
		)
	)
)

#
# Match error cases + recovery cases.
#
parse
# Expected define statement or rule
(Op) => (Op)

# Expected op name
[Tag]
(Op | *) => (Op)

# Expected bind label
[Tag]
(Op $*) => (Op)

# Expected match pattern
[Tag]
(Op 1.1) => (Op)

# Expected match pattern
[Tag]
(Op * & $foo:*) => (Op)

# Expected operator name
[Tag]
(Op * & ^(*)) => (Op)

# Expected match pattern in list
[Tag]
(Op [ ... 10.1 ... ]) => (Op)

# Expected list end bracket (any case)
[Tag]
(Op [ ... * ...) => (Op)

# Expected list end bracket (last case)
[Tag]
(Op [ ... * ) => (Op)

# Expected list end bracket (empty case)
[Tag]
(Op [ ) => (Op)

# Numeric value out of range
[Tag]
(Op 1000000000000000000000000000) => (Op)
----
test.opt:2:1: expected define statement or rule, found '('
test.opt:6:7: expected name, found '*'
test.opt:10:6: expected label, found '*'
test.opt:14:6: expected expression, found '.'
test.opt:18:9: expected expression, found '$'
test.opt:22:11: expected name, found '*'
test.opt:26:13: expected expression, found '.'
test.opt:30:16: expected expression, found ')'
test.opt:34:13: expected expression, found ')'
test.opt:38:7: expected expression, found ')'
test.opt:42:5: strconv.ParseInt: parsing "1000000000000000000000000000": value out of range

#
# Replace error cases + recovery cases.
#
parse
# Expected replace expression
[Tag]
(Op) => 123

# Expected construct name
[Tag]
(Op) => (*)

# Expected replace expression (nested)
[Tag]
(Op) => (Op .123)

# Expected construct name (nested)
[Tag]
(Op) => ((123))

# Expected replace pattern, found name
[Tag]
(Op) => Op

# Replace with string expression.
[Tag]
(Op) => "foo"
----
test.opt:3:9: expected replace pattern, found '123'
test.opt:7:10: expected name, found '*'
test.opt:11:13: expected expression, found '.'
test.opt:15:11: expected name, found '123'
test.opt:19:9: expected replace pattern, found 'Op'
test.opt:23:9: expected replace pattern, found '"foo"'

#
# Replace EOF error case
#
parse
[Name]
(Op)
----
test.opt:2:5: expected '=>', found EOF

#
# Error opening file. Ensure that file error is last error, with no recovery
# attempted.
#
parse unknown.opt
define Empty {}
----
unknown file 'unknown.opt'
