livetext 0.9.23 → 0.9.26

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/README.lt3 +6 -6
  3. data/bin/livetext +57 -40
  4. data/imports/bookish.rb +81 -81
  5. data/imports/calibre.rb +3 -3
  6. data/imports/livemagick.rb +17 -17
  7. data/imports/markdown.rb +10 -10
  8. data/imports/pyggish.rb +13 -13
  9. data/imports/tutorial.rb +15 -15
  10. data/lib/cmdargs.rb +7 -4
  11. data/lib/{errors.rb → livetext/errors.rb} +4 -3
  12. data/lib/{formatline.rb → livetext/formatline.rb} +119 -18
  13. data/lib/livetext/funcall.rb +168 -0
  14. data/lib/{functions.rb → livetext/functions.rb} +0 -2
  15. data/lib/{global_helpers.rb → livetext/global_helpers.rb} +6 -3
  16. data/lib/{handler → livetext/handler}/import.rb +5 -9
  17. data/lib/livetext/handler/mixin.rb +33 -0
  18. data/lib/{handler.rb → livetext/handler.rb} +1 -1
  19. data/lib/{helpers.rb → livetext/helpers.rb} +78 -66
  20. data/lib/{html.rb → livetext/html.rb} +2 -3
  21. data/lib/livetext/lineparser.rb +441 -0
  22. data/lib/livetext/more.rb +158 -0
  23. data/lib/{parser → livetext/parser}/general.rb +0 -0
  24. data/lib/{parser → livetext/parser}/set.rb +0 -0
  25. data/lib/{parser → livetext/parser}/string.rb +0 -0
  26. data/lib/{parser.rb → livetext/parser.rb} +0 -3
  27. data/lib/{parsing.rb → livetext/parsing.rb} +0 -2
  28. data/lib/livetext/paths.rb +13 -0
  29. data/lib/{processor.rb → livetext/processor.rb} +18 -8
  30. data/lib/livetext/reopen.rb +12 -0
  31. data/lib/livetext/skeleton.rb +22 -0
  32. data/lib/{standard.rb → livetext/standard.rb} +150 -127
  33. data/lib/livetext/userapi.rb +170 -0
  34. data/lib/livetext/version.rb +6 -0
  35. data/lib/livetext.rb +14 -152
  36. data/plugin/bookish.rb +82 -81
  37. data/plugin/calibre.rb +3 -3
  38. data/plugin/livemagick.rb +17 -17
  39. data/plugin/markdown.rb +10 -10
  40. data/plugin/pyggish.rb +118 -118
  41. data/plugin/tutorial.rb +15 -15
  42. data/test/all.rb +6 -0
  43. data/test/snapshots/{error_inc_line_num → basic_formatting}/actual-error.txt +0 -0
  44. data/test/snapshots/basic_formatting/actual-output.txt +13 -0
  45. data/test/snapshots/basic_formatting/err-sdiff.txt +1 -0
  46. data/test/snapshots/basic_formatting/out-sdiff.txt +14 -0
  47. data/test/snapshots/def_method/expected-output.txt +2 -0
  48. data/test/snapshots/def_method/source.lt3 +4 -2
  49. data/test/snapshots/error_inc_line_num/{OUT → README.txt} +11 -8
  50. data/test/snapshots/error_inc_line_num/expected-output.txt +0 -6
  51. data/test/snapshots/error_inc_line_num/match-error.txt +1 -1
  52. data/test/snapshots/error_invalid_name/foo +5 -0
  53. data/test/snapshots/error_line_num/match-error.txt +1 -1
  54. data/test/snapshots/error_missing_end/expected-output.txt +0 -1
  55. data/test/snapshots/error_name_not_permitted/expected-output.txt +4 -0
  56. data/test/snapshots/error_name_not_permitted/match-error.txt +1 -1
  57. data/test/snapshots/error_no_such_copy/expected-output.txt +1 -0
  58. data/test/snapshots/error_no_such_mixin/expected-output.txt +1 -0
  59. data/test/snapshots/error_no_such_mixin/match-error.txt +1 -1
  60. data/test/snapshots/error_no_such_mixin/source.lt3 +1 -1
  61. data/test/snapshots/example_alpha/source.lt3 +2 -2
  62. data/test/snapshots/example_alpha2/expected-output.txt +0 -2
  63. data/test/snapshots/example_alpha2/source.lt3 +5 -4
  64. data/test/snapshots/import/expected-output.txt +2 -1
  65. data/test/snapshots/import/match-error.txt +1 -1
  66. data/test/snapshots/import/simple_import.rb +1 -1
  67. data/test/snapshots/import2/simple_import.rb +1 -1
  68. data/test/snapshots/import_bookish/expected-output.txt +4 -4
  69. data/test/snapshots/{error_invalid_name/actual-output.txt → more_functions/actual-error.txt} +0 -0
  70. data/test/snapshots/more_functions/actual-output.txt +37 -0
  71. data/test/snapshots/more_functions/err-sdiff.txt +1 -0
  72. data/test/snapshots/more_functions/expected-output.txt +1 -1
  73. data/test/snapshots/more_functions/out-sdiff.txt +38 -0
  74. data/test/snapshots/more_functions/source.lt3 +1 -1
  75. data/test/snapshots/raw_lines/expected-output.txt +0 -2
  76. data/test/snapshots/simple_import/simple_import.rb +1 -1
  77. data/test/snapshots/simple_mixin/simple_mixin.rb +1 -1
  78. data/test/snapshots/{error_missing_end/actual-output.txt → simple_vars/actual-error.txt} +0 -0
  79. data/test/snapshots/simple_vars/actual-output.txt +6 -0
  80. data/test/snapshots/simple_vars/err-sdiff.txt +1 -0
  81. data/test/snapshots/simple_vars/out-sdiff.txt +7 -0
  82. data/test/snapshots/single_raw_line/expected-output.txt +0 -2
  83. data/test/snapshots/subset.txt +9 -7
  84. data/test/snapshots/{error_no_such_copy/actual-output.txt → var_into_func/actual-error.txt} +0 -0
  85. data/test/snapshots/var_into_func/actual-output.txt +16 -0
  86. data/test/snapshots/var_into_func/err-sdiff.txt +1 -0
  87. data/test/snapshots/{error_no_such_inc/actual-output.txt → var_into_func/expected-error.txt} +0 -0
  88. data/test/snapshots/var_into_func/expected-output.txt +16 -0
  89. data/test/snapshots/var_into_func/out-sdiff.txt +17 -0
  90. data/test/snapshots/var_into_func/source.lt3 +16 -0
  91. data/test/snapshots.rb +16 -7
  92. data/test/unit/all.rb +3 -1
  93. data/test/unit/formatline.rb +145 -276
  94. data/test/unit/html.rb +1 -2
  95. data/test/unit/lineparser.rb +650 -0
  96. data/test/unit/parser/set.rb +13 -12
  97. data/test/unit/standard.rb +0 -1
  98. data/test/unit/tokenizer.rb +534 -0
  99. metadata +49 -39
  100. data/lib/funcall.rb +0 -93
  101. data/lib/parser/file.rb +0 -6
  102. data/lib/parser/mixin.rb +0 -34
  103. data/lib/userapi.rb +0 -164
  104. data/test/snapshots/error_inc_line_num/actual-output.txt +0 -17
  105. data/test/snapshots/error_invalid_name/actual-error.txt +0 -10
  106. data/test/snapshots/error_invalid_name/out-sdiff.txt +0 -6
  107. data/test/snapshots/error_missing_end/actual-error.txt +0 -10
  108. data/test/snapshots/error_missing_end/out-sdiff.txt +0 -6
  109. data/test/snapshots/error_no_such_copy/actual-error.txt +0 -10
  110. data/test/snapshots/error_no_such_copy/out-sdiff.txt +0 -5
  111. data/test/snapshots/error_no_such_inc/actual-error.txt +0 -10
  112. data/test/snapshots/error_no_such_inc/out-sdiff.txt +0 -6
  113. data/test/snapshots/error_no_such_mixin/actual-error.txt +0 -13
  114. data/test/snapshots/error_no_such_mixin/actual-output.txt +0 -0
  115. data/test/snapshots/error_no_such_mixin/out-sdiff.txt +0 -6
