aurum 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/example/expression/expression.rb +29 -0
- data/lib/aurum.rb +10 -0
- data/lib/aurum/engine.rb +173 -0
- data/lib/aurum/grammar.rb +234 -0
- data/lib/aurum/lexical_table_generator.rb +423 -0
- data/lib/aurum/parsing_table_generator.rb +445 -0
- data/test/engine/lexer_test.rb +52 -0
- data/test/engine/semantic_attributes_test.rb +15 -0
- data/test/grammar_definition/character_class_definition_test.rb +28 -0
- data/test/grammar_definition/grammar_definition_test.rb +54 -0
- data/test/grammar_definition/lexical_definition_test.rb +56 -0
- data/test/grammar_definition/operator_precedence_definition_test.rb +35 -0
- data/test/grammar_definition/production_definition_test.rb +60 -0
- data/test/lexical_table_generator/automata_test.rb +74 -0
- data/test/lexical_table_generator/character_set_test.rb +73 -0
- data/test/lexical_table_generator/interval_test.rb +36 -0
- data/test/lexical_table_generator/pattern_test.rb +109 -0
- data/test/lexical_table_generator/subset_determinizer_test.rb +19 -0
- data/test/lexical_table_generator/table_generator_test.rb +126 -0
- data/test/parsing_table_generator/augmented_grammar_test.rb +45 -0
- data/test/parsing_table_generator/lalr_n_computation_test.rb +89 -0
- data/test/parsing_table_generator/lr_0_automata_test.rb +91 -0
- data/test/parsing_table_generator/lr_item_test.rb +33 -0
- data/test/parsing_table_generator/parsing_table_state_test.rb +39 -0
- data/test/parsing_table_generator/precedence_table_test.rb +28 -0
- data/test/parsing_table_generator/production_test.rb +9 -0
- data/test/test_helper.rb +103 -0
- metadata +78 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + '/../')
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
class IntervalTest < Test::Unit::TestCase
|
5
|
+
def test_should_include_character_in_interval
|
6
|
+
interval = Aurum::CharacterSet::Interval.new ?a, ?c
|
7
|
+
assert interval.include?('b')
|
8
|
+
assert !interval.include?('d')
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_should_use_lowest_as_first_and_highest_as_end
|
12
|
+
interval_a = Aurum::CharacterSet::Interval.new ?a, ?c
|
13
|
+
interval_b = Aurum::CharacterSet::Interval.new ?b, ?d
|
14
|
+
assert interval_a.merge!(interval_b)
|
15
|
+
assert_equal Aurum::CharacterSet::Interval.new(?a, ?d), interval_a
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_should_cat_two_intervals
|
19
|
+
interval_a = Aurum::CharacterSet::Interval.new ?a, ?c
|
20
|
+
interval_b = Aurum::CharacterSet::Interval.new ?d, ?f
|
21
|
+
assert interval_a.merge!(interval_b)
|
22
|
+
assert_equal Aurum::CharacterSet::Interval.new(?a, ?f), interval_a
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_should_return_nil_if_two_intervls_do_not_have_any_char_in_common
|
26
|
+
interval_a = Aurum::CharacterSet::Interval.new ?a, ?c
|
27
|
+
interval_b = Aurum::CharacterSet::Interval.new ?e, ?f
|
28
|
+
assert !interval_a.merge!(interval_b)
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_should_def_interval_for_single_char
|
32
|
+
interval = Aurum::CharacterSet::Interval.new ?a
|
33
|
+
assert interval.include?('a')
|
34
|
+
assert !interval.include?('b')
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + '/../')
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
class PatternTest < Test::Unit::TestCase
|
5
|
+
Epsilon = Aurum::Epsilon
|
6
|
+
|
7
|
+
def test_should_match_single_string
|
8
|
+
pattern = Aurum::Pattern.from_string 'a'
|
9
|
+
assert match?('a', pattern)
|
10
|
+
assert !match?('b', pattern)
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_should_match_string_literal
|
14
|
+
pattern = Aurum::Pattern.from_string 'abc'
|
15
|
+
assert match?('abc', pattern)
|
16
|
+
assert !match?('abcabc', pattern)
|
17
|
+
assert !match?('bcd', pattern)
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_should_match_character_set
|
21
|
+
char_set = Aurum::CharacterSet::Interval.new(?A, ?Z).to_char_set
|
22
|
+
pattern = Aurum::Pattern.from_char_set char_set
|
23
|
+
('A'..'Z').each {|x| assert match?(x, pattern)}
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_should_match_string_literal_zero_or_more_times
|
27
|
+
pattern = Aurum::Pattern.from_string('abc').kleene
|
28
|
+
assert match?('', pattern)
|
29
|
+
assert match?('abc' * 10, pattern)
|
30
|
+
assert !match?('ab', pattern)
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_should_match_string_literal_one_or_more_times
|
34
|
+
pattern = Aurum::Pattern.from_string('abc').iterate
|
35
|
+
assert match?('abc', pattern)
|
36
|
+
assert match?('abc' * 10, pattern)
|
37
|
+
assert !match?('', pattern)
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_should_match_string_literal_zero_or_one_time
|
41
|
+
pattern = Aurum::Pattern.from_string('abc').opt
|
42
|
+
assert match?('', pattern)
|
43
|
+
assert match?('abc', pattern)
|
44
|
+
assert !match?('abc' * 2, pattern)
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_should_match_concate_string
|
48
|
+
first = Aurum::Pattern.from_string 'first'
|
49
|
+
second = Aurum::Pattern.from_string 'second'
|
50
|
+
pattern = Aurum::Pattern.concat(first, second)
|
51
|
+
assert match?('firstsecond', pattern)
|
52
|
+
assert !match?('first', pattern)
|
53
|
+
assert !match?('second', pattern)
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_should_match_pattern_a_or_pattern_b
|
57
|
+
pattern_a = Aurum::Pattern.from_string 'patterna'
|
58
|
+
pattern_b = Aurum::Pattern.from_string 'patternb'
|
59
|
+
pattern = pattern_a | pattern_b
|
60
|
+
['patterna', 'patternb'].each {|x| assert match?(x, pattern)}
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_should_match_pattern_n_times
|
64
|
+
pattern = Aurum::Pattern.from_string 'pattern'
|
65
|
+
assert match?('pattern' * 5, pattern[5])
|
66
|
+
assert match?('pattern' * 10, pattern[10])
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_should_match_pattern_n_to_m_times
|
70
|
+
pattern = Aurum::Pattern.from_string('pattern')[5, 7]
|
71
|
+
(5..7).each {|x| assert match?('pattern' * x, pattern)}
|
72
|
+
assert !match?('pattern' * 4, pattern)
|
73
|
+
assert !match?('pattern' * 8, pattern)
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_should_match_everything_but_the_strings_matched_by_pattern
|
77
|
+
pattern = Aurum::Pattern.from_string 'pattern'
|
78
|
+
negative_pattern = pattern.negate
|
79
|
+
assert !match?('pattern', negative_pattern)
|
80
|
+
assert match?('anything', negative_pattern)
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_should_match_everything_upto_first_occurrence_of_a_text_matched_by_pattern
|
84
|
+
pattern = ~ Aurum::Pattern.from_string('*/')
|
85
|
+
assert match?('comments */', pattern)
|
86
|
+
assert !match?('everything', pattern)
|
87
|
+
end
|
88
|
+
|
89
|
+
def match? expected_string, pattern
|
90
|
+
states = closure pattern.automata.table, [0]
|
91
|
+
expected_string.each_byte {|char| states = move(pattern.automata.table, states, char)}
|
92
|
+
states.include?(pattern.accept)
|
93
|
+
end
|
94
|
+
|
95
|
+
def move automata, states, char
|
96
|
+
result = []
|
97
|
+
states.each {|state| automata[state].each {|tran| result.concat(closure(automata, [tran.destination])) if tran.symbols.include? char} }
|
98
|
+
result.uniq;
|
99
|
+
end
|
100
|
+
|
101
|
+
def closure automata, states
|
102
|
+
closure, unvisited = Set.new(states.dup), states.dup
|
103
|
+
filter = lambda {|x| x.symbols == Epsilon && !closure.include?(x.destination)}
|
104
|
+
while !unvisited.empty? do
|
105
|
+
automata[unvisited.pop].grep_each(filter){|tran| [closure, unvisited].each {|x| x << tran.destination}}
|
106
|
+
end
|
107
|
+
closure.to_a
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + '/../')
|
2
|
+
require 'test_helper'
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
class SubsetDeterminizerTest < Test::Unit::TestCase
|
6
|
+
def test_should_create_equivalentDFA()
|
7
|
+
a, b, abb = Aurum::Pattern.from_string('a'), Aurum::Pattern.from_string('b'), Aurum::Pattern.from_string('abb')
|
8
|
+
pattern = Aurum::Pattern.concat((a | b).kleene, abb)
|
9
|
+
automata, accepts = pattern.automata.determinize [pattern.accept]
|
10
|
+
final = move automata.table, 'aaabbbaaabb'
|
11
|
+
assert accepts.include?(final)
|
12
|
+
end
|
13
|
+
|
14
|
+
def move table, source
|
15
|
+
state = 0
|
16
|
+
source.each_byte {|char| state = (table[state].find {|tran| tran.symbols.include? char}).destination}
|
17
|
+
state
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + '/../')
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
Aurum::LexicalTableGenerator.class_eval do
|
5
|
+
attr_reader :accept_states, :partitions, :lexical_automata
|
6
|
+
|
7
|
+
public :construct_automata, :make_initial_partitions, :refine_partitions
|
8
|
+
|
9
|
+
def table
|
10
|
+
@lexical_automata.table
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class LexicalTableGeneratorTest < Test::Unit::TestCase
|
15
|
+
def test_should_construct_sub_automata_for_lexical_states
|
16
|
+
specification = {:initial => {PATTERN_A => 'recognize'},
|
17
|
+
:state_a => {PATTERN_B => 'recognize'}}
|
18
|
+
|
19
|
+
generator = Aurum::LexicalTableGenerator.new specification
|
20
|
+
generator.construct_automata
|
21
|
+
@table, @accepts, @lexical_states = generator.table, generator.accept_states, generator.lexical_states
|
22
|
+
|
23
|
+
assert recognize?(:initial, 'pattern_a')
|
24
|
+
assert !recognize?(:initial, 'pattern_b')
|
25
|
+
assert !recognize?(:state_a, 'pattern_a')
|
26
|
+
assert recognize?(:state_a, 'pattern_b')
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_should_add_common_patterns_to_all_lexical_states
|
30
|
+
specification = {:initial => {PATTERN_A => 'recognize'},
|
31
|
+
:state_a => {PATTERN_B => 'recognize'},
|
32
|
+
:all => {PATTERN_C => 'recognize'}}
|
33
|
+
|
34
|
+
generator = Aurum::LexicalTableGenerator.new specification
|
35
|
+
generator.construct_automata
|
36
|
+
@table, @accepts, @lexical_states = generator.table, generator.accept_states, generator.lexical_states
|
37
|
+
|
38
|
+
assert recognize?(:initial, 'pattern_c')
|
39
|
+
assert recognize?(:state_a, 'pattern_b')
|
40
|
+
assert !recognize?(:all, 'pattern_b')
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_initial_partiations_should_be_start_accepts_and_non_accepts
|
44
|
+
specification = {:initial => {PATTERN_A => 'recognize'},
|
45
|
+
:state_a => {PATTERN_B => 'recognize'}}
|
46
|
+
|
47
|
+
generator = Aurum::LexicalTableGenerator.new specification
|
48
|
+
generator.construct_automata
|
49
|
+
generator.make_initial_partitions
|
50
|
+
partitions = generator.partitions
|
51
|
+
|
52
|
+
assert_equal 3, partitions.size
|
53
|
+
assert partitions.include?([0])
|
54
|
+
assert partitions.include?(generator.accept_states.keys)
|
55
|
+
assert partitions.include?(generator.lexical_automata.all_states - generator.accept_states.keys - [0])
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_should_not_split_accept_states_if_has_same_action
|
59
|
+
specification = {:initial => {PATTERN_A => 'recognize', PATTERN_B => 'recognize'}}
|
60
|
+
generator = Aurum::LexicalTableGenerator.new specification
|
61
|
+
generator.construct_automata
|
62
|
+
generator.make_initial_partitions
|
63
|
+
|
64
|
+
assert generator.partitions.include?(generator.accept_states.keys)
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_should_split_accept_states_if_has_different_actions
|
68
|
+
specification = {:initial => {PATTERN_A => 'recognizeA', PATTERN_B => 'recognizeB'}}
|
69
|
+
generator = Aurum::LexicalTableGenerator.new specification
|
70
|
+
generator.construct_automata
|
71
|
+
generator.make_initial_partitions
|
72
|
+
|
73
|
+
assert !generator.partitions.include?(generator.accept_states.keys)
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_should_partition_size_should_equal_to_state_size_if_min_dfa_given
|
77
|
+
specification = {:initial => {PATTERN_A => 'recognize'}}
|
78
|
+
generator = Aurum::LexicalTableGenerator.new specification
|
79
|
+
generator.construct_automata
|
80
|
+
generator.make_initial_partitions
|
81
|
+
generator.refine_partitions
|
82
|
+
assert_equal generator.table.size, generator.partitions.size
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_should_partition_size_should_less_than_state_size
|
86
|
+
specification = {:initial => {ABABB => 'recognize'}}
|
87
|
+
generator = Aurum::LexicalTableGenerator.new specification
|
88
|
+
generator.construct_automata
|
89
|
+
generator.make_initial_partitions
|
90
|
+
generator.refine_partitions
|
91
|
+
assert generator.table.size > generator.partitions.size
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_should_return_original_automata_if_min_dfa_given
|
95
|
+
specification = {:initial => {PATTERN_A => 'recognize'}}
|
96
|
+
generator = Aurum::LexicalTableGenerator.new specification
|
97
|
+
lexical_table, accepts = generator.lexical_table
|
98
|
+
assert generator.table.eql?(lexical_table)
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_should_recognize_same_lexeme
|
102
|
+
specification = {:initial => {ABABB => 'recognize'},
|
103
|
+
:state_a => {PATTERN_B => 'recognize'},
|
104
|
+
:all => {PATTERN_C => 'recognize'}}
|
105
|
+
|
106
|
+
generator = Aurum::LexicalTableGenerator.new specification
|
107
|
+
@table, @accepts = generator.lexical_table
|
108
|
+
@lexical_states = generator.lexical_states
|
109
|
+
|
110
|
+
assert recognize?(:initial, 'aabaabaabb')
|
111
|
+
assert !recognize?(:initial, 'patterna')
|
112
|
+
assert recognize?(:state_a, 'pattern_b')
|
113
|
+
assert !recognize?(:all, 'pattern_b')
|
114
|
+
end
|
115
|
+
|
116
|
+
def recognize? lexical_state, source
|
117
|
+
begin
|
118
|
+
lexical_state = - @lexical_states.index(lexical_state) - 1
|
119
|
+
state = (@table[0].find {|tran| tran.symbols.include?(lexical_state)}).destination
|
120
|
+
source.each_byte {|char| state = (@table[state].find {|tran| tran.symbols.include? char}).destination}
|
121
|
+
@accepts.keys.include?(state)
|
122
|
+
rescue
|
123
|
+
false
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + '/../')
|
2
|
+
require 'test_helper'
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
Aurum::ParsingTableGenerator.class_eval do
|
6
|
+
attr_reader :nullables, :first_sets
|
7
|
+
end
|
8
|
+
|
9
|
+
class AugmentedGrammarTest < Test::Unit::TestCase
|
10
|
+
def atest_should_find_all_used_symbols
|
11
|
+
generator = parser_generator E=>[production(E, T)], T=>[production(T, F)], F=>[production(F, ID)]
|
12
|
+
generator.start_from E
|
13
|
+
assert_equal [E, T, F, ID], generator.symbols
|
14
|
+
generator.start_from T
|
15
|
+
assert_equal [T, F, ID], generator.symbols
|
16
|
+
end
|
17
|
+
|
18
|
+
def atest_should_find_all_used_productions
|
19
|
+
generator = parser_generator E=>[production(E, T)], T=>[production(T, F)], F=>[production(F, ID)]
|
20
|
+
generator.start_from E
|
21
|
+
assert_equal [production(START, E), production(E, T), production(T, F), production(F, ID)].to_set, generator.productions.to_set
|
22
|
+
generator.start_from T
|
23
|
+
assert_equal [production(START, T), production(T, F), production(F, ID)].to_set, generator.productions.to_set
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_should_compute_nullable_nonterminals
|
27
|
+
generator = parser_generator E=>[production(E, T)], T=>[production(T)], F=>[production(F, T, ID)]
|
28
|
+
generator.start_from E
|
29
|
+
assert_equal [T, E, START].to_set, generator.nullables.to_set
|
30
|
+
generator.start_from F
|
31
|
+
assert_equal [T].to_set, generator.nullables.to_set
|
32
|
+
end
|
33
|
+
|
34
|
+
def atest_first_set_should_contain_terminals_left_depends_on_nt_dirctly
|
35
|
+
generator = parser_generator E=>[production(E, T, ID), production(E, T, T, T, terminal('other'))], T=>[production(T)]
|
36
|
+
generator.start_from E
|
37
|
+
assert_equal [ID, terminal('other')].to_set, generator.first_sets[E].to_set
|
38
|
+
end
|
39
|
+
|
40
|
+
def atest_should_contain_fist_set_of_nt_which_left_depends_on_nt_dirctly
|
41
|
+
generator = parser_generator E=>[production(E, T, ID), production(E, T, T, T, terminal('other'))], T=>[production(T)], F=>[production(F, T, E)]
|
42
|
+
generator.start_from F
|
43
|
+
assert_equal generator.first_sets[F].to_set, generator.first_sets[E].to_set
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + '/../')
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
Aurum::ParsingTableGenerator.class_eval do
|
5
|
+
attr_reader :states
|
6
|
+
public :construct_LR0_automata, :compute_LALR_1_lookahead, :compute_LALR_n_lookahead, :default_action
|
7
|
+
end
|
8
|
+
class LALRLookaheadComputationTest < Test::Unit::TestCase
|
9
|
+
|
10
|
+
def test_should_compute_reduce_action_for_inconsistent_states
|
11
|
+
generator = parser_generator EXPRESSION_GRAMMAR_LALR1
|
12
|
+
generator.start_from E
|
13
|
+
generator.construct_LR0_automata
|
14
|
+
states = generator.states.find_all {|x| !x.consistent?}
|
15
|
+
generator.compute_LALR_1_lookahead
|
16
|
+
assert_equal [reduce(0)].to_set, states[0][terminal('$eof')]
|
17
|
+
assert_equal [reduce(2)].to_set, states[1][terminal('+')]
|
18
|
+
assert_equal [reduce(2)].to_set, states[1][terminal(')')]
|
19
|
+
assert_equal [reduce(1)].to_set, states[2][terminal('+')]
|
20
|
+
assert_equal [reduce(1)].to_set, states[2][terminal(')')]
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_should_replace_conficted_state_actions_with_lookahead_action
|
24
|
+
generator = parser_generator BNF_GRAMMAR_LALR2
|
25
|
+
generator.start_from BNF
|
26
|
+
generator.construct_LR0_automata
|
27
|
+
generator.compute_LALR_1_lookahead
|
28
|
+
conflicted_state = (generator.states.find_all {|x| x.conflicted? })[0]
|
29
|
+
generator.compute_LALR_n_lookahead
|
30
|
+
assert_equal [lookahead_shift(generator.states.length - 1)].to_set, conflicted_state[terminal('s')]
|
31
|
+
assert !conflicted_state.conflicted?
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_should_add_reduce_action_to_lookahead_state
|
35
|
+
generator = parser_generator BNF_GRAMMAR_LALR2
|
36
|
+
generator.start_from BNF
|
37
|
+
generator.construct_LR0_automata
|
38
|
+
generator.compute_LALR_1_lookahead
|
39
|
+
generator.compute_LALR_n_lookahead
|
40
|
+
states = generator.states
|
41
|
+
assert_equal [read_reduce(6)].to_set, states[-1][terminal('s')]
|
42
|
+
assert_equal [read_reduce(6)].to_set, states[-1][terminal('$eof')]
|
43
|
+
assert_equal [reduce(4)].to_set, states[-1][terminal('->')]
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_should_return_default_reduce_if_no_action_for_given_symbol
|
47
|
+
generator = parser_generator BNF_GRAMMAR_LALR2
|
48
|
+
generator.start_from BNF
|
49
|
+
generator.construct_LR0_automata
|
50
|
+
generator.compute_LALR_1_lookahead
|
51
|
+
generator.compute_LALR_n_lookahead
|
52
|
+
default_actions = generator.states.map {|x| generator.default_action x}
|
53
|
+
assert_equal [reduce(2), reduce(1), nil, reduce(5), reduce(4), reduce(4)], default_actions
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_should_return_lookahead_level
|
57
|
+
generator = parser_generator SIMPLE_GRAMMAR_LR0
|
58
|
+
table, level = generator.start_from(E).parsing_table
|
59
|
+
assert_equal 0, level
|
60
|
+
generator = parser_generator EXPRESSION_GRAMMAR_LALR1
|
61
|
+
table, level = generator.start_from(E).parsing_table
|
62
|
+
assert_equal 1, level
|
63
|
+
generator = parser_generator IF_GRAMMAR_LALR2
|
64
|
+
table, level = generator.start_from(STATEMENT).parsing_table
|
65
|
+
assert_equal 2, level
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_should_raise_error_if_grammar_not_lalr_n
|
69
|
+
begin
|
70
|
+
parser_generator(NOT_LALR_GRAMMAR).start_from(E).parsing_table
|
71
|
+
assert false
|
72
|
+
rescue RuntimeError => error
|
73
|
+
assert_equal 'not LALR(n)', error.message
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_should_resolve_conflicts_for_expression
|
78
|
+
op_a, op_b = terminal('+'), terminal('*')
|
79
|
+
generator = parser_generator EXPRESSION_GRAMMAR, [[op_b], [op_a]]
|
80
|
+
generator.start_from E
|
81
|
+
generator.construct_LR0_automata
|
82
|
+
generator.compute_LALR_1_lookahead
|
83
|
+
state = generator.states.last
|
84
|
+
assert_equal 1, state[terminal('+')].size
|
85
|
+
assert state[terminal('+')].to_a.first.kind_of?(Aurum::ReduceAction)
|
86
|
+
assert_equal 1, state[terminal('*')].size
|
87
|
+
assert state[terminal('*')].to_a.first.kind_of?(Aurum::ShiftAction)
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + '/../')
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
Aurum::ParsingTableGenerator.class_eval do
|
5
|
+
attr_reader :states
|
6
|
+
public :closure, :goto, :read_set, :construct_LR0_automata
|
7
|
+
end
|
8
|
+
|
9
|
+
class LR0AutomataTest < Test::Unit::TestCase
|
10
|
+
def test_closure_should_contain_items_themselves
|
11
|
+
generator = parser_generator E=>[production(E, ID)]
|
12
|
+
assert_equal [LR_item(0, E, ID)], generator.closure([LR_item(0, E, ID)])
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_closure_should_contain_all_right_most_lr_items_of_dot_symbol
|
16
|
+
generator = parser_generator E=>[production(E, T)], T=>[production(T, ID), production(T, terminal('other'))]
|
17
|
+
closure = generator.closure [LR_item(0, E, T)]
|
18
|
+
[LR_item(0, T, ID), LR_item(0, T, terminal('other'))].each {|x| assert closure.include?(x)}
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_should_return_goto_items_if_expected_symbol_given
|
22
|
+
generator = parser_generator E=>[production(E, T)], T=>[production(T, ID), production(T, terminal('other'))]
|
23
|
+
assert_equal [LR_item(1, E, T)], generator.goto([LR_item(0, E, T)], T)
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_goto_items_should_be_closured_if_expected_symbol_given
|
27
|
+
generator = parser_generator E=>[production(E, T, T)], T=>[production(T, ID), production(T, terminal('other'))]
|
28
|
+
goto = generator.goto [LR_item(0, E, T, T)], T
|
29
|
+
[LR_item(0, T, ID), LR_item(0, T, terminal('other'))].each {|x| assert goto.include?(x)}
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_should_use_LR0_items_of_collection_as_state
|
33
|
+
generator = parser_generator SIMPLE_GRAMMAR_LR0
|
34
|
+
generator.start_from E
|
35
|
+
generator.construct_LR0_automata
|
36
|
+
states = generator.states
|
37
|
+
assert 3, states.length
|
38
|
+
assert [LR_item(0, START, E), LR_item(0, T, terminal('+'), T), LR_item(0, T, ID)], states[0]
|
39
|
+
assert [LR_item(1, T, terminal('+'), T)], states[1]
|
40
|
+
assert [LR_item(2, T, terminal('+'), T), LR_item(0, T, ID)], states[2]
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_should_add_shift_action_to_states
|
44
|
+
generator = parser_generator SIMPLE_GRAMMAR_LR0
|
45
|
+
generator.start_from E
|
46
|
+
generator.construct_LR0_automata
|
47
|
+
states = generator.states
|
48
|
+
assert_equal [shift(1)].to_set, states[0][T]
|
49
|
+
assert_equal [shift(2)].to_set, states[1][terminal('+')]
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_should_add_read_reduce_action_to_states
|
53
|
+
generator = parser_generator SIMPLE_GRAMMAR_LR0
|
54
|
+
generator.start_from E
|
55
|
+
generator.construct_LR0_automata
|
56
|
+
states = generator.states
|
57
|
+
assert_equal [read_reduce(0)].to_set, states[0][E]
|
58
|
+
assert_equal [read_reduce(2)].to_set, states[0][ID]
|
59
|
+
assert_equal [read_reduce(2)].to_set, states[2][ID]
|
60
|
+
assert_equal [read_reduce(1)].to_set, states[2][T]
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_should_return_all_predsucceors
|
64
|
+
generator = parser_generator EXPRESSION_GRAMMAR_LALR1
|
65
|
+
generator.start_from E
|
66
|
+
generator.construct_LR0_automata
|
67
|
+
states = generator.states
|
68
|
+
assert_equal [LR_item(0, START, E),
|
69
|
+
LR_item(0, E, E, terminal('+'), T),
|
70
|
+
LR_item(0, E, T),
|
71
|
+
LR_item(0, T, T, terminal('*'), F),
|
72
|
+
LR_item(0, T, F),
|
73
|
+
LR_item(0, F, terminal('('), E, terminal(')')),
|
74
|
+
LR_item(0, F, ID)], states[2].predsucceors([T])[0]
|
75
|
+
assert_equal [LR_item(1, F, terminal('('), E, terminal(')')),
|
76
|
+
LR_item(0, E, E, terminal('+'), T),
|
77
|
+
LR_item(0, E, T),
|
78
|
+
LR_item(0, T, T, terminal('*'), F),
|
79
|
+
LR_item(0, T, F),
|
80
|
+
LR_item(0, F, terminal('('), E, terminal(')')),
|
81
|
+
LR_item(0, F, ID)], states[2].predsucceors([T])[1]
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_should_return_read_set_for_state
|
85
|
+
generator = parser_generator EXPRESSION_GRAMMAR_LALR1
|
86
|
+
generator.start_from E
|
87
|
+
generator.construct_LR0_automata
|
88
|
+
states = generator.states
|
89
|
+
assert_equal [terminal('id'), terminal('(')].to_set, generator.read_set(states[2], terminal('*')).to_set
|
90
|
+
end
|
91
|
+
end
|