livetext 0.9.25 → 0.9.26

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/imports/bookish.rb +1 -2
  3. data/lib/livetext/errors.rb +3 -0
  4. data/lib/livetext/formatline.rb +102 -15
  5. data/lib/livetext/funcall.rb +86 -2
  6. data/lib/livetext/global_helpers.rb +5 -0
  7. data/lib/livetext/handler/import.rb +2 -6
  8. data/lib/livetext/handler/mixin.rb +2 -6
  9. data/lib/livetext/helpers.rb +9 -11
  10. data/lib/livetext/lineparser.rb +441 -0
  11. data/lib/livetext/more.rb +158 -0
  12. data/lib/livetext/processor.rb +3 -1
  13. data/lib/livetext/skeleton.rb +5 -0
  14. data/lib/livetext/standard.rb +12 -8
  15. data/lib/livetext/userapi.rb +27 -10
  16. data/lib/livetext/version.rb +1 -1
  17. data/lib/livetext.rb +3 -152
  18. data/test/snapshots/basic_formatting/actual-error.txt +0 -0
  19. data/test/snapshots/basic_formatting/actual-output.txt +13 -0
  20. data/test/snapshots/basic_formatting/err-sdiff.txt +1 -0
  21. data/test/snapshots/basic_formatting/out-sdiff.txt +14 -0
  22. data/test/snapshots/error_invalid_name/foo +5 -0
  23. data/test/snapshots/import_bookish/expected-output.txt +4 -4
  24. data/test/snapshots/more_functions/actual-error.txt +0 -0
  25. data/test/snapshots/more_functions/actual-output.txt +37 -0
  26. data/test/snapshots/more_functions/err-sdiff.txt +1 -0
  27. data/test/snapshots/more_functions/expected-output.txt +1 -1
  28. data/test/snapshots/more_functions/out-sdiff.txt +38 -0
  29. data/test/snapshots/more_functions/source.lt3 +1 -1
  30. data/test/snapshots/simple_vars/actual-error.txt +0 -0
  31. data/test/snapshots/simple_vars/actual-output.txt +6 -0
  32. data/test/snapshots/simple_vars/err-sdiff.txt +1 -0
  33. data/test/snapshots/simple_vars/out-sdiff.txt +7 -0
  34. data/test/snapshots/subset.txt +2 -0
  35. data/test/snapshots/var_into_func/actual-error.txt +0 -0
  36. data/test/snapshots/var_into_func/actual-output.txt +16 -0
  37. data/test/snapshots/var_into_func/err-sdiff.txt +1 -0
  38. data/test/snapshots/var_into_func/expected-error.txt +0 -0
  39. data/test/snapshots/var_into_func/expected-output.txt +16 -0
  40. data/test/snapshots/var_into_func/out-sdiff.txt +17 -0
  41. data/test/snapshots/var_into_func/source.lt3 +16 -0
  42. data/test/unit/all.rb +3 -1
  43. data/test/unit/formatline.rb +143 -274
  44. data/test/unit/lineparser.rb +650 -0
  45. data/test/unit/parser/set.rb +13 -12
  46. data/test/unit/tokenizer.rb +534 -0
  47. metadata +26 -5
  48. data/test/snapshots/error_inc_line_num/OUT +0 -17
  49. data/test/snapshots/error_no_such_copy/duh +0 -26
  50. data/test/snapshots/error_no_such_copy/mystery.txt +0 -36
