aurum 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|