fbtxt-parser 0.9.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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +4 -0
  3. data/Manifest.txt +50 -0
  4. data/README.md +22 -0
  5. data/Rakefile +53 -0
  6. data/lib/fbtxt/parser/debuggable.rb +53 -0
  7. data/lib/fbtxt/parser/lexer-logger.rb +20 -0
  8. data/lib/fbtxt/parser/lexer-on_goal.rb +167 -0
  9. data/lib/fbtxt/parser/lexer-on_group_def.rb +31 -0
  10. data/lib/fbtxt/parser/lexer-on_prop_lineup.rb +79 -0
  11. data/lib/fbtxt/parser/lexer-on_prop_misc.rb +123 -0
  12. data/lib/fbtxt/parser/lexer-on_prop_penalties.rb +40 -0
  13. data/lib/fbtxt/parser/lexer-on_round_def.rb +37 -0
  14. data/lib/fbtxt/parser/lexer-on_top.rb +133 -0
  15. data/lib/fbtxt/parser/lexer-prep_doc.rb +131 -0
  16. data/lib/fbtxt/parser/lexer-prep_line.rb +63 -0
  17. data/lib/fbtxt/parser/lexer-tokenize.rb +468 -0
  18. data/lib/fbtxt/parser/lexer.rb +231 -0
  19. data/lib/fbtxt/parser/lexer_buffer.rb +68 -0
  20. data/lib/fbtxt/parser/lexer_token.rb +126 -0
  21. data/lib/fbtxt/parser/parse_tree--core.rb +29 -0
  22. data/lib/fbtxt/parser/parse_tree-match.rb +309 -0
  23. data/lib/fbtxt/parser/parse_tree-props.rb +233 -0
  24. data/lib/fbtxt/parser/parse_tree.rb +202 -0
  25. data/lib/fbtxt/parser/parser-runtime.rb +379 -0
  26. data/lib/fbtxt/parser/parser-top.rb +102 -0
  27. data/lib/fbtxt/parser/parser.rb +2343 -0
  28. data/lib/fbtxt/parser/token-date--helpers.rb +130 -0
  29. data/lib/fbtxt/parser/token-date--names.rb +108 -0
  30. data/lib/fbtxt/parser/token-date.rb +200 -0
  31. data/lib/fbtxt/parser/token-date_duration.rb +171 -0
  32. data/lib/fbtxt/parser/token-geo.rb +134 -0
  33. data/lib/fbtxt/parser/token-goals--helpers.rb +114 -0
  34. data/lib/fbtxt/parser/token-goals.rb +306 -0
  35. data/lib/fbtxt/parser/token-group.rb +29 -0
  36. data/lib/fbtxt/parser/token-note.rb +40 -0
  37. data/lib/fbtxt/parser/token-prop.rb +309 -0
  38. data/lib/fbtxt/parser/token-prop_name.rb +83 -0
  39. data/lib/fbtxt/parser/token-round.rb +88 -0
  40. data/lib/fbtxt/parser/token-score--helpers.rb +189 -0
  41. data/lib/fbtxt/parser/token-score.rb +60 -0
  42. data/lib/fbtxt/parser/token-score_full.rb +331 -0
  43. data/lib/fbtxt/parser/token-score_fuller.rb +434 -0
  44. data/lib/fbtxt/parser/token-score_legs.rb +59 -0
  45. data/lib/fbtxt/parser/token-status.rb +192 -0
  46. data/lib/fbtxt/parser/token-status_inline.rb +112 -0
  47. data/lib/fbtxt/parser/token-text.rb +221 -0
  48. data/lib/fbtxt/parser/token-time.rb +144 -0
  49. data/lib/fbtxt/parser/token.rb +224 -0
  50. data/lib/fbtxt/parser/version.rb +24 -0
  51. data/lib/fbtxt/parser.rb +140 -0
  52. metadata +145 -0