@@ -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
@@ -0,0 +1,158 @@
1
+
2
+ # Class Livetext reopened (top level).
3
+
4
+ class Livetext
5
+
6
+ include Helpers
7
+
8
+ class Variables
9
+ def initialize(hash = {})
10
+ @vars = {}
11
+ hash.each_pair {|k, v| @vars[k.to_sym] = v }
12
+ end
13
+
14
+ def [](var)
15
+ @vars[var.to_sym]
16
+ end
17
+
18
+ def []=(var, value)
19
+ @vars[var.to_sym] = value
20
+ end
21
+ end
22
+
23
+ Vars = Variables.new
24
+
25
+ TTY = ::File.open("/dev/tty", "w")
26
+
27
+ attr_reader :main, :sources
28
+ attr_accessor :nopass, :nopara
29
+ attr_accessor :body, :indentation
30
+
31
+ class << self
32
+ attr_accessor :output # bad solution?
33
+ end
34
+
35
+ def vars
36
+ @_vars
37
+ end
38
+
39
+ def self.interpolate(str)
40
+ parse = Livetext::LineParser.new(str)
41
+ parse.var_func_parse
42
+ end
43
+
44
+ def self.customize(mix: [], call: [], vars: {})
45
+ obj = self.new
46
+ mix = Array(mix)
47
+ call = Array(call)
48
+ mix.each {|lib| obj.mixin(lib) }
49
+ call.each {|cmd| obj.main.send(cmd[1..-1]) } # ignores leading dot, no param
50
+ vars.each_pair {|var, val| obj.setvar(var, val.to_s) }
51
+ obj
52
+ end
53
+
54
+ def peek_nextline
55
+ @main.peek_nextline # delegate
56
+ end
57
+
58
+ def nextline
59
+ @main.nextline # delegate
60
+ end
61
+
62
+ def sources
63
+ @main.sources # delegate
64
+ end
65
+
66
+ def save_location
67
+ @save_location # delegate
68
+ end
69
+
70
+ def save_location=(where)
71
+ @save_location = where # delegate
72
+ end
73
+
74
+ def dump(file = nil) # not a dot command!
75
+ file ||= ::STDOUT
76
+ file.puts @body
77
+ rescue => err
78
+ TTY.puts "#dump had an error: #{err.inspect}"
79
+ end
80
+
81
+ def graceful_error(err)
82
+ dump
83
+ raise err
84
+ end
85
+
86
+ def customize(mix: [], call: [], vars: {})
87
+ mix = Array(mix)
88
+ call = Array(call)
89
+ mix.each {|lib| mixin(lib) }
90
+ call.each {|cmd| @main.send(cmd[1..-1]) } # ignores leading dot, no param
91
+ vars.each_pair {|var, val| @api.setvar(var, val.to_s) }
92
+ self
93
+ end
94
+
95
+ def initialize(output = ::STDOUT)
96
+ @source = nil
97
+ @_mixins = []
98
+ @_imports = []
99
+ @_outdir = "."
100
+ @no_puts = output.nil?
101
+ @body = ""
102
+ @main = Processor.new(self, output)
103
+ @indentation = [0]
104
+ @_vars = Livetext::Vars
105
+ @api = UserAPI.new(self)
106
+ initial_vars
107
+ end
108
+
109
+ def api
110
+ @api
111
+ end
112
+
113
+ def initial_vars
114
+ # Other predefined variables (see also setfile)
115
+ @api.setvar(:User, `whoami`.chomp)
116
+ @api.setvar(:Version, Livetext::VERSION)
117
+ end
118
+
119
+ def transform(text)
120
+ setfile!("(string)")
121
+ enum = text.each_line
122
+ front = text.match(/.*?\n/).to_a.first.chomp rescue ""
123
+ @main.source(enum, "STDIN: '#{front}...'", 0)
124
+ loop do
125
+ line = @main.nextline
126
+ break if line.nil?
127
+ process_line(line)
128
+ end
129
+ result = @body
130
+ # @body = ""
131
+ result
132
+ end
133
+
134
+ # EXPERIMENTAL and incomplete
135
+ def xform(*args, file: nil, text: nil, vars: {})
136
+ case
137
+ when file && text.nil?
138
+ xform_file(file)
139
+ when file.nil? && text
140
+ transform(text)
141
+ when file.nil? && text.nil?
142
+ raise "Must specify file or text"
143
+ when file && text
144
+ raise "Cannot specify file and text"
145
+ end
146
+ self.process_file(file)
147
+ self.body
148
+ end
149
+
150
+ def xform_file(file, vars: nil)
151
+ Livetext::Vars.replace(vars) unless vars.nil?
152
+ @_vars.replace(vars) unless vars.nil?
153
+ self.process_file(file)
154
+ self.body
155
+ end
156
+
157
+ end
158
+
@@ -57,7 +57,9 @@ class Processor
57
57
  end
58
58
 
59
59
  def disallowed?(name)
60
- Disallowed.include?(name.to_sym)
60
+ flag = Disallowed.include?(name.to_sym)
61
+ # api.tty "disa name = #{name.inspect} flag = #{flag}"
62
+ flag
61
63
  end
62
64
 
63
65
  def source(enum, file, line)
@@ -9,6 +9,11 @@ class Livetext
9
9
  module ParsingConstants
10
10
  end
11
11
 
12
+ class LineParser < StringParser
13
+ module FunCall
14
+ end
15
+ end
16
+
12
17
  class FormatLine < StringParser
13
18
  module FunCall
14
19
  end
@@ -25,13 +25,13 @@ module Livetext::Standard
25
25
 
26
26
  attr_reader :data
27
27
 
28
- def data=(val) # FIXME this is weird, let's remove it soonish
29
- api.data = val.chomp
30
- # @args = val.split rescue []
28
+ def data=(val) # FIXME this is weird, let's remove it soonish and why are there two???
29
+ # api.tty ">>>> in #{__FILE__}: api id = #{api.object_id}"
30
+ val = val.chomp
31
+ api.data = val
32
+ api.args = format(val).split rescue []
31
33
  @mixins = []
32
34
  @imports = []
33
- ###
34
- # api.data = val
35
35
  end
36
36
 
37
37
  # dumb name - bold, italic, teletype, striketrough
@@ -143,10 +143,14 @@ module Livetext::Standard
143
143
  end
144
144
 
145
145
  def dot_def(args = nil, body = nil)
146
+ # api.tty "in #{__FILE__}: api id = #{api.inspect}"
146
147
  name = api.args[0]
147
- str = "def #{name}\n"
148
+ # api.tty :dd1
149
+ # api.tty name.inspect
148
150
  check_disallowed(name)
151
+ # api.tty :dd2
149
152
  # Difficult to avoid eval here
153
+ str = "def #{name}\n"
150
154
  str << api.body(true).join("\n")
151
155
  str << "\nend\n"
152
156
  eval str
@@ -172,7 +176,7 @@ module Livetext::Standard
172
176
  else
173
177
  lines = api.body
174
178
  end
175
- pairs = Livetext::ParseGeneral.parse_vars(prefix, lines)
179
+ pairs = Livetext::ParseGeneral.parse_vars(lines, prefix: nil)
176
180
  set_variables(pairs)
177
181
  api.optional_blank_line
178
182
  end
@@ -187,7 +191,7 @@ module Livetext::Standard
187
191
  else
188
192
  lines = api.body
189
193
  end
190
- pairs = Livetext::ParseGeneral.parse_vars(prefix, lines)
194
+ pairs = Livetext::ParseGeneral.parse_vars(lines, prefix: nil)
191
195
  set_variables(pairs)
192
196
  api.optional_blank_line
193
197
  end