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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +4 -0
- data/Manifest.txt +50 -0
- data/README.md +22 -0
- data/Rakefile +53 -0
- data/lib/fbtxt/parser/debuggable.rb +53 -0
- data/lib/fbtxt/parser/lexer-logger.rb +20 -0
- data/lib/fbtxt/parser/lexer-on_goal.rb +167 -0
- data/lib/fbtxt/parser/lexer-on_group_def.rb +31 -0
- data/lib/fbtxt/parser/lexer-on_prop_lineup.rb +79 -0
- data/lib/fbtxt/parser/lexer-on_prop_misc.rb +123 -0
- data/lib/fbtxt/parser/lexer-on_prop_penalties.rb +40 -0
- data/lib/fbtxt/parser/lexer-on_round_def.rb +37 -0
- data/lib/fbtxt/parser/lexer-on_top.rb +133 -0
- data/lib/fbtxt/parser/lexer-prep_doc.rb +131 -0
- data/lib/fbtxt/parser/lexer-prep_line.rb +63 -0
- data/lib/fbtxt/parser/lexer-tokenize.rb +468 -0
- data/lib/fbtxt/parser/lexer.rb +231 -0
- data/lib/fbtxt/parser/lexer_buffer.rb +68 -0
- data/lib/fbtxt/parser/lexer_token.rb +126 -0
- data/lib/fbtxt/parser/parse_tree--core.rb +29 -0
- data/lib/fbtxt/parser/parse_tree-match.rb +309 -0
- data/lib/fbtxt/parser/parse_tree-props.rb +233 -0
- data/lib/fbtxt/parser/parse_tree.rb +202 -0
- data/lib/fbtxt/parser/parser-runtime.rb +379 -0
- data/lib/fbtxt/parser/parser-top.rb +102 -0
- data/lib/fbtxt/parser/parser.rb +2343 -0
- data/lib/fbtxt/parser/token-date--helpers.rb +130 -0
- data/lib/fbtxt/parser/token-date--names.rb +108 -0
- data/lib/fbtxt/parser/token-date.rb +200 -0
- data/lib/fbtxt/parser/token-date_duration.rb +171 -0
- data/lib/fbtxt/parser/token-geo.rb +134 -0
- data/lib/fbtxt/parser/token-goals--helpers.rb +114 -0
- data/lib/fbtxt/parser/token-goals.rb +306 -0
- data/lib/fbtxt/parser/token-group.rb +29 -0
- data/lib/fbtxt/parser/token-note.rb +40 -0
- data/lib/fbtxt/parser/token-prop.rb +309 -0
- data/lib/fbtxt/parser/token-prop_name.rb +83 -0
- data/lib/fbtxt/parser/token-round.rb +88 -0
- data/lib/fbtxt/parser/token-score--helpers.rb +189 -0
- data/lib/fbtxt/parser/token-score.rb +60 -0
- data/lib/fbtxt/parser/token-score_full.rb +331 -0
- data/lib/fbtxt/parser/token-score_fuller.rb +434 -0
- data/lib/fbtxt/parser/token-score_legs.rb +59 -0
- data/lib/fbtxt/parser/token-status.rb +192 -0
- data/lib/fbtxt/parser/token-status_inline.rb +112 -0
- data/lib/fbtxt/parser/token-text.rb +221 -0
- data/lib/fbtxt/parser/token-time.rb +144 -0
- data/lib/fbtxt/parser/token.rb +224 -0
- data/lib/fbtxt/parser/version.rb +24 -0
- data/lib/fbtxt/parser.rb +140 -0
- 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
|