@@ -0,0 +1,68 @@
1
+
2
+ module Fbtxt
3
+
4
+ ## note - Tokens was placed inside Lexer - keep "top-level" for now inside Fbtxt
5
+ ## for easier reuse with (new) lexer variants!!
6
+
7
+ ## transforms
8
+ ##
9
+ ## Netherlands 1-2 (1-1) England
10
+ ## => text => team
11
+ ## score|vs
12
+ ## text => team
13
+
14
+
15
+
16
+ ## token iter/find better name
17
+ ## e.g. TokenBuffer/Scanner or such ??
18
+ class Tokens
19
+ def initialize( tokens )
20
+ @tokens = tokens
21
+ @pos = 0
22
+ end
23
+
24
+ def pos() @pos; end
25
+ def eos?() @pos >= @tokens.size; end
26
+
27
+
28
+
29
+
30
+ ## pattern e.g. [:TEXT, [:VS,:SCORE], :TEXT]
31
+ def match?( *pattern )
32
+ ## puts " starting match? #{pattern.inspect} @ #{@pos}"
33
+ pattern.each_with_index do |types,offset|
34
+ tok = peek(offset)
35
+ return false if tok.nil? ## no more tokens (cannot match)
36
+
37
+ ## if single symbol wrap in array
38
+ types = types.is_a?(Array) ? types : [types]
39
+ return false unless types.include?( tok.type )
40
+ end
41
+ true
42
+ end
43
+
44
+
45
+
46
+ def cur() peek(0); end
47
+
48
+ def peek(offset=1)
49
+ ## return nil if eos
50
+ if @pos+offset >= @tokens.size
51
+ nil
52
+ else
53
+ @tokens[@pos+offset]
54
+ end
55
+ end
56
+
57
+ def next
58
+ # if @pos >= @tokens.size
59
+ # raise ArgumentError, "end of array - #{@pos} >= #{@tokens.size}"
60
+ # end
61
+ # throw (standard) end of iteration here why? why not?
62
+
63
+ t = @tokens[@pos]
64
+ @pos += 1
65
+ t
66
+ end
67
+ end # class Tokens
68
+ end # module Fbtxt
@@ -0,0 +1,126 @@
1
+ module Fbtxt
2
+ class Lexer
3
+
4
+
5
+
6
+ class Token
7
+
8
+ ## Token.newline( lineno: 1, offset: [1,2] )
9
+ ## maps to =>
10
+ ## Token.new( :NEWLINE, "\n", lineno: 1, offset: [1,2])
11
+ ##
12
+ ## use self.nl ?
13
+ def self.newline( lineno:, offset: [])
14
+ new( :NEWLINE, "\n", lineno: lineno, offset: offset )
15
+ end
16
+
17
+ ## Token.literal( ",", lineno: 4, offset: [5,6])
18
+ ## # maps to =>
19
+ ## Token.new( ",", ",", lineno: 4, offset: [5,6])
20
+ ##
21
+ ## use self.lit?
22
+ def self.literal( literal, lineno:, offset: [])
23
+ new( literal, literal, lineno: lineno, offset: offset )
24
+ end
25
+
26
+ ## or use virt or pseudo - why? why not?
27
+ def self.virtual( type, lineno:, offset: [])
28
+ ## note - offset (start/end) should be same number (zero-width assertions!!)
29
+ ## e.g. :GOALS_COMPAT, "<|GOALS_COMPAT|>"
30
+ new( type, '', lineno: lineno, offset: offset )
31
+ end
32
+
33
+
34
+ attr_reader :type, :text,
35
+ :lineno, :offset
36
+
37
+ def initialize( type, text='',
38
+ lineno:, offset: [],
39
+ value: nil )
40
+ @type = type
41
+ @text = text # note - lexeme (string from source)
42
+ @lineno = lineno # note - lineno (integer number - not line as string) !!!
43
+
44
+ raise TypeError, "type Array required for offset; got #{offset.inspect}" unless offset.is_a?( Array )
45
+ @offset = offset # note - for now char offset [start,end] in line (NOT absolute!!)
46
+ # maybe latter add MatchData#byteoffset instead - why? why not?
47
+ @value = value # might be (union of) string/array/hash
48
+ end
49
+
50
+ def value
51
+ ## note - if value is not set (nil) return text (lexeme)
52
+ ## no need to duplicate text as value
53
+ @value.nil? ? @text : @value
54
+ end
55
+
56
+
57
+ ## note: do NOT use as_text/text to avoid confusion with (raw) text (lexeme)
58
+ ##
59
+ ## use
60
+ ## as_str -- value (as String)
61
+ ## as_int -- value (as Integer)
62
+ ## as_hash -- value (as Hash)
63
+ ## as_ary -- value (as Array)
64
+
65
+ def as_str
66
+ raise TypeError, "token value #{@value.inspect} is #{@value.class.name} NOT string; sorry" if @value && !@value.is_a?(String)
67
+ ## note - if value is not set (nil) return text (lexeme)
68
+ ## no need to duplicate text as value
69
+ @value.nil? ? @text : @value
70
+ end
71
+
72
+ def as_int
73
+ raise TypeError, "token value #{@value.inspect} is #{@value.class.name} NOT integer; sorry" if !@value.is_a?(Integer)
74
+ @value
75
+ end
76
+
77
+ def as_hash
78
+ raise TypeError, "token value #{@value.inspect} is #{@value.class.name} NOT hash; sorry" if !@value.is_a?(Hash)
79
+ @value
80
+ end
81
+
82
+ def as_ary
83
+ raise TypeError, "token value #{@value.inspect} is #{@value.class.name} NOT array; sorry" if !@value.is_a?(Array)
84
+ @value
85
+ end
86
+
87
+
88
+ def to_legacy
89
+ ## return old "legacy" array format
90
+ if @value.nil?
91
+ [@type, @text]
92
+ else
93
+ [@type, [@text, @value]]
94
+ end
95
+ end
96
+
97
+
98
+ ## pretty print
99
+ def pretty_print( printer )
100
+ ## check for literal e.g. "," etc.
101
+ if @type.is_a?( String ) && @type == @text && @value.nil?
102
+ printer.text( "[#{@type.inspect}" )
103
+ elsif @type.is_a?( Symbol ) && @text == '' && @value.nil?
104
+ ## assume virtual token (zero-width)
105
+ ## use <!...!> style
106
+ printer.text( "[<|#{@type}|>" )
107
+ else
108
+ printer.text( "[#{@type.inspect} #{@text.inspect}" )
109
+ printer.text( ", #{value.inspect}") if @value
110
+ end
111
+
112
+
113
+ printer.text( " @#{@lineno}" )
114
+ ## note - for now print only start_offset (offset[0])
115
+ ## to keep dump/output shorter
116
+ ## note - start counting columns at one (NOT zero), thus, add +1 !!
117
+ printer.text( ":#{@offset[0]+1}" ) if @offset.is_a?(Array) && @offset.size == 2
118
+ printer.text( "]" )
119
+ end
120
+
121
+ end # class Token
122
+
123
+
124
+
125
+ end # class Lexer
126
+ end # module Fbtxt
@@ -0,0 +1,29 @@
1
+
2
+
3
+ # module JsonSerializable
4
+ # def to_json(*args)
5
+ # as_json.to_json(*args)
6
+ # end
7
+ # end
8
+
9
+ ###
10
+ ## add as_json to Object/Array/Hash
11
+ class Object
12
+ def as_json(*)
13
+ self
14
+ end
15
+ end
16
+
17
+ class Array
18
+ def as_json(*)
19
+ map { |v| v.as_json }
20
+ end
21
+ end
22
+
23
+ class Hash
24
+ def as_json(*)
25
+ each_with_object({}) do |(k, v), h|
26
+ h[k.to_s] = v.as_json
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,309 @@
1
+ module Fbtxt
2
+ class Parser
3
+
4
+
5
+
6
+ MatchLineBye = Struct.new( :team, :note ) do
7
+ def pretty_print( q )
8
+ q.group( 4, '<MatchLineBye ', '>') do ## group( indent, open, close)
9
+ q.text( "#{team} bye")
10
+ if note
11
+ q.text( " note=" )
12
+ q.pp( note )
13
+ end
14
+ end
15
+ end
16
+ end # MatchLineBye
17
+
18
+
19
+
20
+
21
+ MatchLineLegs = Struct.new( :team1, :team2,
22
+ :score ) do
23
+ def pretty_print( q )
24
+ q.group( 4, '<MatchLineLegs ', '>') do ## group( indent, open, close)
25
+ q.text( "#{team1} v #{team2}")
26
+
27
+ members.zip(values) do |name, value|
28
+ next if [:team1, :team2].include?( name )
29
+ next if value.nil?
30
+
31
+ q.breakable
32
+ q.text( "#{name}=" )
33
+ q.pp( value )
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+
40
+ #
41
+ # note: use two status attributes for now
42
+ # 1) inline_status and 2) (note_)status
43
+ # for now e.g. A abd. B vs A v B [abadoned]
44
+ # A 3-0 awd B vs A 3-0 B [awarded]
45
+ # note - BOTH might be present at the same time
46
+
47
+
48
+ MatchLine = Struct.new( :header, :tty, ## tty = TELETYPE MODE for teams and score!!
49
+ :num, :date, :time, :time_local, :year,
50
+ :team1, :team2,
51
+ :score,
52
+ :status, :status_inline, :status_note,
53
+ :round_inline_short, :round_inline_big,
54
+ :geo,
55
+ :neutral, ## true/false - NOT -home/away - neutral ground
56
+ :note,
57
+ :att ) do ## change to geos - why? why not?
58
+
59
+
60
+ def as_json(*)
61
+ h = { 'team1' => team1.as_json,
62
+ 'team2' => team2.as_json,
63
+ }
64
+
65
+ members.zip(values) do |name, value|
66
+ next if [:team1, :team2].include?( name )
67
+ next if value.nil?
68
+
69
+ h[name.to_s] = value.as_json
70
+ end
71
+
72
+ ['<MatchLine>', h]
73
+ end
74
+
75
+
76
+ def pretty_print( q )
77
+ q.group( 4, '<MatchLine ', '>') do ## group( indent, open, close)
78
+ q.text( "#{team1} v #{team2}")
79
+
80
+ members.zip(values) do |name, value|
81
+ next if [:team1, :team2].include?( name )
82
+ next if value.nil?
83
+
84
+ q.breakable
85
+ q.text( "#{name}=" )
86
+ q.pp( value )
87
+ end
88
+ end
89
+ end
90
+
91
+ end # struct MatchLine
92
+
93
+
94
+
95
+ GoalLine = Struct.new( :goals ) do
96
+
97
+ def as_json(*)
98
+ ['<GoalLine>', { 'goals' => goals.as_json }]
99
+ end
100
+
101
+ def pretty_print( q )
102
+ q.group( 4, '<GoalLine ', '>') do
103
+ q.text( "goals=" )
104
+ q.pp( goals )
105
+ end
106
+ end
107
+ end # GoalLine
108
+
109
+
110
+
111
+ ###
112
+ ## change/rename Goal to GoalScorer - why? why not?
113
+
114
+ Goal = Struct.new( :player, :minutes, :count ) do
115
+
116
+ def as_json(*)
117
+ h = { 'player' => player.as_json }
118
+
119
+ h['count'] = count.as_json if count
120
+ h['minutes'] = minutes.as_json if minutes && !minutes.empty?
121
+
122
+ ## ['<Goal>', h]
123
+ h # note - return "inline" json object
124
+ end
125
+
126
+
127
+ def to_s
128
+ buf = String.new
129
+ buf << "#{player}"
130
+ if count
131
+ buf << (" " + count.inspect + ",")
132
+ else
133
+ if minutes.nil? || minutes.empty?
134
+ ## add nothing if no minutes available/present
135
+ else
136
+ buf << " "
137
+ buf << minutes.map { |min| min.to_s }.join(' ')
138
+ end
139
+ end
140
+ buf
141
+ end
142
+
143
+ def pretty_print( q )
144
+ q.text( to_s )
145
+ end
146
+ end
147
+
148
+
149
+ ## check - use a different name e.g. GoalLineScore or such - why? why not?
150
+ GoalLineAlt = Struct.new( :goals ) do
151
+ def pretty_print( q )
152
+ q.group( 4, '<GoalLineAlt ', '>') do
153
+ q.text( "goals=" )
154
+ q.pp( goals )
155
+ end
156
+ end
157
+ end # GoalLineAlt
158
+
159
+
160
+ ##
161
+ ## score and player REQUIRED
162
+ ## note - (goal) minute is optional
163
+ ## if no minute goal type is optional e.g. "standalone" (p), (og), etc.
164
+
165
+ GoalAlt = Struct.new( :score, :player, :minute, :goal_type ) do
166
+ def to_s
167
+ buf = String.new
168
+ buf << "#{score[0]}-#{score[1]}"
169
+ buf << " #{player}"
170
+ buf << " #{minute}" if minute
171
+ buf << " #{goal_type}" if goal_type
172
+ buf
173
+ end
174
+
175
+ def pretty_print( q )
176
+ q.text( to_s )
177
+ end
178
+ end # GoalAlt
179
+
180
+
181
+
182
+ GoalLineCompat = Struct.new( :goals ) do
183
+ def pretty_print( q )
184
+ q.group( 4, '<GoalLineCompat ', '>') do
185
+ q.text( "goals=" )
186
+ q.pp( goals )
187
+ end
188
+ end
189
+ end # GoalLineCompat
190
+
191
+
192
+ ##
193
+ ## minute and player REQUIRED
194
+ ## note - score (e.g. 1-1) is optional
195
+ ## goal type is optional e.g. "standalone" (p), (og), etc.
196
+
197
+ GoalCompat = Struct.new( :score, :player, :minute, :goal_type ) do
198
+ def to_s
199
+ buf = String.new
200
+ buf << "#{minute}"
201
+ buf << " #{player}"
202
+ buf << " #{goal_type}" if goal_type
203
+ buf << " #{score[0]}-#{score[1]}" if score
204
+ buf
205
+ end
206
+
207
+ def pretty_print( q )
208
+ q.text( to_s )
209
+ end
210
+ end
211
+
212
+
213
+
214
+ ## FIX/FIX/FIX
215
+ ## split into Minute and
216
+ ## GoalMinute (Minute+GoalType)
217
+ ##
218
+ ## fix - move :og, :pen to Goal if possible - why? why not?
219
+ ## or change to GoalMinute ???
220
+ ##
221
+ ## fix!!! split into GoalMinute and Minute
222
+ ## goal_minute incl. :og, :pen, :freekick, :header,
223
+ ## seconds etc.
224
+ ##
225
+ ## GoalMinute = Minute + GoalType !!!
226
+
227
+ GoalMinute = Struct.new( :m, :offset, :secs,
228
+ :og, :pen, :header, :freekick,
229
+ ) do
230
+
231
+
232
+ def as_json(*) to_s; end
233
+
234
+ def to_s
235
+ buf = String.new
236
+ buf << "#{m}"
237
+ buf << "'"
238
+ buf << "+#{offset}" if offset
239
+ buf << "(og)" if og
240
+ buf << "(p)" if pen
241
+ buf << "(f)" if freekick
242
+ buf << "(h)" if header
243
+ buf << " (#{secs} secs)" if secs
244
+ buf
245
+ end
246
+
247
+ def pretty_print( q )
248
+ q.text( to_s )
249
+ end
250
+
251
+ ### quick hack:
252
+ ### split struct into Minute+GoalType structs
253
+ def to_minute
254
+ Minute.new( m: m,
255
+ offset: offset,
256
+ secs: secs )
257
+ end
258
+
259
+ def to_goal_type
260
+ if og || pen || header || freekick
261
+ GoalType.new( og: og,
262
+ pen: pen,
263
+ header: header,
264
+ freekick: freekick )
265
+ else
266
+ nil ## note - return nil; if no goal type present!!
267
+ end
268
+ end
269
+ end
270
+
271
+
272
+ GoalType = Struct.new( :og, :pen, :header, :freekick ) do
273
+ def to_s
274
+ buf = String.new
275
+ buf << "(og)" if og
276
+ buf << "(pen)" if pen
277
+ buf << "(f)" if freekick
278
+ buf << "(h)" if header
279
+ buf
280
+ end
281
+
282
+ def pretty_print( q )
283
+ q.text( to_s )
284
+ end
285
+ end # GoalType
286
+
287
+
288
+
289
+ Minute = Struct.new( :m, :offset, :secs ) do
290
+ def as_json(*) to_s; end
291
+
292
+ def to_s
293
+ buf = String.new
294
+ buf << "#{m}"
295
+ buf << "'"
296
+ buf << "+#{offset}" if offset
297
+ buf << "/#{secs} secs" if secs
298
+ buf
299
+ end
300
+
301
+ def pretty_print( q )
302
+ q.text( to_s )
303
+ end
304
+ end # Minute
305
+
306
+
307
+
308
+ end # class Parser
309
+ end # module Fbtxt