rpdf2txt 0.8.2
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +5 -0
- data/LICENCE +515 -0
- data/Manifest.txt +126 -0
- data/README.txt +30 -0
- data/Rakefile +24 -0
- data/bin/rpdf2txt +58 -0
- data/config.save +12 -0
- data/install.rb +1098 -0
- data/lib/rpdf2txt-rockit/base_extensions.rb +73 -0
- data/lib/rpdf2txt-rockit/bootstrap.rb +120 -0
- data/lib/rpdf2txt-rockit/bounded_lru_cache.rb +43 -0
- data/lib/rpdf2txt-rockit/conflict_resolution.rb +302 -0
- data/lib/rpdf2txt-rockit/directed_graph.rb +401 -0
- data/lib/rpdf2txt-rockit/glr_parser.rb +393 -0
- data/lib/rpdf2txt-rockit/grammar.rb +644 -0
- data/lib/rpdf2txt-rockit/graphdrawing.rb +107 -0
- data/lib/rpdf2txt-rockit/graphviz_dot.rb +63 -0
- data/lib/rpdf2txt-rockit/indexable.rb +53 -0
- data/lib/rpdf2txt-rockit/lalr_parsetable_generator.rb +144 -0
- data/lib/rpdf2txt-rockit/parse_table.rb +273 -0
- data/lib/rpdf2txt-rockit/parsetable_generation.rb +164 -0
- data/lib/rpdf2txt-rockit/parsing_ambiguities.rb +84 -0
- data/lib/rpdf2txt-rockit/profiler.rb +168 -0
- data/lib/rpdf2txt-rockit/reduce_actions_generator.rb +523 -0
- data/lib/rpdf2txt-rockit/rockit.rb +76 -0
- data/lib/rpdf2txt-rockit/rockit_grammar_ast_eval.rb +187 -0
- data/lib/rpdf2txt-rockit/rockit_grammars_parser.rb +126 -0
- data/lib/rpdf2txt-rockit/sourcecode_dumpable.rb +181 -0
- data/lib/rpdf2txt-rockit/stringscanner.rb +54 -0
- data/lib/rpdf2txt-rockit/syntax_tree.rb +452 -0
- data/lib/rpdf2txt-rockit/token.rb +364 -0
- data/lib/rpdf2txt-rockit/version.rb +3 -0
- data/lib/rpdf2txt/attributesparser.rb +42 -0
- data/lib/rpdf2txt/cmapparser.rb +65 -0
- data/lib/rpdf2txt/data/_cmap.grammar +11 -0
- data/lib/rpdf2txt/data/_cmap_range.grammar +15 -0
- data/lib/rpdf2txt/data/_pdfattributes.grammar +32 -0
- data/lib/rpdf2txt/data/cmap.grammar +11 -0
- data/lib/rpdf2txt/data/cmap.rb +37 -0
- data/lib/rpdf2txt/data/cmap_range.grammar +15 -0
- data/lib/rpdf2txt/data/cmap_range.rb +43 -0
- data/lib/rpdf2txt/data/fonts/Courier-Bold.afm +342 -0
- data/lib/rpdf2txt/data/fonts/Courier-BoldOblique.afm +342 -0
- data/lib/rpdf2txt/data/fonts/Courier-Oblique.afm +342 -0
- data/lib/rpdf2txt/data/fonts/Courier.afm +342 -0
- data/lib/rpdf2txt/data/fonts/Helvetica-Bold.afm +2827 -0
- data/lib/rpdf2txt/data/fonts/Helvetica-BoldOblique.afm +2827 -0
- data/lib/rpdf2txt/data/fonts/Helvetica-Oblique.afm +3051 -0
- data/lib/rpdf2txt/data/fonts/Helvetica.afm +3051 -0
- data/lib/rpdf2txt/data/fonts/License-Adobe.txt +65 -0
- data/lib/rpdf2txt/data/fonts/Symbol.afm +213 -0
- data/lib/rpdf2txt/data/fonts/Times-Bold.afm +2588 -0
- data/lib/rpdf2txt/data/fonts/Times-BoldItalic.afm +2384 -0
- data/lib/rpdf2txt/data/fonts/Times-Italic.afm +2667 -0
- data/lib/rpdf2txt/data/fonts/Times-Roman.afm +2419 -0
- data/lib/rpdf2txt/data/fonts/ZapfDingbats.afm +225 -0
- data/lib/rpdf2txt/data/pdfattributes.grammar +32 -0
- data/lib/rpdf2txt/data/pdfattributes.rb +71 -0
- data/lib/rpdf2txt/data/pdftext.grammar +102 -0
- data/lib/rpdf2txt/data/pdftext.rb +146 -0
- data/lib/rpdf2txt/default_handler.rb +352 -0
- data/lib/rpdf2txt/lzw.rb +69 -0
- data/lib/rpdf2txt/object.rb +1114 -0
- data/lib/rpdf2txt/parser.rb +169 -0
- data/lib/rpdf2txt/symbol.rb +408 -0
- data/lib/rpdf2txt/text.rb +182 -0
- data/lib/rpdf2txt/text_state.rb +434 -0
- data/lib/rpdf2txt/textparser.rb +42 -0
- data/test/data/3392_obj +0 -0
- data/test/data/397_decrypted +15 -0
- data/test/data/450_decrypted +153 -0
- data/test/data/450_obj +0 -0
- data/test/data/452_decrypted +125 -0
- data/test/data/454_decrypted +108 -0
- data/test/data/456_decrypted +106 -0
- data/test/data/458_decrypted +111 -0
- data/test/data/458_obj +0 -0
- data/test/data/460_decrypted +118 -0
- data/test/data/460_obj +0 -0
- data/test/data/463_decrypted +117 -0
- data/test/data/465_decrypted +107 -0
- data/test/data/465_obj +0 -0
- data/test/data/90_obj +0 -0
- data/test/data/90_obj_comp +1 -0
- data/test/data/decrypted +0 -0
- data/test/data/encrypt_obj +0 -0
- data/test/data/encrypt_string +0 -0
- data/test/data/encrypt_string_128bit +0 -0
- data/test/data/encrypted_object_stream.pdf +0 -0
- data/test/data/firststream +1 -0
- data/test/data/index.pdfobj +0 -0
- data/test/data/index_2bit.pdfobj +0 -0
- data/test/data/index_masked.pdfobj +0 -0
- data/test/data/indexed.pdfobj +0 -0
- data/test/data/indexed_2bit.pdfobj +0 -0
- data/test/data/indexed_masked.pdfobj +0 -0
- data/test/data/inline.png +0 -0
- data/test/data/logo.png +0 -0
- data/test/data/lzw.pdfobj +0 -0
- data/test/data/lzw_index.pdfobj +0 -0
- data/test/data/page_tree.pdf +148 -0
- data/test/data/pdf_20.png +0 -0
- data/test/data/pdf_21.png +0 -0
- data/test/data/pdf_22.png +0 -0
- data/test/data/pdf_50.png +0 -0
- data/test/data/png.pdfobj +0 -0
- data/test/data/space_bug_stream.txt +119 -0
- data/test/data/stream.txt +292 -0
- data/test/data/stream_kerning_bug.txt +13 -0
- data/test/data/stream_kerning_bug2.txt +6 -0
- data/test/data/test.pdf +0 -0
- data/test/data/test.txt +8 -0
- data/test/data/test_text.txt +42 -0
- data/test/data/working_obj +0 -0
- data/test/data/working_obj2 +0 -0
- data/test/mock.rb +149 -0
- data/test/suite.rb +30 -0
- data/test/test_pdf_object.rb +1802 -0
- data/test/test_pdf_parser.rb +1340 -0
- data/test/test_pdf_text.rb +789 -0
- data/test/test_space_bug_05_2004.rb +87 -0
- data/test/test_stream.rb +194 -0
- data/test/test_text_state.rb +315 -0
- data/usage-en.txt +112 -0
- data/user-stories/UserStories_Rpdf2Txt.txt +34 -0
- data/user-stories/documents/swissmedicjournal/04_2004.pdf +0 -0
- metadata +220 -0
@@ -0,0 +1,73 @@
|
|
1
|
+
class Array
|
2
|
+
def equality_uniq
|
3
|
+
uniq_elements = []
|
4
|
+
self.each {|e| uniq_elements.push(e) unless uniq_elements.index(e)}
|
5
|
+
uniq_elements
|
6
|
+
end
|
7
|
+
|
8
|
+
def delete_at_indices(indices = [])
|
9
|
+
not_deleted = Array.new
|
10
|
+
self.each_with_index {|e,i| not_deleted.push(e) if !indices.include?(i)}
|
11
|
+
not_deleted
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class DefaultInitArray < Array
|
16
|
+
def initialize(*args, &initblock)
|
17
|
+
super(*args)
|
18
|
+
@initblock = initblock
|
19
|
+
end
|
20
|
+
|
21
|
+
def [](index)
|
22
|
+
super(index) || (self[index] = @initblock.call(index))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class ArrayOfArrays < DefaultInitArray
|
27
|
+
@@create_array = proc{|i| Array.new}
|
28
|
+
def initialize(*args)
|
29
|
+
super(*args, &@@create_array)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class ArrayOfHashes < DefaultInitArray
|
34
|
+
@@create_hash = proc{|i| Hash.new}
|
35
|
+
def initialize(*args)
|
36
|
+
super(*args, &@@create_hash)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Hash which takes a block that is called to give a default value when a key
|
41
|
+
# has the value nil in the hash.
|
42
|
+
class DefaultInitHash < Hash
|
43
|
+
def initialize(*args, &initblock)
|
44
|
+
super(*args)
|
45
|
+
@initblock = initblock
|
46
|
+
end
|
47
|
+
|
48
|
+
def [](key)
|
49
|
+
#super(key) || (self[key] = @initblock.call(key))
|
50
|
+
fetch(key) {
|
51
|
+
store(key, @initblock.call(key))
|
52
|
+
}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
unless Object.constants.include?("TimesClass")
|
57
|
+
TimesClass = (RUBY_VERSION < "1.7") ? Time : Process
|
58
|
+
end
|
59
|
+
|
60
|
+
def time_and_puts(string, &block)
|
61
|
+
if $TIME_AND_PUTS_VERBOSE
|
62
|
+
print string; STDOUT.flush
|
63
|
+
end
|
64
|
+
starttime = [Time.new, TimesClass.times]
|
65
|
+
block.call
|
66
|
+
endtime = [Time.new, TimesClass.times]
|
67
|
+
duration = endtime[0] - starttime[0]
|
68
|
+
begin
|
69
|
+
load = [((endtime[1].utime+endtime[1].stime)-(starttime[1].utime+starttime[1].stime))/duration*100.0, 100.0].min
|
70
|
+
puts " (%.2f s %.2f%%)" % [duration, load] if $TIME_AND_PUTS_VERBOSE
|
71
|
+
rescue FloatDomainError
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# This is a version of the grammar for Rockit grammars in code. It is used
|
2
|
+
# to bootstrap the rockit grammar parser.
|
3
|
+
#
|
4
|
+
# NOTE: The handcoded grammar in this file may not be
|
5
|
+
# fully in sync with the rockit-grammar.grammar. This is not
|
6
|
+
# needed as long as it can be used to parse the file. However,
|
7
|
+
# YOU SHOULD NOT USE THE HANDCODED GRAMMAR IN THIS FILE AS A REFERENCE
|
8
|
+
# TO ROCKIT GRAMMARS.
|
9
|
+
#
|
10
|
+
# Overview of bootstrapping process:
|
11
|
+
# 1. Generate a parser (rp) for Rockit grammars from this in-code grammar.
|
12
|
+
# The parser MAY NOT BE FULLY UP-TO-DATE but it must be able to parse
|
13
|
+
# the rockit-grammar.grammar file.
|
14
|
+
# 2. Parse the rockit-grammar.grammar file with rp.
|
15
|
+
# 3. Generate source code for up-to-date rockit parser.
|
16
|
+
#
|
17
|
+
require 'rpdf2txt-rockit/rockit'
|
18
|
+
require 'rpdf2txt-rockit/base_extensions'
|
19
|
+
|
20
|
+
module Parse
|
21
|
+
RockitTokens = [
|
22
|
+
blank = t("Blank", /\s+/n, :Skip),
|
23
|
+
comment = t("Comment", /#.*$/n, :Skip),
|
24
|
+
string = t("String", /('((\\')|[^'])*')|("((\\")|[^"])*")/n),
|
25
|
+
regexp = t("Regexp", /\/((\\\/)|[^\/])*\/[iomx]*/n),
|
26
|
+
arrow = t("Arrow", /(->)|(::=)|(:)/n),
|
27
|
+
symbol_name = t("SymbolName", /[A-Z][A-Za-z]*/n),
|
28
|
+
production_reference = t("ProductionReference", /[A-Z][A-Za-z]*\d+/n)
|
29
|
+
]
|
30
|
+
|
31
|
+
RockitTokenProds = [
|
32
|
+
prod(:Tokens, ['Tokens', plus(:TokenSpec)], stb(:^, [:_, :tokens])),
|
33
|
+
prod(:TokenSpec,
|
34
|
+
[symbol_name, '=', ore(string, regexp), maybe(:TokenOpts)],
|
35
|
+
stb(nil, [:tokenname, :_, :regexp, :options])),
|
36
|
+
prod(:TokenOpts, ['[', /:Skip/in, ']'], stb(:^, [:_, :options, :_]))
|
37
|
+
]
|
38
|
+
|
39
|
+
def rockit_tokens_parser
|
40
|
+
Parse.parser_from_grammar(Grammar.new("RockitTokens", RockitTokenProds,
|
41
|
+
RockitTokens))
|
42
|
+
end
|
43
|
+
|
44
|
+
RockitPrioritiesProds = [
|
45
|
+
prod(:Priorities, ['Priorities', plus(:Priority)], stb(:^, [:_, :prios])),
|
46
|
+
prod(:Priority, [ore('left(', 'right('), liste(:ProdRef, ','), ')'],
|
47
|
+
stb(:Associativity, [:relation, :productionrefs, :_])),
|
48
|
+
prod(:Priority, [:ProdRef, plus(ore('>', '='), :ProdRef)],
|
49
|
+
stb(:Precedence, [:first, :rest])),
|
50
|
+
prod(:Priority, [:Priority, ','], stb(:^, [:prio, :_])),
|
51
|
+
prod(:ProdRef, [ore(symbol_name, production_reference)], stb(:^))
|
52
|
+
]
|
53
|
+
|
54
|
+
def rockit_priorities_parser
|
55
|
+
Parse.parser_from_grammar(Grammar.new("RockitPriorities",
|
56
|
+
RockitPrioritiesProds,
|
57
|
+
RockitTokens))
|
58
|
+
end
|
59
|
+
|
60
|
+
RockitProductionsProds = [
|
61
|
+
prod(:Productions, ['Productions', plus(:Prod)],
|
62
|
+
stb(:Productions, [:_, :productions])),
|
63
|
+
prod(:Prod, [symbol_name, arrow, liste(:Alt, '|')],
|
64
|
+
stb(nil, [:nonterminal, :_, :alts])),
|
65
|
+
prod(:Alt, [plus(:Element), maybe(:AstSpec)],
|
66
|
+
stb(nil, [:elements, :astspec])),
|
67
|
+
prod(:Element, [symbol_name], stb(:^)),
|
68
|
+
prod(:Element, [ore(string, regexp)], stb(:ImplicitToken, [:regexp])),
|
69
|
+
prod(:Element, [:Element, '?'], stb(:Maybe, [:element, :_])),
|
70
|
+
prod(:Element, [:Element, '+'], stb(:Plus, [:element, :_])),
|
71
|
+
prod(:Element, [:Element, '*'], stb(:Mult, [:element, :_])),
|
72
|
+
prod(:Element, ['(', liste(:Element, '|'), ')'],
|
73
|
+
stb(:Or, [:_,:elements, :_])),
|
74
|
+
prod(:Element, ['(', plus(:Element), ')'],
|
75
|
+
stb(:Sequence, [:_, :elements, :_])),
|
76
|
+
prod(:Element, ['list(', :Element, ',', :Element, ')'],
|
77
|
+
stb(:List, [:_, :element, :_, :delimiter])),
|
78
|
+
prod(:AstSpec, ['[', maybe(:ProdSpec), maybe(:ElemSpecs), ']'],
|
79
|
+
stb(nil, [:_, :prodspec, :elemspecs, :_])),
|
80
|
+
prod(:ElemSpecs, [': ', liste(:ElemSpec, ',')], stb(:^, [:_, :specs])),
|
81
|
+
prod(:ElemSpec, [ore(/[a-z]+/n, '_')], stb(:^)),
|
82
|
+
prod(:ProdSpec, [ore(symbol_name, '^')], stb(:^, [:name])),
|
83
|
+
]
|
84
|
+
|
85
|
+
def rockit_productions_parser
|
86
|
+
Parse.parser_from_grammar(Grammar.new("RockitProductions",
|
87
|
+
RockitProductionsProds,
|
88
|
+
RockitTokens))
|
89
|
+
end
|
90
|
+
module_function :rockit_productions_parser
|
91
|
+
|
92
|
+
RockitProds = [
|
93
|
+
prod(:Grammar,
|
94
|
+
['Grammar', /[A-Za-z]+([-_]*[A-Za-z\d]+)*/n,
|
95
|
+
maybe(:Tokens), :Productions, maybe(:Priorities)],
|
96
|
+
stb(:Grammar, [:_, :language, :tokens, :productions, :priorities]))
|
97
|
+
] + RockitTokenProds + RockitProductionsProds + RockitPrioritiesProds
|
98
|
+
|
99
|
+
def rockit_grammars_bootstrap_parser
|
100
|
+
Parse.parser_from_grammar(Grammar.new("RockitGrammar",
|
101
|
+
RockitProds, RockitTokens))
|
102
|
+
end
|
103
|
+
module_function :rockit_grammars_bootstrap_parser
|
104
|
+
end
|
105
|
+
|
106
|
+
if __FILE__ == $0
|
107
|
+
$TIME_AND_PUTS_VERBOSE = true
|
108
|
+
grammarfile = ARGV[0] || "rockit-grammar.grammar"
|
109
|
+
parser_filename = ARGV[1] || "rockit_grammars_parser.rb"
|
110
|
+
module_name = ARGV[3] || "Parse"
|
111
|
+
parser_name = as_module_method_named(module_name,
|
112
|
+
ARGV[2] || (ARGV[0] ? "parser" :
|
113
|
+
"rockit_grammars_parser"))
|
114
|
+
time_and_puts("Boostrapping by generating parser from handcoded grammar") {
|
115
|
+
$bootstrap_parser = Parse.rockit_grammars_bootstrap_parser
|
116
|
+
}
|
117
|
+
Parse.generate_parser_from_file_to_file(grammarfile, parser_filename,
|
118
|
+
parser_name, module_name,
|
119
|
+
$bootstrap_parser)
|
120
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# Cache as hash with bounded size. Will delete least recently used (LRU) entry
|
2
|
+
# if full when new key-value pair added.
|
3
|
+
# NOTE: Not thread safe...
|
4
|
+
class BoundedLruCache
|
5
|
+
attr_accessor :max_size
|
6
|
+
|
7
|
+
def initialize(max_size = 2**30-1, anObject = nil)
|
8
|
+
@hash, @uses, @max_size = Hash.new(anObject), Array.new, max_size
|
9
|
+
end
|
10
|
+
|
11
|
+
def []=(key, val)
|
12
|
+
delete_least_recently_used if @hash.length >= @max_size
|
13
|
+
latest_used_key(key)
|
14
|
+
@hash[key] = val
|
15
|
+
end
|
16
|
+
|
17
|
+
def [](key)
|
18
|
+
res = @hash[key]
|
19
|
+
latest_used_key(key) if res
|
20
|
+
res
|
21
|
+
end
|
22
|
+
|
23
|
+
# Delegate undefined methods to the hash
|
24
|
+
def method_missing(methodId, *args)
|
25
|
+
if @hash.respond_to?(methodId)
|
26
|
+
@hash.send(methodId, *args)
|
27
|
+
else
|
28
|
+
super
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def latest_used_key(key)
|
35
|
+
@uses.delete(key)
|
36
|
+
@uses.push(key)
|
37
|
+
end
|
38
|
+
|
39
|
+
def delete_least_recently_used
|
40
|
+
lru = @uses.shift
|
41
|
+
@hash.delete lru
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,302 @@
|
|
1
|
+
require 'rpdf2txt-rockit/sourcecode_dumpable'
|
2
|
+
# A ProductionPriorities object knows the relative priorities between
|
3
|
+
# productions and can resolve which subTreeProducitons are valid at
|
4
|
+
# certain positions in a production. In essence it is a partial ordering
|
5
|
+
# among the productions of a grammar.
|
6
|
+
#
|
7
|
+
# For more info about this kind of tree filtering conflict resolution
|
8
|
+
# see Eelco Visser's thesis from 1997.
|
9
|
+
#
|
10
|
+
# IDEA: Change to a more general handling of associativity using associativity
|
11
|
+
# vectors � la Ziemovit Laski's Bertha parser gen. Each production/rule has
|
12
|
+
# an assoc. vector with ones in the positions were subtrees having the same
|
13
|
+
# precedence is allowed. Left associativity is thus expressed as an assoc.
|
14
|
+
# vector with a one in the first position and zeros in the rest. Right
|
15
|
+
# associativity is zeros in all but the last position etc. See the papers
|
16
|
+
# on Bertha on www.ziemovitlaski.net.
|
17
|
+
#
|
18
|
+
Relation = Struct.new("Relation", :left, :relation, :right)
|
19
|
+
class Relation
|
20
|
+
include SourceCodeDumpable
|
21
|
+
|
22
|
+
def inspect
|
23
|
+
"#{relation.inspect}(#{left.inspect}, #{right.inspect})"
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_src(name = nil, nameHash = {})
|
27
|
+
left_src = nameHash[left] ? nameHash[left] : left.to_src(nil, nameHash)
|
28
|
+
right_src = nameHash[right] ? nameHash[right] : right.to_src(nil, nameHash)
|
29
|
+
assign_to(name,
|
30
|
+
new_of_my_type(as_code(left_src),
|
31
|
+
as_code(relation.inspect),
|
32
|
+
as_code(right_src)))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
class RelationCircularityException < Exception; end
|
36
|
+
|
37
|
+
class ProductionPriorities
|
38
|
+
include SourceCodeDumpable
|
39
|
+
|
40
|
+
def initialize(relations = [])
|
41
|
+
@left, @right, @non_associativity = Hash.new, Hash.new, Hash.new
|
42
|
+
@higher, @equal = Hash.new(nil), Hash.new(nil)
|
43
|
+
@in_conflict, @transitivity_stack = Hash.new(false), []
|
44
|
+
init_relations(relations)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Precedence relations can be :HIGHER, :EQUAL or :LOWER. When not set the
|
48
|
+
# (default) relation is a non-relation, ie. the productions are not related
|
49
|
+
# and can thus not have a (precedence) conflict.
|
50
|
+
def set_precedence(p1, p2, precedenceRelation = :HIGHER)
|
51
|
+
return if p1 == p2
|
52
|
+
@in_conflict[p1] = true; @in_conflict[p2] = true
|
53
|
+
if precedenceRelation == :LOWER
|
54
|
+
precedenceRelation = :HIGHER
|
55
|
+
p1, p2 = p2, p1
|
56
|
+
end
|
57
|
+
if (precedenceRelation == :HIGHER and
|
58
|
+
(higher_precedence?(p2,p1) or equal_precedence?(p2,p1))) or
|
59
|
+
(precedenceRelation == :EQUAL and
|
60
|
+
(higher_precedence?(p2,p1) or higher_precedence?(p1,p2)))
|
61
|
+
raise RelationCircularityException
|
62
|
+
end
|
63
|
+
transitivity(p1, p2, precedenceRelation)
|
64
|
+
set_basic_precedence(p1, p2, precedence_hash(precedenceRelation))
|
65
|
+
if :EQUAL == precedenceRelation
|
66
|
+
set_basic_precedence(p2, p1, precedence_hash(precedenceRelation))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def higher_precedence(a)
|
71
|
+
@higher[a] || []
|
72
|
+
end
|
73
|
+
|
74
|
+
def higher_precedence?(a,b)
|
75
|
+
higher_precedence(a).include?(b)
|
76
|
+
end
|
77
|
+
|
78
|
+
def equal_precedence(a)
|
79
|
+
@equal[a] || []
|
80
|
+
end
|
81
|
+
|
82
|
+
def equal_precedence?(a,b)
|
83
|
+
equal_precedence(a).include?(b) or equal_precedence(b).include?(a)
|
84
|
+
end
|
85
|
+
|
86
|
+
def set_associativity(production1, production2, associativityRelation)
|
87
|
+
@in_conflict[production1] = true; @in_conflict[production2] = true
|
88
|
+
if associativityRelation == :LEFT
|
89
|
+
associativity_hash = @left
|
90
|
+
elsif associativityRelation == :RIGHT
|
91
|
+
associativity_hash = @right
|
92
|
+
else
|
93
|
+
associativity_hash = @non_associativity
|
94
|
+
end
|
95
|
+
begin
|
96
|
+
associativity_hash[production1].push production2
|
97
|
+
rescue NameError
|
98
|
+
associativity_hash[production1] = [production2]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def in_some_conflict?(production)
|
103
|
+
@in_conflict[production]
|
104
|
+
end
|
105
|
+
|
106
|
+
# Is there a conflict with having a child derived from 'subTreeProduction'
|
107
|
+
# at position 'position' in 'production'?
|
108
|
+
def conflict?(subTreeProduction, position, production)
|
109
|
+
# If production yielding subtree has higher precedence
|
110
|
+
# than production yielding root node or left or right associativity
|
111
|
+
# conflict
|
112
|
+
precedence_conflict?(subTreeProduction, production) or
|
113
|
+
left_or_non_associativity_conflict?(subTreeProduction, position,
|
114
|
+
production) or
|
115
|
+
right_or_non_associativity_conflict?(subTreeProduction, position,
|
116
|
+
production)
|
117
|
+
end
|
118
|
+
|
119
|
+
def precedence_conflict?(subTreeProduction, production)
|
120
|
+
higher_precedence?(production, subTreeProduction)
|
121
|
+
end
|
122
|
+
|
123
|
+
def left_or_non_associativity_conflict?(subTreeProduction, position, production)
|
124
|
+
if position == 0 or
|
125
|
+
position != production.elements.length-1
|
126
|
+
# No conflict unless the position is rightmost and there is something
|
127
|
+
# preceding it
|
128
|
+
false
|
129
|
+
else
|
130
|
+
left_associative?(subTreeProduction, production) or
|
131
|
+
non_associative?(subTreeProduction, production)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def right_or_non_associativity_conflict?(subTreeProduction, position,
|
136
|
+
production)
|
137
|
+
if position != 0 or
|
138
|
+
position == production.elements.length-1
|
139
|
+
# No conflict unless the position is leftmost and there is something
|
140
|
+
# after it
|
141
|
+
false
|
142
|
+
else
|
143
|
+
right_associative?(subTreeProduction, production) or
|
144
|
+
non_associative?(subTreeProduction, production)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def each(&block)
|
149
|
+
each_production_pair(@left, :LEFT, &block)
|
150
|
+
each_production_pair(@right, :RIGHT, &block)
|
151
|
+
each_production_pair(@non_associativity, :NONASSOC, &block)
|
152
|
+
each_production_pair(@higher, :HIGHER, &block)
|
153
|
+
each_production_pair(@equal, :EQUAL, &block)
|
154
|
+
end
|
155
|
+
|
156
|
+
def each_production_pair(hash, relation, &block)
|
157
|
+
hash.each do |p1, ps|
|
158
|
+
ps.each do |p2|
|
159
|
+
block.call(Relation.new(p1, relation, p2))
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def to_src(name = nil, nameHash = {})
|
165
|
+
str = relations.to_src("relations", nameHash) + "\n"
|
166
|
+
str + assign_to(name, new_of_my_type(as_code("relations")))
|
167
|
+
end
|
168
|
+
|
169
|
+
def relations
|
170
|
+
relations = Array.new
|
171
|
+
self.each {|relation| relations.push relation}
|
172
|
+
relations
|
173
|
+
end
|
174
|
+
|
175
|
+
protected
|
176
|
+
|
177
|
+
def set_basic_precedence(a, b, hash)
|
178
|
+
begin
|
179
|
+
hash[a].push(b) unless hash[a].include?(b)
|
180
|
+
rescue NameError
|
181
|
+
hash[a] = [b]
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def precedence_hash(relation)
|
186
|
+
case relation
|
187
|
+
when :HIGHER
|
188
|
+
@higher
|
189
|
+
when :EQUAL
|
190
|
+
@equal
|
191
|
+
else
|
192
|
+
raise ArgumentError, "Invalid relation #{relation.inspect}"
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def transitivity(a, b, relation)
|
197
|
+
return if @transitivity_stack.include?([a,b,relation])
|
198
|
+
@transitivity_stack.push [a,b,relation]
|
199
|
+
higher_precedence(b).each {|c| set_precedence(a,c, :HIGHER)}
|
200
|
+
one_step_higher_than(a).each {|h| set_precedence(h,b, :HIGHER)}
|
201
|
+
equal_precedence(a).each {|c| set_precedence(c,b, relation)}
|
202
|
+
equal_precedence(b).each {|c| set_precedence(a,c, relation)}
|
203
|
+
if relation == :EQUAL
|
204
|
+
higher_precedence(a).each {|c| set_precedence(b,c, :HIGHER)}
|
205
|
+
end
|
206
|
+
@transitivity_stack.pop
|
207
|
+
end
|
208
|
+
|
209
|
+
def one_step_higher_than(a)
|
210
|
+
@higher.keys.select {|p| p != a and higher_precedence?(p, a)}
|
211
|
+
end
|
212
|
+
|
213
|
+
def init_relations(relations)
|
214
|
+
relations.each do |relation|
|
215
|
+
case relation.relation
|
216
|
+
when :LEFT, :RIGHT, :NONASSOC
|
217
|
+
set_associativity(relation.left, relation.right, relation.relation)
|
218
|
+
when :HIGHER, :LOWER, :EQUAL
|
219
|
+
set_precedence(relation.left, relation.right, relation.relation)
|
220
|
+
else
|
221
|
+
raise ArgumentError, "Unknown relation #{relation.inspect}"
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def left_associative?(production1, production2)
|
227
|
+
check_associativity(production1, production2, @left) or
|
228
|
+
check_associativity(production2, production1, @left)
|
229
|
+
end
|
230
|
+
|
231
|
+
def right_associative?(production1, production2)
|
232
|
+
check_associativity(production1, production2, @right) or
|
233
|
+
check_associativity(production2, production1, @right)
|
234
|
+
end
|
235
|
+
|
236
|
+
def non_associative?(production1, production2)
|
237
|
+
check_associativity(production1, production2, @non_associativity) or
|
238
|
+
check_associativity(production2, production1, @non_associativity)
|
239
|
+
end
|
240
|
+
|
241
|
+
def check_associativity(production1, production2, associativityHash)
|
242
|
+
begin
|
243
|
+
associativityHash[production1].include?(production2)
|
244
|
+
rescue NameError
|
245
|
+
false
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def precedence(production1, production2)
|
250
|
+
return :EQUAL if production1 == production2
|
251
|
+
begin
|
252
|
+
@precedence[production1][production2]
|
253
|
+
rescue NameError
|
254
|
+
nil # For the case when production1 is not related to anyone
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# Short hand funcs for specifying priorities
|
260
|
+
def priorities(relations = [], *rest)
|
261
|
+
relations += rest
|
262
|
+
ProductionPriorities.new(relations.flatten.flatten)
|
263
|
+
end
|
264
|
+
|
265
|
+
def left(p1, *rest)
|
266
|
+
assocs(:LEFT, true, p1, *rest)
|
267
|
+
end
|
268
|
+
|
269
|
+
def right(p1, *rest)
|
270
|
+
assocs(:RIGHT, true, p1, *rest)
|
271
|
+
end
|
272
|
+
|
273
|
+
def nonassoc(p1, *rest)
|
274
|
+
assocs(:NONASSOC, true, p1, *rest)
|
275
|
+
end
|
276
|
+
|
277
|
+
def assocs(rel, allPairs, p1, *rest)
|
278
|
+
return assocs(rel, allPairs, *(p1+rest)) if p1.kind_of?(Array)
|
279
|
+
if rest.length == 0
|
280
|
+
assocs(rel, allPairs, p1, p1)
|
281
|
+
elsif rest.length == 1
|
282
|
+
[Relation.new(p1, rel, rest[0])]
|
283
|
+
else
|
284
|
+
if allPairs
|
285
|
+
rest.map {|prod| assocs(rel, allPairs, p1, prod)} +
|
286
|
+
assocs(rel, allPairs, rest.shift, *rest)
|
287
|
+
else
|
288
|
+
rest.unshift p1
|
289
|
+
(0...(rest.length-1)).map do |i|
|
290
|
+
assocs(rel, allPairs, rest[i], rest[i+1])
|
291
|
+
end.flatten
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
def decreasing_precedence(p1, p2, *rest)
|
297
|
+
assocs(:HIGHER, false, p1, p2, *rest)
|
298
|
+
end
|
299
|
+
|
300
|
+
def equal_precedence(p1, p2, *rest)
|
301
|
+
assocs(:EQUAL, false, p1, p2, *rest)
|
302
|
+
end
|