@@ -1,5 +1,5 @@
1
- # p __FILE__
2
1
 
2
+ require_relative 'global_helpers'
3
3
 
4
4
  module Livetext::Helpers
5
5
 
@@ -9,6 +9,14 @@ module Livetext::Helpers
9
9
  ESCAPING = { "'" => ''', '&' => '&', '"' => '"',
10
10
  '<' => '&lt;', '>' => '&gt;' }
11
11
 
12
+ def friendly_error(err)
13
+ return graceful_error(err) if self.respond_to?(:graceful_error)
14
+ return self.parent.graceful_error(err) if self.respond_to?(:parent)
15
+ raise err
16
+ rescue => myerr
17
+ TTY.puts "--- Warning: friendly_error #{myerr.inspect}"
18
+ end
19
+
12
20
  def escape_html(string)
13
21
  enc = string.encoding
14
22
  unless enc.ascii_compatible?
@@ -25,34 +33,47 @@ module Livetext::Helpers
25
33
  string.gsub(/['&\"<>]/, ESCAPING)
26
34
  end
27
35
 
36
+ def showme(obj, tag = "")
37
+ whence = caller[0]
38
+ file, line, meth = whence.split(":")
39
+ file = File.basename(file)
40
+ meth = meth[4..-2]
41
+ tag << " =" if tag
42
+ hide_class = [true, false, nil].include?(obj)
43
+ klass = hide_class ? "" : "(#{obj.class}) "
44
+ puts " #{tag} #{klass}#{obj.inspect} in ##{meth} [#{file} line #{line}]"
45
+ end
46
+
47
+ def debug(*args)
48
+ puts(*args) if ENV['debug']
49
+ end
50
+
28
51
  def find_file(name, ext=".rb", which="imports")
29
52
  failed = "#{__method__}: expected 'imports' or 'plugin'"
30
53
  raise failed unless %w[imports plugin].include?(which)
31
- paths = [Livetext::Path.sub(/lib/, "#{which}/"), "./"]
54
+ paths = [Livetext::Path.sub(/lib.livetext/, "#{which}/"), "./"]
32
55
  base = "#{name}#{ext}"
33
56
  paths.each do |path|
34
57
  file = path + base
58
+ ::Livetext::TTY.puts " Checking: #{file}"
35
59
  return file if File.exist?(file)
36
60
  end
37
-
38
- raise "No such mixin '#{name}'"
39
-
40
- # # Really want to search upward??
41
- # raise "No such mixin '#{name}'" if cwd_root?
42
- # Dir.chdir("..") { find_file(name) }
61
+ ::Livetext::TTY.puts " ...oops"
62
+ return nil
43
63
  end
44
64
 
45
65
  def self.rx(str, space=nil)
46
66
  Regexp.compile("^" + Regexp.escape(str) + "#{space}")
47
67
  end
48
68
 
49
- Comment = rx(Sigil, Space)
50
- Dotcmd = rx(Sigil)
51
- Ddotcmd = /^ *\$\.[A-Za-z]/
69
+ Comment = rx(Sigil, Space)
70
+ DotCmd = rx(Sigil)
71
+ DollarDot = /^ *\$\.[A-Za-z]/
52
72
 
53
- ## FIXME process_file[!] should call process[_text]
73
+ ## FIXME process_file[!] should call process[_text] ?
54
74
 
55
75
  def process_file(fname, btrace=false)
76
+ graceful_error FileNotFound(fname) unless File.exist?(fname)
56
77
  setfile(fname)
57
78
  text = File.readlines(fname)
58
79
  enum = text.each
@@ -62,77 +83,64 @@ module Livetext::Helpers
62
83
  loop do
63
84
  line = @main.nextline
64
85
  break if line.nil?
65
- process_line(line)
86
+ success = process_line(line)
87
+ break unless success
66
88
  end
67
89
  val = @main.finalize rescue nil
68
90
  @body # FIXME? @body.join("\n") # array
69
- rescue StandardError => err
70
- # TTY.puts ">>> rescue in process_file!! (helpers)"
71
- # TTY.puts @body
72
- raise err
91
+ return true
73
92
  end
74
93
 
75
94
  def process_line(line)
76
- nomarkup = true
95
+ success = true
77
96
  case line # must apply these in order
78
- when Comment
79
- handle_scomment(line)
80
- when Dotcmd
81
- handle_dotcmd(line)
82
- when Ddotcmd
83
- indent = line.index("$") + 1
84
- @indentation.push(indent)
85
- line.sub!(/^ *\$/, "")
86
- handle_dotcmd(line)
87
- indentation.pop
97
+ when Comment
98
+ success = handle_scomment(line)
99
+ when DotCmd
100
+ success = handle_dotcmd(line)
101
+ when DollarDot
102
+ success = handle_dollar_dot
88
103
  else
89
- @main._passthru(line)
104
+ api.passthru(line) # must succeed?
90
105
  end
106
+ success
107
+ end
108
+
109
+ def handle_dollar_dot
110
+ indent = line.index("$") + 1
111
+ @indentation.push(indent)
112
+ line.sub!(/^ *\$/, "")
113
+ success = handle_dotcmd(line)
114
+ indentation.pop
115
+ success
116
+ end
117
+
118
+ def invoke_dotcmd(name)
119
+ # FIXME Add cmdargs stuff... depends on name, etc.
120
+ retval = @main.send(name)
121
+ retval
122
+ rescue => err
123
+ graceful_error(err)
91
124
  end
92
125
 
93
126
  def handle_dotcmd(line, indent = 0)
94
127
  indent = @indentation.last # top of stack
95
- line = line.sub(/# .*$/, "")
96
- name = get_name(line).to_sym
97
- result = nil
128
+ line = line.sub(/# .*$/, "") # FIXME Could be problematic?
129
+ name = get_name(line)
130
+ success = true # Be optimistic... :P
98
131
  case
99
132
  when name == :end # special case
100
- puts @body
101
- raise EndWithoutOpening()
133
+ graceful_error EndWithoutOpening()
102
134
  when @main.respond_to?(name)
103
- result = @main.send(name)
104
-
105
- # NOTE: The above line is where the magic happens!
106
- # A name like 'foobar' results in an invocation of
107
- # @main.foobar (where @main is a Processor, and any
108
- # new methods (e.g. from a mixin) are added to @main
109
- #
110
- # So all the functionality from _args and _raw_args
111
- # and _data (among others?) will be encapsulated in
112
- # 'some' kind of PORO which handles access to all
113
- # these things as well as the 'body' between the
114
- # command and its corresponding .end
115
- #
116
- # The 'body' functionality is so commonly used, I plan
117
- # to pass it in separately as needed (even though the
118
- # args object should make it available also).
119
- #
120
- # Every method corresponding to a dot commmand will
121
- # get args and body passed in as needed. Every one of
122
- # the signatures already has (args = nil, body = nil)
123
- # but nothing is being passed in that way yet.
124
- #
125
- # Refer to lib/cmdargs.rb for more! This is *strictly*
126
- # experimental and a "work in progress."
135
+ success = invoke_dotcmd(name)
127
136
  else
128
- puts @body # earlier correct output, not flushed yet
129
- raise "Name '#{name}' is unknown"
130
- return
137
+ graceful_error UnknownMethod(name)
131
138
  end
132
- result
139
+ success
133
140
  end
134
141
 
135
142
  def handle_scomment(line)
143
+ return true
136
144
  end
137
145
 
138
146
  def get_name(line)
@@ -141,15 +149,16 @@ module Livetext::Helpers
141
149
  name = "dot_" + name if %w[include def].include?(name)
142
150
  @main.check_disallowed(name)
143
151
  @main.data = data
144
- name
152
+ @main.api.data = data
153
+ name.to_sym
145
154
  end
146
155
 
147
156
  def check_disallowed(name)
148
- raise DisallowedName(name) if disallowed?(name)
157
+ friendly_error DisallowedName(name) if disallowed?(name)
149
158
  end
150
159
 
151
160
  def check_file_exists(file)
152
- raise FileNotFound(file) unless File.exist?(file)
161
+ return File.exist?(file)
153
162
  end
154
163
 
155
164
  def set_variables(pairs)
@@ -161,6 +170,9 @@ module Livetext::Helpers
161
170
 
162
171
  def grab_file(fname)
163
172
  File.read(fname)
173
+ rescue
174
+ ::STDERR.puts "Can't find #{fname.inspect} \n "
175
+ return nil
164
176
  end
165
177
 
166
178
  def search_upward(file)
@@ -185,7 +197,7 @@ module Livetext::Helpers
185
197
  end
186
198
 
187
199
  def include_file(file)
188
- @_args = [file]
200
+ api.args = [file]
189
201
  dot_include
190
202
  end
191
203
 
@@ -1,4 +1,3 @@
1
- # p __FILE__
2
1
 
3
2
  module HTMLHelper
4
3
 
@@ -17,9 +16,9 @@ module HTMLHelper
17
16
 
18
17
  def wrap(*tags) # helper
19
18
  open, close = open_close_tags(*tags)
20
- _out open
19
+ api.out open
21
20
  yield
22
- _out close
21
+ api.out close
23
22
  end
24
23
 
25
24
  def open_close_tags(*tags)
@@ -0,0 +1,441 @@
1
+
2
+ require_relative 'parsing'
3
+ require_relative 'funcall'
4
+
5
+ # Class LineParser handles the parsing of comments, dot commands, and
6
+ # simple formatting characters, as well as variables and functions.
7
+
8
+ class Livetext::LineParser < StringParser
9
+ include Livetext::ParsingConstants
10
+ include Livetext::LineParser::FunCall
11
+
12
+ FMTS = %w[* _ ~ `]
13
+
14
+ attr_reader :out
15
+ attr_reader :tokenlist
16
+
17
+ def initialize(line)
18
+ super
19
+ @token = Null.dup
20
+ @tokenlist = []
21
+ @live = Livetext.new
22
+ end
23
+
24
+ def self.api
25
+ Livetext.new.main.api
26
+ end
27
+
28
+ def api
29
+ @live.main.api
30
+ end
31
+
32
+ def self.parse!(line)
33
+ return nil if line.nil?
34
+ line.chomp!
35
+ x = self.new(line)
36
+ api.tty "\n-- string: #{line.inspect}" if $testme
37
+ t = x.tokenize
38
+ api.tty "-- Tokens: #{t.inspect}" if $testme
39
+ result = x.evaluate
40
+ api.tty "-- result: #{result.inspect}" if $testme
41
+ result
42
+ end
43
+
44
+ def parse_formatting
45
+ loop do
46
+ case peek
47
+ when Escape; grab; add peek; grab
48
+ when "*", "_", "`", "~"
49
+ marker peek
50
+ add peek
51
+ when LF
52
+ break if eos?
53
+ when nil
54
+ break
55
+ else
56
+ add peek
57
+ end
58
+ grab
59
+ end
60
+ add_token(:str)
61
+ @tokenlist
62
+ end
63
+
64
+ def self.parse_formatting(str)
65
+ fmt = self.new(str)
66
+ loop do
67
+ case fmt.peek
68
+ when Escape; fmt.grab; fmt.add fmt.peek; fmt.grab
69
+ when "*", "_", "`", "~"
70
+ fmt.marker fmt.peek
71
+ fmt.add fmt.peek
72
+ when LF
73
+ break if fmt.eos?
74
+ when nil
75
+ break
76
+ else
77
+ fmt.add fmt.peek
78
+ end
79
+ fmt.grab
80
+ end
81
+ fmt.add_token(:str)
82
+ fmt.tokenlist
83
+ end
84
+
85
+ def self.parse_variables(str)
86
+ return nil if str.nil?
87
+ x = self.new(str.chomp)
88
+ char = x.peek
89
+ loop do
90
+ char = x.grab
91
+ break if char == LF || char == nil
92
+ x.escaped if char == Escape
93
+ x.dollar if char == "$" # Could be $$
94
+ x.add char
95
+ end
96
+ x.add_token(:str)
97
+ result = x.evaluate
98
+ result
99
+ end
100
+
101
+ def embed(sym, str)
102
+ pre, post = SimpleFormats[sym]
103
+ pre + str + post
104
+ end
105
+
106
+ #########
107
+
108
+ def handle_simple_formatting(ch)
109
+ # api.tty "ch = #{ch.inspect}"
110
+ marker ch
111
+ add ch
112
+ # api.tty "token = #{@token.inspect} #{@tokenlist.inspect}"
113
+ c2 = grab
114
+ # api.tty "c2 = #{c2.inspect}"
115
+ end
116
+
117
+ def grab_string
118
+ weird = ["$", nil] # [Escape, "$", nil]
119
+ ch = grab
120
+ add ch # api.tty "-- gs @token = #{@token.inspect}"
121
+ loop do
122
+ ch = peek # api.tty "gs1 ch = #{ch.inspect}"
123
+ break if weird.include?(ch)
124
+ break if FMTS.include?(ch) && (self.prev == " ")
125
+ break if eos? # ch = grab # advance pointer # api.tty "gs3 ch = #{ch.inspect}"
126
+ add grab
127
+ end # ch = grab # advance pointer # api.tty "-- gs4 ch = #{ch.inspect}"; sleep 0.01
128
+ add_token :str
129
+ end
130
+
131
+ def grab_token(ch)
132
+ finish = false
133
+ # api.tty "#{__method__}: ch = #{ch.inspect}"
134
+ case ch
135
+ when nil; finish = true # do nothing
136
+ when LF; finish = true # do nothing - break if eos?
137
+ when Escape; ch = self.escaped; add ch
138
+ when "$"; dollar
139
+ when *FMTS; handle_simple_formatting(ch)
140
+ else grab_string
141
+ end
142
+ # api.tty "#{__method__}: AFTER CASE: api.data = #{api.data.inspect}"
143
+ [ch, finish, @token, @tokenlist]
144
+ end
145
+
146
+ def tokenize
147
+ ch = peek
148
+ loop do
149
+ ch = peek
150
+ stuff = grab_token(ch)
151
+ ch, finish, t, tlist = *stuff
152
+ break if finish
153
+ end
154
+ # api.tty "tokenize: i = #{self.i}"
155
+ # api.tty "tokenize: token = #{@token.inspect} tokenlist = #{@tokenlist.inspect}"
156
+ @tokenlist
157
+ end
158
+
159
+ # def self.get_vars
160
+ # grab
161
+ # case peek
162
+ # when LF, " ", nil
163
+ # add "$"
164
+ # add_token :str
165
+ # when "$"; double_dollar
166
+ ## when "."; dollar_dot
167
+ # when /[A-Za-z]/
168
+ # add_token :str
169
+ # var = peek + grab_alpha_dot
170
+ # add_token(:var, var)
171
+ # else
172
+ # add "$" + peek
173
+ # add_token(:str)
174
+ # end
175
+ # end
176
+ #
177
+ # def self.parse_var_func # FIXME Hmm...
178
+ # loop do
179
+ # case peek
180
+ # when "$"
181
+ # dollar
182
+ # when LF
183
+ # break if eos?
184
+ # when nil
185
+ # break
186
+ # else
187
+ # add peek
188
+ # end
189
+ # grab
190
+ # end
191
+ # add_token(:str)
192
+ # @tokenlist
193
+ # end
194
+
195
+ def terminate?(terminators, ch)
196
+ if terminators.is_a? Regexp
197
+ terminators === ch
198
+ else
199
+ terminators.include?(ch)
200
+ end
201
+ end
202
+
203
+ def var_func_parse
204
+ char = self.peek
205
+ loop do
206
+ char = self.grab
207
+ break if char == LF || char == nil
208
+ self.escaped if char == Escape
209
+ self.dollar if char == "$" # Could be $$
210
+ self.add char
211
+ end
212
+ self.add_token(:str)
213
+ result = self.evaluate
214
+ result
215
+ end
216
+
217
+ def self.var_func_parse(str)
218
+ return nil if str.nil?
219
+ x = self.new(str.chomp)
220
+ char = x.peek
221
+ loop do
222
+ char = x.grab
223
+ break if char == LF || char == nil
224
+ x.escaped if char == Escape
225
+ x.dollar if char == "$" # Could be $$
226
+ x.add char
227
+ end
228
+ x.add_token(:str)
229
+ result = x.evaluate
230
+ result
231
+ end
232
+
233
+ def evaluate(tokens = @tokenlist)
234
+ @out = ""
235
+ return "" if tokens.empty?
236
+ gen = tokens.each
237
+ token = gen.next
238
+ loop do
239
+ break if token.nil?
240
+ sym, val = *token
241
+ case sym
242
+ when :str; eval_str(val)
243
+ when :var; eval_var(val)
244
+ when :func; eval_func(val, gen)
245
+ when *BITS; eval_bits(sym, val)
246
+ else
247
+ add_token :str
248
+ end
249
+ token = gen.next
250
+ end
251
+ @out
252
+ end
253
+
254
+ def add(str)
255
+ @token << str unless str.nil?
256
+ end
257
+
258
+ def add_token(kind, token = @token)
259
+ return if token.nil?
260
+ @tokenlist << [kind, token] unless token.empty?
261
+ @token = Null.dup
262
+ end
263
+
264
+ def grab_alpha
265
+ str = grab
266
+ loop do
267
+ break if eos?
268
+ break if terminate?(NoAlpha, peek)
269
+ str << grab
270
+ end
271
+ str
272
+ end
273
+
274
+ def grab_alpha_dot
275
+ str = grab # Null.dup
276
+ loop do
277
+ break if eos?
278
+ break if terminate?(NoAlphaDot, peek)
279
+ str << grab
280
+ end
281
+ str
282
+ end
283
+
284
+ def dollar
285
+ c1 = grab # $
286
+ c2 = grab # ...
287
+ case c2
288
+ when " "; add_token :str, "$ "
289
+ when LF, nil; add_token :str, "$"
290
+ when "$"; double_dollar
291
+ when "."; dollar_dot
292
+ when /[A-Za-z]/; add_token(:var, c2 + grab_alpha_dot)
293
+ else add_token(:str, "$" + c2)
294
+ end
295
+ end
296
+
297
+ def finish_token(str, kind)
298
+ add str
299
+ add_token :str
300
+ grab
301
+ end
302
+
303
+ def marker(char)
304
+ add_token :str
305
+ sym = Syms[char]
306
+ return if embedded?
307
+ grab
308
+ case peek
309
+ when Space; finish_token(char + " ", :str)
310
+ when LF, nil; finish_token(char, :str)
311
+ when char; double_marker(char)
312
+ when LBrack; long_marker(char)
313
+ else
314
+ str = peek + collect!(sym, Blank)
315
+ add str
316
+ add_token sym, str
317
+ grab
318
+ end
319
+ end
320
+
321
+ def double_marker(char)
322
+ sym = Syms[char]
323
+ kind = sym
324
+ case lookahead # first char after **
325
+ when Space, LF, nil
326
+ pre, post = SimpleFormats[sym]
327
+ add_token kind
328
+ else
329
+ str = collect!(sym, Punc)
330
+ add_token kind, str
331
+ grab
332
+ end
333
+ end
334
+
335
+ def long_marker(char)
336
+ sym = Syms[char]
337
+ grab # skip left bracket
338
+ kind = sym # "param_#{sym}".to_sym
339
+ arg = collect!(sym, Param, true)
340
+ add_token kind, arg
341
+ end
342
+
343
+ def collect_bracketed(sym, terminators)
344
+ str = Null.dup # next is not " ","*","["
345
+ grab # ZZZ
346
+ loop do
347
+ if peek == Escape
348
+ grab
349
+ str << grab
350
+ next
351
+ end
352
+ if terminate?(terminators, peek)
353
+ break
354
+ end
355
+ str << peek # not a terminator
356
+ grab
357
+ end
358
+
359
+ if peek == "]" # skip right bracket
360
+ grab
361
+ end
362
+ add str
363
+ str
364
+ rescue => err
365
+ ::STDERR.puts "ERR = #{err}\n#{err.backtrace}"
366
+ end
367
+
368
+ def escaped
369
+ grab # Eat the backslash
370
+ ch = grab # Take next char
371
+ ch
372
+ end
373
+
374
+ def collect!(sym, terminators, bracketed=nil)
375
+ return collect_bracketed(sym, terminators) if bracketed
376
+ str = Null.dup # next is not " ","*","["
377
+ grab # ZZZ
378
+ loop do
379
+ case
380
+ when peek.nil?
381
+ return str
382
+ when peek == Escape
383
+ str << escaped
384
+ next
385
+ when terminate?(terminators, peek)
386
+ break
387
+ else
388
+ str << peek # not a terminator
389
+ end
390
+ grab
391
+ end
392
+ ungrab
393
+ add str
394
+ str
395
+ rescue => err
396
+ ::STDERR.puts "ERR = #{err}\n#{err.backtrace}"
397
+ end
398
+
399
+ def varsub(name)
400
+ live = Livetext.new
401
+ value = live.vars[name]
402
+ result = value || "[#{name} is undefined]"
403
+ result
404
+ end
405
+
406
+ def embedded?
407
+ ! (['"', "'", " ", nil].include? prev)
408
+ end
409
+
410
+ # private
411
+
412
+ def eval_bits(sym, val)
413
+ # api.tty "eb: #{[sym, val].inspect}"
414
+ val = Livetext.interpolate(val)
415
+ @out << embed(sym, val)
416
+ end
417
+
418
+ def eval_func(val, gen)
419
+ param = nil
420
+ arg = gen.peek rescue :bogus
421
+ unless arg == :bogus
422
+ if [:colon, :brackets].include? arg[0]
423
+ arg = gen.next # for real
424
+ param = arg[1]
425
+ # FIXME - unsure - interpolate again??
426
+ # param = Livetext.interpolate(param)
427
+ end
428
+ end
429
+ str = funcall(val, param)
430
+ @out << str
431
+ end
432
+
433
+ def eval_var(val)
434
+ @out << varsub(val)
435
+ end
436
+
437
+ def eval_str(val)
438
+ @out << val unless val == "\n" # BUG
439
+ end
440
+
441
+ end