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,37 @@
1
+ module Fbtxt
2
+ class Lexer
3
+
4
+
5
+
6
+ ### note - add comma (,) as optional separator
7
+ ROUND_DEF_RE = Regexp.union( SPACES_RE,
8
+ DURATION_RE, # note - duration MUST match before date
9
+ DATE_RE, ## note - date must go before time (e.g. 12.12. vs 12.12)
10
+ / (?<sym> [:|,] ) /x,
11
+ ANY_RE
12
+ )
13
+
14
+
15
+ def _on_round_def( m, ctx: ) ## note - m is MatchData object
16
+
17
+
18
+ if m[:spaces] || m[:space]
19
+ nil ## skip spaces
20
+ elsif m[:date]
21
+ Token.new(:DATE, m[:date],
22
+ lineno: ctx.lineno, offset: m.offset(:date),
23
+ value: _build_date(m))
24
+ elsif m[:duration]
25
+ Token.new(:DURATION, m[:duration],
26
+ lineno: ctx.lineno, offset: m.offset(:duration),
27
+ value: _build_duration( m ))
28
+ elsif m[:sym]
29
+ Token.literal( m[:sym], lineno: ctx.lineno, offset: m.offset(:sym))
30
+ else
31
+ ctx.warn_on_else( m, mode: 'ROUND_DEF' )
32
+ nil
33
+ end
34
+ end
35
+
36
+ end ## class Lexer
37
+ end ## module Fbtxt
@@ -0,0 +1,133 @@
1
+ module Fbtxt
2
+ class Lexer
3
+
4
+
5
+ def _on_top( m, ctx: ) ## note - m is MatchData object
6
+
7
+ ## note - top-level (for now always) assumes TEAM for TEXT match!!
8
+ ## fix/fix/fix change TEXT_RE/:text to TEAM_RE/:team !!!
9
+
10
+ if m[:space] || m[:spaces]
11
+ nil ## skip space(s)
12
+ elsif m[:text] then Token.new(:TEAM, m[:text],
13
+ lineno: ctx.lineno, offset: m.offset(:text))
14
+ elsif m[:team_home] then Token.new(:TEAM_HOME, m[:team_home],
15
+ lineno: ctx.lineno, offset: m.offset(:team_home))
16
+ elsif m[:team_away] then Token.new(:TEAM_AWAY, m[:team_away],
17
+ lineno: ctx.lineno, offset: m.offset(:team_away))
18
+ elsif m[:team_neutral] then Token.new(:TEAM_NEUTRAL, m[:team_neutral],
19
+ lineno: ctx.lineno, offset: m.offset(:team_neutral))
20
+
21
+ ## (match) status e.g. cancelled, awarded, etc.
22
+ ## inline: w/o - walkover
23
+ ## n/p - not played
24
+ ## bye
25
+ ## abd/abd. - abandoned
26
+ ## void
27
+ ## susp/susp. - suspended
28
+ ## ppd/ppd. or postp/postp. - postponed
29
+ ## awd/awd. - awarded
30
+ ## canc/canc. - cancelled/canceled
31
+ elsif m[:inline_wo] then Token.new(:INLINE_WO, m[:inline_wo],
32
+ lineno: ctx.lineno, offset: m.offset(:inline_wo))
33
+ elsif m[:inline_np] then Token.new(:INLINE_NP, m[:inline_np],
34
+ lineno: ctx.lineno, offset: m.offset(:inline_np))
35
+ elsif m[:inline_bye] then Token.new(:INLINE_BYE, m[:inline_bye],
36
+ lineno: ctx.lineno, offset: m.offset(:inline_bye))
37
+ elsif m[:inline_abd] then Token.new(:INLINE_ABD, m[:inline_abd],
38
+ lineno: ctx.lineno, offset: m.offset(:inline_abd))
39
+ elsif m[:inline_void] then Token.new(:INLINE_VOID, m[:inline_void],
40
+ lineno: ctx.lineno, offset: m.offset(:inline_void))
41
+ elsif m[:inline_susp] then Token.new(:INLINE_SUSP, m[:inline_susp],
42
+ lineno: ctx.lineno, offset: m.offset(:inline_susp))
43
+ elsif m[:inline_ppd] then Token.new(:INLINE_PPD, m[:inline_ppd],
44
+ lineno: ctx.lineno, offset: m.offset(:inline_ppd))
45
+ elsif m[:inline_awd] then Token.new(:INLINE_AWD, m[:inline_awd],
46
+ lineno: ctx.lineno, offset: m.offset(:inline_awd))
47
+ elsif m[:inline_canc] then Token.new(:INLINE_CANC, m[:inline_canc],
48
+ lineno: ctx.lineno, offset: m.offset(:inline_canc))
49
+
50
+ elsif m[:inline_round_short] then Token.new(:INLINE_ROUND_SHORT, m[0],
51
+ lineno: ctx.lineno, offset: m.offset(0),
52
+ value: m[:inline_round_text])
53
+ elsif m[:inline_round_big] then Token.new(:INLINE_ROUND_BIG, m[0],
54
+ lineno: ctx.lineno, offset: m.offset(0),
55
+ value: m[:inline_round_text])
56
+
57
+ elsif m[:status] then Token.new(:STATUS, m[:status],
58
+ lineno: ctx.lineno, offset: m.offset(:status),
59
+ value: _build_status( m ))
60
+ elsif m[:note]
61
+ ### todo/check:
62
+ ## use value hash - why? why not? or simplify to:
63
+ ## [:NOTE, [m[:note], {note: m[:note] } ]]
64
+ Token.new(:NOTE, m[:note],
65
+ lineno: ctx.lineno, offset: m.offset(:note))
66
+
67
+ elsif m[:attendance]
68
+ att = {}
69
+ att[:value] = m[:value].gsub( '_', '' ).to_i(10)
70
+ ## note - for token id use INLINE_ATTENDANCE (ATTENDANCE in use for prop!!!)
71
+ Token.new(:INLINE_ATTENDANCE, m[:attendance],
72
+ lineno: ctx.lineno, offset: m.offset(:attendance),
73
+ value: att)
74
+
75
+ elsif m[:time] then Token.new(:TIME, m[:time],
76
+ lineno: ctx.lineno, offset: m.offset(:time),
77
+ value: _build_time(m))
78
+ elsif m[:date] then Token.new(:DATE, m[:date],
79
+ lineno: ctx.lineno, offset: m.offset(:date),
80
+ value: _build_date(m))
81
+ elsif m[:date_legs] then Token.new(:DATE_LEGS, m[:date_legs],
82
+ lineno: ctx.lineno, offset: m.offset(:date_legs),
83
+ value: _build_date_legs(m))
84
+
85
+ elsif m[:score_legs] then Token.new(:SCORE_LEGS, m[:score_legs],
86
+ lineno: ctx.lineno, offset: m.offset(:score_legs),
87
+ value: _build_score_legs( m ))
88
+ elsif m[:score_full] then Token.new(:SCORE_FULL, m[:score_full],
89
+ lineno: ctx.lineno, offset: m.offset(:score_full),
90
+ value: _build_score_full( m ))
91
+ elsif m[:score_fuller] then Token.new(:SCORE_FULLER, m[:score_fuller],
92
+ lineno: ctx.lineno, offset: m.offset(:score_fuller),
93
+ value: _build_score_fuller( m ))
94
+ elsif m[:score_fuller_more] then Token.new(:SCORE_FULLER_MORE, m[:score_fuller_more],
95
+ lineno: ctx.lineno, offset: m.offset(:score_fuller_more),
96
+ value: _build_score_fuller_more( m ))
97
+ elsif m[:score] then Token.new(:SCORE, m[:score],
98
+ lineno: ctx.lineno, offset: m.offset(:score),
99
+ value: _build_score( m ))
100
+ elsif m[:score_awd] then Token.new(:SCORE_AWD, m[:score_awd],
101
+ lineno: ctx.lineno, offset: m.offset(:score_awd),
102
+ value: _build_score_awd( m ))
103
+ elsif m[:score_abd] then Token.new(:SCORE_ABD, m[:score_abd],
104
+ lineno: ctx.lineno, offset: m.offset(:score_abd),
105
+ value: _build_score_abd( m ))
106
+
107
+ elsif m[:vs] then Token.new(:VS, m[:vs],
108
+ lineno: ctx.lineno, offset: m.offset(:vs))
109
+ elsif m[:sym]
110
+ case m[:sym] ## return symbols "inline" as is - why? why not?
111
+ when '@' ## enter geo mode
112
+ _trace( 'ENTER GEO_RE MODE' )
113
+ @re = GEO_RE
114
+ @geo_count = 0
115
+ Token.literal( m[:sym], lineno: ctx.lineno, offset: m.offset(:sym))
116
+ when '(' ## enter goal scorer mode on "free-floating" open paranthesis!!!
117
+ _trace( 'ENTER GOAL_RE MODE' )
118
+ @re = GOAL_RE
119
+ ## note - eat-up ( for now; do NOT pass along as token
120
+ ## pass along "virutal" INLINE GOALS - why? why not?
121
+ Token.virtual( :INLINE_GOALS, lineno: ctx.lineno )
122
+ else
123
+ Token.literal( m[:sym], lineno: ctx.lineno, offset: m.offset(:sym))
124
+ end
125
+ else
126
+ ctx.warn_on_else( m )
127
+ nil
128
+ end
129
+ end
130
+
131
+
132
+ end ## class Lexer
133
+ end ## module Fbtxt
@@ -0,0 +1,131 @@
1
+ module Fbtxt
2
+ class Lexer
3
+
4
+
5
+
6
+ HTML_COMMENT_RE = %r{ <!--
7
+ .*? ## note - use non-greedy/lazy *? match
8
+ -->
9
+ }xm ## note - turn on multi-line (newline) match (for dot (.))
10
+
11
+
12
+ ##
13
+ ## check for "literal" (multi-line) note blocks
14
+ ## eg. nb: or note:
15
+ ## space required after double colon - why? why not?
16
+ PREPROC_NOTA_BENE_RE = %r{
17
+ ^
18
+ [ ]* (?: nb | note) [ ]* : [ ]+
19
+ .+? ## non-greedy
20
+
21
+ ## positive lookahead
22
+ ## note - must end with blank line or end-of-file/document
23
+ (?= \n[ ]*\n
24
+ | \z
25
+ )
26
+ }xim
27
+
28
+
29
+
30
+ ##
31
+ ## note - [] block may NOT incl. square brackets
32
+ ## what about comments (e.g. #)?
33
+ ## todo/check - rename to NOTE_BLOCK or TEXT_BLOCK or ???
34
+ PREPROC_BLOCK_RE = %r{ \[
35
+ [^\[\]\#]*? ## note - use non-greedy/lazy *? match
36
+ \]
37
+ }xm ## note - turn on multi-line match (for dot(.))
38
+
39
+
40
+
41
+
42
+
43
+
44
+
45
+ def _prep_doc( txt )
46
+ ## preprocess automagically
47
+ ## strip html comments
48
+ ## keep empty lines? - yes (turn in BLANK tokens)
49
+ ## keep leading spaces (indent) - yes (maybe used later in upstream parser!!)
50
+ ##
51
+ ## note - KEEP empty lines (get turned into BLANK token!!!!)
52
+
53
+
54
+ ### normalize unicode (decomposed chars to composed chars)
55
+ ##
56
+ ## note: é is decomposed (in two chars e.g.)
57
+ ## e (101)
58
+ ## ́ (769)
59
+ ## vs
60
+ ## é (233)
61
+ txt = txt.unicode_normalize(:nfc)
62
+
63
+
64
+ ## "universal" newlines
65
+ ## replace all windows-style cr+lf (\r\n) to lf (\n) only
66
+ txt = txt.gsub( "\r\n", "\n" )
67
+
68
+
69
+
70
+ ###
71
+ ## quick hack for now
72
+ ## remove html-style comments <!-- -->
73
+ ## (incl. multi-line) with two spaces
74
+ ## will mess-up lineno tracking!!!
75
+ ## fix later to have function lineno & colno!!!
76
+ ##
77
+ ## todo/fix - why? why not?
78
+ ## to keep lineno intact
79
+ ## replace with space and newline
80
+
81
+ ###
82
+ ## add more "native" multi-line comment-styles
83
+ ## e.g. #[[ ... ]] or #<<< .. >>> or #<< .. >>
84
+ ## or such - why? why not?
85
+
86
+ txt = txt.gsub( HTML_COMMENT_RE ) do |m|
87
+ _trace('preproc html comment:', m )
88
+ ' '
89
+ end
90
+
91
+
92
+
93
+ txt = txt.gsub( PREPROC_NOTA_BENE_RE ) do |m|
94
+ if m.include?( "\n" ) ## check for newlines (\n) and replace
95
+ _trace('preproc (multi-line) note/nota bene block:', m )
96
+ m.gsub( "\n", '↵' )
97
+ else
98
+ m
99
+ end
100
+ end
101
+
102
+
103
+ #####
104
+ ## (another) quick hack for now
105
+ ## turn multi-line note blocks into
106
+ ## single-line note blocks
107
+ ## by changing newline (\n) to ⏎ (unicode U+23CE)
108
+ ## or why not to ___ ?
109
+ ##
110
+ ## unicode options for return/arrows:
111
+ ## - ↵ (U+21B5): Downwards Arrow With Corner Leftwards.
112
+ ## This is the most common "carriage return" symbol.
113
+ ## - ⏎ (U+23CE): Return Symbol.
114
+ ## Specifically designated as the keyboard's "Return" key symbol,
115
+ ## often used in user interfaces.
116
+
117
+ txt = txt.gsub( PREPROC_BLOCK_RE ) do |m|
118
+ if m.include?( "\n" ) ## check for newlines (\n) and replace
119
+ _trace( 'preproc (multi-line) block:', m )
120
+ m.gsub( "\n", '↵' )
121
+ else
122
+ m
123
+ end
124
+ end
125
+
126
+
127
+ txt
128
+ end
129
+
130
+ end # class Lexer
131
+ end # module Fbtxt
@@ -0,0 +1,63 @@
1
+ module Fbtxt
2
+ class Lexer
3
+
4
+
5
+ ######
6
+ ## auto-fix checks line-by-line
7
+
8
+ def _prep_line( line )
9
+
10
+ ##
11
+ ## first check for tabs
12
+ ## add error/warn
13
+ ## for auto-fix - replace tabs with two spaces
14
+
15
+ line = line.gsub( "\t" ) do |_|
16
+ ## report error here
17
+ ## todo/add error here
18
+ _warn( "auto-fix; replacing tab (\\t) with two spaces in line #{line.inspect}" )
19
+ ' ' ## replace with two spaces
20
+ end
21
+
22
+
23
+ ## U+00A0 (160) -- non-breaking space (unicode)
24
+ line = line.gsub( "\u00A0" ) do |uni|
25
+ ## report error here
26
+ ## todo/add error here
27
+ _warn( "auto-fix; replacing non-breaking unicode space (#{uni}/#{uni.ord}) w/ ascii space ( /#{" ".ord}) in line #{line.inspect}" )
28
+ ' ' ## replace with space
29
+ end
30
+
31
+ ###
32
+ ## todo/fix - print unicode numbers for [–−]
33
+ ## different candidates to differentiate and document!!!
34
+ ## – => U+2013 (8211) -- En Dash (unicode)
35
+ ## − => U+2212 (8722) -- Minus Sign (unicode)
36
+ line = line.gsub( /[–−]/ ) do |uni|
37
+ ## report error here
38
+ ## todo/add error here
39
+ _warn( "auto-fix; replacing unicode dash (#{uni}/#{uni.ord}) w/ ascii dash (-/#{"-".ord}) in line #{line.inspect}" )
40
+ '-' ## replace with ascii dash (-)
41
+ end
42
+
43
+ #### add more unsmart quotes
44
+ ## smart quotes
45
+ line = line.gsub( /[‘’]/ ) do |uni|
46
+ ## report error here
47
+ ## todo/add error here
48
+ _warn( "auto-fix; replacing unicode (smart) quote (#{uni}/#{uni.ord}) w/ ascii quote ('/#{"'".ord}) in line #{line.inspect}" )
49
+ "'"
50
+ end
51
+
52
+ line = line.gsub( /[“”]/ ) do |uni|
53
+ ## report error here
54
+ ## todo/add error here
55
+ _warn( %Q{auto-fix; replacing unicode (smart) double quote (#{uni}/#{uni.ord}) w/ ascii double quote ("/#{'"'.ord}) in line #{line.inspect}} )
56
+ '"'
57
+ end
58
+
59
+ line
60
+ end
61
+
62
+ end # class Lexer
63
+ end # module Fbtxt