lxl 0.3.0 → 0.3.1
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.
- data/CHANGES +7 -0
- data/README.en +3 -3
- data/VERSION +1 -1
- data/lib/lxl.rb +170 -86
- data/lxl.gemspec +19 -0
- data/lxl.rdoc +114 -0
- data/test/lxl_test.rb +2 -1
- metadata +7 -4
data/CHANGES
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
0.3.1
|
2
|
+
|
3
|
+
- LXL::LittleLexer => LXL::Lexer, types is a string again.
|
4
|
+
- Range regexp made more explicit.
|
5
|
+
- Added the NOT function.
|
6
|
+
- Componentized LXL classes into x_class methods
|
7
|
+
|
1
8
|
0.3.0
|
2
9
|
|
3
10
|
- Functions and constants moved into LXL::Namespace (major refactoring)
|
data/README.en
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
LXL README
|
2
|
-
|
2
|
+
==========
|
3
3
|
|
4
|
-
LXL (Like Excel) is a mini-language that mimics Microsoft Excel formulas
|
5
|
-
|
4
|
+
LXL (Like Excel) is a mini-language that mimics Microsoft Excel formulas
|
5
|
+
and is easily extended with new constants and functions.
|
6
6
|
|
7
7
|
Usage
|
8
8
|
-----
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.3.
|
1
|
+
0.3.1
|
data/lib/lxl.rb
CHANGED
@@ -1,14 +1,66 @@
|
|
1
|
-
# LXL (Like Excel) is a mini-language that mimics Microsoft Excel formulas
|
2
|
-
#
|
3
|
-
#
|
4
|
-
# =
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
1
|
+
# LXL (Like Excel) is a mini-language that mimics Microsoft Excel formulas
|
2
|
+
# and is easily extended with new constants and functions.
|
3
|
+
#
|
4
|
+
# =Namespaces
|
5
|
+
#
|
6
|
+
# Extending the language is as easy as defining new constants
|
7
|
+
# and methods on an LXL::Namespace class.
|
8
|
+
#
|
9
|
+
# class MyLXLNamespace < LXL::Namespace
|
10
|
+
# NAME = 'John Doe'
|
11
|
+
# def upper(text) text.to_s.upcase end
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# class MyLXL < LXL::Parser
|
15
|
+
# def self.namespace_class() MyLXLNamespace end
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# MyLXL.eval('=UPPER(NAME)')
|
19
|
+
# # => JOHN DOE
|
20
|
+
#
|
21
|
+
# =Ranges
|
22
|
+
#
|
23
|
+
# B3:D5 => LXL::Range.new("B3","D5")
|
24
|
+
#
|
25
|
+
# LXL::Range is a subclass of Range which overrides
|
26
|
+
# the #each method to return Excel-style ranges.
|
27
|
+
#
|
28
|
+
# # Standard Ruby Range
|
29
|
+
# Range.new("B3", "D5").collect
|
30
|
+
# # => ["B3", "B4", "B5", "B6", "B7", "B8", "B9",
|
31
|
+
# "C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9",
|
32
|
+
# "D0", "D1", "D2", "D3", "D4", "D5"]
|
33
|
+
#
|
34
|
+
# # LXL Range
|
35
|
+
# LXL::Range.new("B3", "D5").collect
|
36
|
+
# # => ["B3", "B4", "B5", "C3", "C4", "C5", "D3", "D4", "D5"]
|
37
|
+
#
|
38
|
+
# =Percentages
|
39
|
+
#
|
40
|
+
# 25% => .25
|
41
|
+
#
|
42
|
+
# =Deferred function calls
|
43
|
+
#
|
44
|
+
# LXL::Deferred snapshots the symbol/arguments of a function call for later use.
|
45
|
+
#
|
46
|
+
# class MyNamespace < LXL::Namespace
|
47
|
+
# register_deferred :foo
|
48
|
+
# end
|
49
|
+
# LXL.new(MyNamespace.new).eval('=FOO(1, "two", 3)').inspect
|
50
|
+
# # => <LXL::Deferred @args=[1, "two", 3] @symbol=:foo>
|
51
|
+
#
|
52
|
+
# =Symbol registration
|
53
|
+
#
|
54
|
+
# Register uppercase constants of the same name and value.
|
55
|
+
#
|
56
|
+
# class MyNamespace < LXL::Namespace
|
57
|
+
# register_symbols :foo, :bar
|
58
|
+
# end
|
59
|
+
# LXL.new(MyNamespace.new).eval('=LIST(FOO, BAR)').inspect
|
60
|
+
# # => [:FOO, :BAR]
|
61
|
+
#
|
10
62
|
# =Operators
|
11
|
-
#
|
63
|
+
#
|
12
64
|
# a + b # Add
|
13
65
|
# a - b # Subtract
|
14
66
|
# a * b # Multiply
|
@@ -19,60 +71,74 @@
|
|
19
71
|
# a > b # Greater than
|
20
72
|
# a <= b # Less than or equal to
|
21
73
|
# a >= b # Greater than or equal to
|
22
|
-
# a &
|
23
|
-
# n ^ n
|
24
|
-
#
|
74
|
+
# a & b # String concentation
|
75
|
+
# n ^ n # Exponential
|
76
|
+
#
|
77
|
+
# =Constants
|
78
|
+
#
|
79
|
+
# TRUE # true
|
80
|
+
# FALSE # false
|
81
|
+
# NULL # nil
|
82
|
+
#
|
25
83
|
# =Logical Functions
|
26
|
-
#
|
84
|
+
#
|
27
85
|
# AND (a,b)
|
28
86
|
# OR (a,b)
|
87
|
+
# NOT (cond)
|
29
88
|
# IF (cond,true,false)
|
30
|
-
#
|
89
|
+
#
|
31
90
|
# =Date/Time Functions
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
91
|
+
#
|
92
|
+
# TODAY () # current date value
|
93
|
+
# NOW () # current date/time value
|
94
|
+
# DATE (y,m,d) # date value
|
95
|
+
# TIME (h,m,s) # time value
|
96
|
+
# DATETIME (text) # convert a date/time string into a date/time value
|
97
|
+
#
|
39
98
|
# =List Functions
|
40
|
-
#
|
99
|
+
#
|
41
100
|
# LIST (a,b,..) # Variable length list
|
42
|
-
# IN (find,list) # True if find
|
43
|
-
#
|
101
|
+
# IN (find,list) # True if find is found in the given list/string
|
102
|
+
#
|
44
103
|
# =Notes
|
45
|
-
#
|
46
|
-
#
|
47
|
-
#
|
48
|
-
# * Ranges are translated: A1:D5 => LXL::Range.new("A1", "D5")
|
49
|
-
# * Return multiple results in an array by separating formulas with a semi-colon (;)
|
50
|
-
# * Constant and Function names are case-insensitive
|
51
|
-
# * Values prefixed with = are formulas, anything else is assumed to be text:
|
52
|
-
#
|
104
|
+
#
|
105
|
+
# * Values prefixed with = are formulas, anything else is assumed to be text.
|
106
|
+
#
|
53
107
|
# LXL.eval("5+5") # => '5+5'
|
54
108
|
# LXL.eval("=5+5") # => 10
|
109
|
+
#
|
110
|
+
# * Constant and Function names are case-insensitive.
|
111
|
+
#
|
112
|
+
# * The number zero is interpereted as FALSE.
|
113
|
+
#
|
114
|
+
# * Separating formulas with a semi-colon returns an Array of results.
|
55
115
|
#
|
56
116
|
module LXL
|
57
117
|
|
58
118
|
module_function
|
119
|
+
|
120
|
+
# Default parser class.
|
121
|
+
#
|
122
|
+
def parser_class
|
123
|
+
LXL::Parser
|
124
|
+
end
|
59
125
|
|
60
|
-
#
|
126
|
+
# Create a new Parser.
|
61
127
|
#
|
62
|
-
def
|
63
|
-
|
128
|
+
def new(*args)
|
129
|
+
parser_class.new(*args)
|
64
130
|
end
|
65
131
|
|
66
132
|
# Evaluate a formula.
|
67
133
|
#
|
68
134
|
def eval(formula)
|
69
|
-
|
135
|
+
parser_class.eval(formula)
|
70
136
|
end
|
71
137
|
|
72
|
-
#
|
138
|
+
# False if nil, false or zero.
|
73
139
|
#
|
74
|
-
def
|
75
|
-
|
140
|
+
def to_b(obj)
|
141
|
+
[nil,false,0].include?(obj) ? false : true
|
76
142
|
end
|
77
143
|
|
78
144
|
end
|
@@ -107,30 +173,42 @@ class LXL::Parser
|
|
107
173
|
self.new.eval(formula)
|
108
174
|
end
|
109
175
|
|
176
|
+
# Default lexer class.
|
177
|
+
#
|
178
|
+
def self.lexer_class
|
179
|
+
LXL::Lexer
|
180
|
+
end
|
181
|
+
|
110
182
|
# Default namespace class.
|
111
183
|
#
|
112
184
|
def self.namespace_class
|
113
185
|
LXL::Namespace
|
114
186
|
end
|
115
187
|
|
188
|
+
# Default range class.
|
189
|
+
#
|
190
|
+
def self.range_class
|
191
|
+
LXL::Range
|
192
|
+
end
|
193
|
+
|
116
194
|
# Initialize namespace and parser.
|
117
195
|
#
|
118
196
|
def initialize(namespace=self.class.namespace_class.new)
|
119
197
|
@namespace = namespace
|
120
198
|
ops = EXCEL_OPERATORS.collect { |v| Regexp.quote(v) }.join('|')
|
121
|
-
@lexer =
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
199
|
+
@lexer = self.class.lexer_class.new([
|
200
|
+
[/\A[\s,]+/, ?w], # Whitespace (includes Commas)
|
201
|
+
[/\A;/, ?;], # Semi-Colon (statement separator)
|
202
|
+
[/\A(#{ops})/, ?o], # Operator
|
203
|
+
[/\A("([^"]|"")*")/m, ?s], # String
|
204
|
+
[/\A\w+:\w+/, ?r], # Range
|
205
|
+
[/\A\d+(\.\d+)?%/, ?p], # Percentage
|
206
|
+
[/\A\d+\.\d+/, ?f], # Float
|
207
|
+
[/\A\d+/, ?i], # Integer
|
208
|
+
[/\A\w+/, ?t], # Token
|
209
|
+
[/\A\(/, ?(], # Open (
|
210
|
+
[/\A\)/, ?)], # Close )
|
211
|
+
], false)
|
134
212
|
end
|
135
213
|
|
136
214
|
# Evaluate a formula.
|
@@ -145,7 +223,7 @@ class LXL::Parser
|
|
145
223
|
tokens = f.collect { |t| t.last }
|
146
224
|
token = tokens.join.strip
|
147
225
|
if token =~ /^=/
|
148
|
-
tokens.each_index { |i| tokens[i] = translate_quotes(tokens[i]) if types[i] ==
|
226
|
+
tokens.each_index { |i| tokens[i] = translate_quotes(tokens[i]) if types[i] == ?s }
|
149
227
|
token = tokens.join.strip.gsub(/^=+/, '')
|
150
228
|
else
|
151
229
|
token = translate_quotes(quote(tokens.join.strip))
|
@@ -158,7 +236,7 @@ class LXL::Parser
|
|
158
236
|
|
159
237
|
protected
|
160
238
|
|
161
|
-
# Quote a
|
239
|
+
# Quote a string.
|
162
240
|
#
|
163
241
|
# quote('a "quoted" value.')
|
164
242
|
# # => '"a ""quoted"" value."'
|
@@ -167,7 +245,7 @@ class LXL::Parser
|
|
167
245
|
'"'+text.to_s.gsub('"','""')+'"'
|
168
246
|
end
|
169
247
|
|
170
|
-
# Translate "" to \" in quoted
|
248
|
+
# Translate "" to \" in quoted strings.
|
171
249
|
#
|
172
250
|
# translate_quotes('"a ""quoted"" value."')
|
173
251
|
# # => '"a \"quoted\" value."'
|
@@ -176,7 +254,7 @@ class LXL::Parser
|
|
176
254
|
text.to_s.gsub(/([^\\])""/,'\1\"')
|
177
255
|
end
|
178
256
|
|
179
|
-
# Tokenize a formula into <tt>[
|
257
|
+
# Tokenize a formula into <tt>[TypesString, TokensArray]</tt>.
|
180
258
|
#
|
181
259
|
def tokenize(formula)
|
182
260
|
ops = Hash[*EXCEL_OPERATORS.zip(RUBY_OPERATORS).flatten]
|
@@ -189,12 +267,12 @@ class LXL::Parser
|
|
189
267
|
tokens.each_index do |i|
|
190
268
|
type, token = types[i], tokens[i]
|
191
269
|
tokens[i] = case type
|
192
|
-
when
|
193
|
-
when
|
194
|
-
when
|
195
|
-
when
|
196
|
-
when
|
197
|
-
when
|
270
|
+
when ?o then ops[token]
|
271
|
+
when ?r then "self.class.range_class.new(*#{token.split(':').inspect})"
|
272
|
+
when ?p then token.to_f/100
|
273
|
+
when ?f then token.to_f
|
274
|
+
when ?i then token.to_i
|
275
|
+
when ?t then
|
198
276
|
upper = token.to_s.upcase.to_sym
|
199
277
|
lower = token.to_s.downcase.to_sym
|
200
278
|
raise NoMethodError, "protected method `#{token}` called for #{self}" if @namespace.const_get(:METHODS).include?(token.to_s)
|
@@ -202,11 +280,11 @@ class LXL::Parser
|
|
202
280
|
if tokens[i+1] != '('
|
203
281
|
raise ArgumentError, "wrong number of arguments for #{token}"
|
204
282
|
else
|
205
|
-
types[i] =
|
283
|
+
types[i] = ?F
|
206
284
|
"@namespace.method(:#{lower}).call"
|
207
285
|
end
|
208
286
|
elsif @namespace.const_defined?(upper)
|
209
|
-
types[i] =
|
287
|
+
types[i] = ?C
|
210
288
|
"@namespace.const_get(:#{upper})"
|
211
289
|
else token
|
212
290
|
end
|
@@ -237,13 +315,14 @@ end
|
|
237
315
|
#
|
238
316
|
class LXL::Range < Range
|
239
317
|
|
318
|
+
EXCEL_RANGE = /[A-Z]+[1-9]+/
|
319
|
+
|
240
320
|
def each
|
241
|
-
|
242
|
-
if
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
range = Range.new(a_col,b_col).each{ |col| Range.new(a_cel,b_cel).each { |cel| yield col+cel } }
|
321
|
+
range = [self.begin,self.end].collect! { |x| x.to_s.upcase }.sort
|
322
|
+
if range.first =~ EXCEL_RANGE and range.last =~ EXCEL_RANGE
|
323
|
+
fcol,lcol = range.collect { |x| x.gsub(/\d/, '') }
|
324
|
+
fcel,lcel = range.collect { |x| x.gsub(/\D/, '') }
|
325
|
+
Range.new(fcol,lcol).each{ |col| Range.new(fcel,lcel).each { |cel| yield col+cel } }
|
247
326
|
else
|
248
327
|
super
|
249
328
|
end
|
@@ -279,6 +358,12 @@ class LXL::EmptyNamespace
|
|
279
358
|
METHODS = ['__id__', '__send__', 'constants', 'const_defined?', 'const_get', 'const_set', 'class', 'instance_methods', 'method', 'methods', 'respond_to?', 'to_s']
|
280
359
|
(instance_methods-METHODS).sort.each { |m| undef_method m }
|
281
360
|
|
361
|
+
# Default deferred class.
|
362
|
+
#
|
363
|
+
def self.deferred_class
|
364
|
+
LXL::Deferred
|
365
|
+
end
|
366
|
+
|
282
367
|
# Ensure all constants are included (inherited, extended, included, etc).
|
283
368
|
#
|
284
369
|
def self.const_defined?(symbol) #:nodoc:
|
@@ -295,7 +380,7 @@ class LXL::EmptyNamespace
|
|
295
380
|
#
|
296
381
|
def self.register_deferred(*symbols)
|
297
382
|
symbols.collect! { |s| s.to_s.downcase.to_sym }
|
298
|
-
symbols.each { |s| define_method(s) { |*args|
|
383
|
+
symbols.each { |s| define_method(s) { |*args| self.class.deferred_class.new(s, *args) } }
|
299
384
|
end
|
300
385
|
|
301
386
|
# Register uppercase constants of the same name and value.
|
@@ -341,10 +426,14 @@ class LXL::Namespace < LXL::EmptyNamespace
|
|
341
426
|
LXL.to_b(a) || LXL.to_b(b)
|
342
427
|
end
|
343
428
|
|
429
|
+
def not(c)
|
430
|
+
! c
|
431
|
+
end
|
432
|
+
|
344
433
|
def if(c,a,b)
|
345
434
|
LXL.to_b(c) ? a : b
|
346
435
|
end
|
347
|
-
|
436
|
+
|
348
437
|
def list(*items)
|
349
438
|
items
|
350
439
|
end
|
@@ -375,13 +464,9 @@ class LXL::Namespace < LXL::EmptyNamespace
|
|
375
464
|
|
376
465
|
end
|
377
466
|
|
378
|
-
# John Carter's LittleLexer.
|
467
|
+
# Based on John Carter's LittleLexer (http://littlelexer.rubyforge.org).
|
379
468
|
#
|
380
|
-
|
381
|
-
#
|
382
|
-
# CHANGE: types are returned as [*Symbol] vs String
|
383
|
-
#
|
384
|
-
class LXL::LittleLexer #:nodoc: all
|
469
|
+
class LXL::Lexer #:nodoc: all
|
385
470
|
|
386
471
|
class LexerJammed < Exception; end
|
387
472
|
|
@@ -391,29 +476,29 @@ class LXL::LittleLexer #:nodoc: all
|
|
391
476
|
end
|
392
477
|
|
393
478
|
def scan(string,string_token_list=nil)
|
394
|
-
|
395
|
-
token_list =
|
479
|
+
result_string = String.new
|
480
|
+
token_list = Array.new
|
396
481
|
if string_token_list
|
397
482
|
next_token(string) do |t,token, tail|
|
398
|
-
|
483
|
+
result_string << t
|
399
484
|
token_list << [string_token_list[0...tail], string[0...tail]]
|
400
485
|
string = string[tail..-1]
|
401
486
|
string_token_list = string_token_list[tail..-1]
|
402
487
|
end
|
403
488
|
else
|
404
489
|
next_token(string) do |t,token, tail|
|
405
|
-
|
490
|
+
result_string << t
|
406
491
|
token_list << token
|
407
492
|
end
|
408
493
|
end
|
409
|
-
return
|
494
|
+
return result_string, token_list
|
410
495
|
end
|
411
496
|
|
412
497
|
private
|
413
498
|
|
414
499
|
def next_token( string)
|
415
500
|
match_data = nil
|
416
|
-
while string
|
501
|
+
while ! string.empty?
|
417
502
|
failed = true
|
418
503
|
@regex_to_char.each do |regex,char|
|
419
504
|
match_data = regex.match(string)
|
@@ -426,7 +511,6 @@ class LXL::LittleLexer #:nodoc: all
|
|
426
511
|
end
|
427
512
|
raise LexerJammed, string if failed
|
428
513
|
end
|
429
|
-
|
430
514
|
rescue RegexpError => details
|
431
515
|
raise RegexpError, "Choked while '#{@scanner}' was trying to match '#{string}' : #{details}"
|
432
516
|
end
|
data/lxl.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
version = File.read('VERSION').strip
|
4
|
+
raise "no version" if version.empty?
|
5
|
+
|
6
|
+
spec = Gem::Specification.new do |s|
|
7
|
+
s.name = 'lxl'
|
8
|
+
s.version = version
|
9
|
+
s.author = "Kevin Howe"
|
10
|
+
s.email = "kh@newclear.ca"
|
11
|
+
s.homepage = "lxl.rubyforge.org"
|
12
|
+
s.platform = Gem::Platform::RUBY
|
13
|
+
s.summary = "LXL (Like Excel) is a mini-language that mimics Microsoft Excel formulas. Easily extended with new constants and functions."
|
14
|
+
s.files = Dir['**/*'].delete_if { |f| f =~ /(cvs|gem|svn)$/i }
|
15
|
+
s.require_path = 'lib'
|
16
|
+
s.test_file = "test/lxl_test.rb"
|
17
|
+
s.has_rdoc = true
|
18
|
+
s.rubyforge_project = "lxl"
|
19
|
+
end
|
data/lxl.rdoc
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
LXL (Like Excel) is a mini-language that mimics Microsoft Excel formulas
|
2
|
+
and is easily extended with new constants and functions.
|
3
|
+
|
4
|
+
=Namespaces
|
5
|
+
|
6
|
+
Extending the language is as easy as defining new constants
|
7
|
+
and methods on an LXL::Namespace class.
|
8
|
+
|
9
|
+
class MyLXLNamespace < LXL::Namespace
|
10
|
+
NAME = 'John Doe'
|
11
|
+
def upper(text) text.to_s.upcase end
|
12
|
+
end
|
13
|
+
|
14
|
+
class MyLXL < LXL::Parser
|
15
|
+
def self.namespace_class() MyLXLNamespace end
|
16
|
+
end
|
17
|
+
|
18
|
+
MyLXL.eval('=UPPER(NAME)')
|
19
|
+
# => JOHN DOE
|
20
|
+
|
21
|
+
=Ranges
|
22
|
+
|
23
|
+
B3:D5 => LXL::Range.new("B3","D5")
|
24
|
+
|
25
|
+
LXL::Range is a subclass of Range which overrides
|
26
|
+
the #each method to return Excel-style ranges.
|
27
|
+
|
28
|
+
# Standard Ruby Range
|
29
|
+
Range.new("B3", "D5").collect
|
30
|
+
# => ["B3", "B4", "B5", "B6", "B7", "B8", "B9",
|
31
|
+
"C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9",
|
32
|
+
"D0", "D1", "D2", "D3", "D4", "D5"]
|
33
|
+
|
34
|
+
# LXL Range
|
35
|
+
LXL::Range.new("B3", "D5").collect
|
36
|
+
# => ["B3", "B4", "B5", "C3", "C4", "C5", "D3", "D4", "D5"]
|
37
|
+
|
38
|
+
=Percentages
|
39
|
+
|
40
|
+
25% => .25
|
41
|
+
|
42
|
+
=Deferred function calls
|
43
|
+
|
44
|
+
LXL::Deferred snapshots the symbol/arguments of a function call for later use.
|
45
|
+
|
46
|
+
class MyNamespace < LXL::Namespace
|
47
|
+
register_deferred :foo
|
48
|
+
end
|
49
|
+
LXL.new(MyNamespace.new).eval('=FOO(1, "two", 3)').inspect
|
50
|
+
# => <LXL::Deferred @args=[1, "two", 3] @symbol=:foo>
|
51
|
+
|
52
|
+
=Symbol registration
|
53
|
+
|
54
|
+
Register uppercase constants of the same name and value.
|
55
|
+
|
56
|
+
class MyNamespace < LXL::Namespace
|
57
|
+
register_symbols :foo, :bar
|
58
|
+
end
|
59
|
+
LXL.new(MyNamespace.new).eval('=LIST(FOO, BAR)').inspect
|
60
|
+
# => [:FOO, :BAR]
|
61
|
+
|
62
|
+
=Operators
|
63
|
+
|
64
|
+
a + b # Add
|
65
|
+
a - b # Subtract
|
66
|
+
a * b # Multiply
|
67
|
+
a / b # Divide
|
68
|
+
a = b # Equal to
|
69
|
+
a <> b # Not equal to
|
70
|
+
a < b # Less than
|
71
|
+
a > b # Greater than
|
72
|
+
a <= b # Less than or equal to
|
73
|
+
a >= b # Greater than or equal to
|
74
|
+
a & b # String concentation
|
75
|
+
n ^ n # Exponential
|
76
|
+
|
77
|
+
=Constants
|
78
|
+
|
79
|
+
TRUE # true
|
80
|
+
FALSE # false
|
81
|
+
NULL # nil
|
82
|
+
|
83
|
+
=Logical Functions
|
84
|
+
|
85
|
+
AND (a,b)
|
86
|
+
OR (a,b)
|
87
|
+
NOT (cond)
|
88
|
+
IF (cond,true,false)
|
89
|
+
|
90
|
+
=Date/Time Functions
|
91
|
+
|
92
|
+
TODAY () # current date value
|
93
|
+
NOW () # current date/time value
|
94
|
+
DATE (y,m,d) # date value
|
95
|
+
TIME (h,m,s) # time value
|
96
|
+
DATETIME (text) # convert a date/time string into a date/time value
|
97
|
+
|
98
|
+
=List Functions
|
99
|
+
|
100
|
+
LIST (a,b,..) # Variable length list
|
101
|
+
IN (find,list) # True if find is found in the given list/string
|
102
|
+
|
103
|
+
=Notes
|
104
|
+
|
105
|
+
* Values prefixed with = are formulas, anything else is assumed to be text.
|
106
|
+
|
107
|
+
LXL.eval("5+5") # => '5+5'
|
108
|
+
LXL.eval("=5+5") # => 10
|
109
|
+
|
110
|
+
* Constant and Function names are case-insensitive.
|
111
|
+
|
112
|
+
* The number zero is interpereted as FALSE.
|
113
|
+
|
114
|
+
* Separating formulas with a semi-colon returns an Array of results.
|
data/test/lxl_test.rb
CHANGED
@@ -23,6 +23,7 @@ class LXLTest < Test::Unit::TestCase
|
|
23
23
|
=";"=":"
|
24
24
|
=A
|
25
25
|
=B
|
26
|
+
=NOT(TRUE)
|
26
27
|
=((1+2)*(10-6))/2
|
27
28
|
=datetime("2004-11-22 11:11:00")=DATE(2004,11,22)+TIME(11,11,00)
|
28
29
|
=IN(" is ", "this is a string")
|
@@ -39,7 +40,7 @@ class LXLTest < Test::Unit::TestCase
|
|
39
40
|
="embedded percentages 25% and semi-colons ; are working properly"
|
40
41
|
}
|
41
42
|
expected = ["This is some text", "\"This is some \"quoted\" text\"", "This is some \"quoted\" text"]
|
42
|
-
expected += [true, false, :A, :B, 6, true, true, [1, "two", 3.0], true, false, true, "yes", "this and that"]
|
43
|
+
expected += [true, false, :A, :B, false, 6, true, true, [1, "two", 3.0], true, false, true, "yes", "this and that"]
|
43
44
|
expected += [8, 0.502, true, true, "embedded percentages 25% and semi-colons ; are working properly"]
|
44
45
|
MyNamespace.register_symbols(:A, :B)
|
45
46
|
lxl = LXL::Parser.new(MyNamespace.new)
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.3
|
|
3
3
|
specification_version: 1
|
4
4
|
name: lxl
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.3.
|
7
|
-
date: 2005-02-
|
6
|
+
version: 0.3.1
|
7
|
+
date: 2005-02-13
|
8
8
|
summary: LXL (Like Excel) is a mini-language that mimics Microsoft Excel formulas. Easily extended with new constants and functions.
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -15,7 +15,7 @@ description:
|
|
15
15
|
autorequire:
|
16
16
|
default_executable:
|
17
17
|
bindir: bin
|
18
|
-
has_rdoc:
|
18
|
+
has_rdoc: true
|
19
19
|
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
20
|
requirements:
|
21
21
|
-
|
@@ -31,11 +31,14 @@ files:
|
|
31
31
|
- VERSION
|
32
32
|
- test
|
33
33
|
- CHANGES
|
34
|
+
- lxl.gemspec
|
34
35
|
- setup.rb
|
35
36
|
- README.en
|
37
|
+
- lxl.rdoc
|
36
38
|
- lib/lxl.rb
|
37
39
|
- test/lxl_test.rb
|
38
|
-
test_files:
|
40
|
+
test_files:
|
41
|
+
- test/lxl_test.rb
|
39
42
|
rdoc_options: []
|
40
43
|
extra_rdoc_files: []
|
41
44
|
executables: []
|