lxl 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|