@@ -2,4 +2,95 @@ defmodule GrammarTest do
2
2
use ExUnit.Case
3
3
4
4
doctest Grammar
5
+
6
+ alias Grammar.SpecialTokens.Number
7
+ alias Grammar.Tokenizer
8
+
9
+ test "strict matching of token" do
10
+ grammar =
11
+ Grammar . new ( )
12
+ |> Grammar . add_clause ( :start , [ % Number { number: 42 } , % Number { } ] , fn [ a , b ] -> { a , b } end )
13
+ |> Grammar . prepare! ( )
14
+ |> Grammar . start ( :start )
15
+
16
+ # second number is loose, so it will match anything
17
+ assert { :ok , { % Number { number: 42 } , % Number { number: 42 } } } = Grammar . loop ( grammar , Tokenizer . new ( "42 42" ) )
18
+ assert { :ok , { % Number { number: 42 } , % Number { number: 1000 } } } = Grammar . loop ( grammar , Tokenizer . new ( "42 1000" ) )
19
+
20
+ # first number is strict, so it will match only 42
21
+ assert { :error , { 1 , 1 } , :no_clause_matched } = Grammar . loop ( grammar , Tokenizer . new ( "43 1000" ) )
22
+ end
23
+
24
+ test "matching prototypes and extracted tokens" do
25
+ grammar =
26
+ Grammar . new ( )
27
+ # ordering of clauses is important ! if :non_contant is before constant, it will always match
28
+ |> Grammar . add_clause ( :choose , [ :constant ] , fn [ value ] -> { :constant , value } end )
29
+ |> Grammar . add_clause ( :choose , [ :non_constant ] , fn [ value ] -> { :non_constant , value } end )
30
+ |> Grammar . add_clause ( :constant , [ % Number { number: 12 } ] , & Enum . at ( & 1 , 0 ) )
31
+ |> Grammar . add_clause ( :non_constant , [ % Number { } ] , & Enum . at ( & 1 , 0 ) )
32
+ |> Grammar . prepare! ( )
33
+ |> Grammar . start ( :choose )
34
+
35
+ assert { :ok , { :non_constant , % Number { number: 123 } } } = Grammar . loop ( grammar , Tokenizer . new ( "123" ) )
36
+ assert { :ok , { :constant , % Number { number: 12 } } } = Grammar . loop ( grammar , Tokenizer . new ( "12" ) )
37
+ end
38
+
39
+ test "tokens drive through rules" do
40
+ grammar =
41
+ Grammar . new ( )
42
+ |> Grammar . add_clause ( :foo , [ :bar ] , fn [ value ] -> { :foo_bar , value } end )
43
+ |> Grammar . add_clause ( :foo , [ :neh ] , fn [ value ] -> { :foo_neh , value } end )
44
+ |> Grammar . add_clause ( :foo , [ % Number { } ] , fn [ value ] -> { :foo_number , value } end )
45
+ |> Grammar . add_clause ( :bar , [ "bar1" ] , fn [ "bar1" = value ] -> value end )
46
+ |> Grammar . add_clause ( :bar , [ "bar2" ] , fn [ "bar2" = value ] -> value end )
47
+ |> Grammar . add_clause ( :bar , [ ~r/ bar[0-9]+/ ] , fn [ value ] -> "caught #{ value } " end )
48
+ |> Grammar . add_clause ( :neh , [ "neh1" ] , fn [ "neh1" = value ] -> value end )
49
+ |> Grammar . add_clause ( :neh , [ "neh2" ] , fn [ "neh2" = value ] -> value end )
50
+ |> Grammar . add_clause ( :neh , [ % Number { number: 12 } ] , fn [ value ] -> value end )
51
+ |> Grammar . prepare! ( )
52
+ |> Grammar . start ( :foo )
53
+
54
+ assert grammar . firsts == % {
55
+ foo: [
56
+ [ "bar1" , "bar2" , ~r/ bar[0-9]+/ ] ,
57
+ [ "neh1" , "neh2" , % Number { number: 12 } ] ,
58
+ [ % Number { } ]
59
+ ] ,
60
+ bar: [ [ "bar1" ] , [ "bar2" ] , [ ~r/ bar[0-9]+/ ] ] ,
61
+ neh: [ [ "neh1" ] , [ "neh2" ] , [ % Number { number: 12 } ] ]
62
+ }
63
+
64
+ assert { :ok , { :foo_bar , "bar1" } } = Grammar . loop ( grammar , Tokenizer . new ( "bar1" ) )
65
+ assert { :ok , { :foo_bar , "bar2" } } = Grammar . loop ( grammar , Tokenizer . new ( "bar2" ) )
66
+ assert { :ok , { :foo_bar , "caught bar42" } } = Grammar . loop ( grammar , Tokenizer . new ( "bar42" ) )
67
+
68
+ assert { :ok , { :foo_neh , "neh1" } } = Grammar . loop ( grammar , Tokenizer . new ( "neh1" ) )
69
+ assert { :ok , { :foo_neh , "neh2" } } = Grammar . loop ( grammar , Tokenizer . new ( "neh2" ) )
70
+ assert { :ok , { :foo_neh , % Number { number: 12 } } } = Grammar . loop ( grammar , Tokenizer . new ( "12" ) )
71
+
72
+ assert { :ok , { :foo_number , % Number { number: 13 } } } = Grammar . loop ( grammar , Tokenizer . new ( "13" ) )
73
+ end
74
+
75
+ test "ambiguities on token are resolved by clauses ordering" do
76
+ assert { :ok , "ident foo" }
77
+
78
+ Grammar . new ( )
79
+ |> Grammar . add_clause ( :foo , [ :ident ] , fn [ ident ] -> "ident #{ ident } " end )
80
+ |> Grammar . add_clause ( :foo , [ "foo" ] , fn _ -> "foo" end )
81
+ |> Grammar . add_clause ( :ident , [ ~r/ [a-z]+/ ] , & & 1 )
82
+ |> Grammar . prepare! ( )
83
+ |> Grammar . start ( :foo )
84
+ |> Grammar . loop ( Tokenizer . new ( "foo" ) )
85
+
86
+ assert { :ok , "foo" }
87
+
88
+ Grammar . new ( )
89
+ |> Grammar . add_clause ( :foo , [ "foo" ] , fn _ -> "foo" end )
90
+ |> Grammar . add_clause ( :foo , [ :ident ] , fn [ ident ] -> "ident #{ ident } " end )
91
+ |> Grammar . add_clause ( :ident , [ ~r/ [a-z]+/ ] , & & 1 )
92
+ |> Grammar . prepare! ( )
93
+ |> Grammar . start ( :foo )
94
+ |> Grammar . loop ( Tokenizer . new ( "foo" ) )
95
+ end
5
96
end
0 commit comments