lxl 0.2.4 → 0.3.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.
- data/CHANGES +13 -0
- data/README.en +68 -0
- data/VERSION +1 -1
- data/lib/lxl.rb +250 -128
- data/setup.rb +1331 -0
- data/test/lxl_test.rb +84 -18
- metadata +4 -3
- data/README +0 -48
data/CHANGES
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
0.3.0
|
2
|
+
|
3
|
+
- Functions and constants moved into LXL::Namespace (major refactoring)
|
4
|
+
- Added ranges (r): A1:D5 => LXL::Range.new("A1","D5")
|
5
|
+
LXL::Range is a Range subclass which overrides #each to return an excel-style range
|
6
|
+
- Added percentages (p): 25% => 0.25
|
7
|
+
- Added & string concentation operator
|
8
|
+
- Added ^ exponential operator
|
9
|
+
- Added semi-colon guessing: eliminates the need for statement separator in most cases (\n= becomes \n;)
|
10
|
+
- Fix: semi-colons parsed as tokens again to allow semis in strings
|
11
|
+
- Refactored string quoting
|
12
|
+
- Filled out rdoc
|
13
|
+
|
1
14
|
0.2.4
|
2
15
|
|
3
16
|
- Added Deferred function calls via register_deferred
|
data/README.en
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
LXL README
|
2
|
+
============
|
3
|
+
|
4
|
+
LXL (Like Excel) is a mini-language that mimics Microsoft Excel formulas.
|
5
|
+
Easily extended with new constants and functions.
|
6
|
+
|
7
|
+
Usage
|
8
|
+
-----
|
9
|
+
|
10
|
+
formulas = %{
|
11
|
+
This is some text;
|
12
|
+
="This is some ""quoted"" text"
|
13
|
+
=((1+2)*(10-6))/2
|
14
|
+
=datetime("2004-11-22 11:11:00")=DATE(2004,11,22)+TIME(11,11,00)
|
15
|
+
=IN(" is ", "this is a string")
|
16
|
+
=LIST(1, "two", 3.0)
|
17
|
+
=IN("b", LIST("a", "b", "c"))
|
18
|
+
=AND(TRUE, NULL)
|
19
|
+
=OR(TRUE, FALSE)
|
20
|
+
=IF(1+1=2, "yes", "no")
|
21
|
+
="this" & " and " & "that"
|
22
|
+
=2^3
|
23
|
+
=25%+25.2%
|
24
|
+
}
|
25
|
+
|
26
|
+
# single formula
|
27
|
+
puts LXL.eval('=5+5').inspect # => 10
|
28
|
+
|
29
|
+
# multiple formulas separated by semi-colon
|
30
|
+
puts LXL.eval(formulas).inspect
|
31
|
+
# => ["This is some text", "This is some \"quoted\" text", 6, true, true, [1, "two", 3.0], true, false, true, "yes", "this and that", 8, 0.502]
|
32
|
+
|
33
|
+
See API docs for more information.
|
34
|
+
|
35
|
+
Requirements
|
36
|
+
------------
|
37
|
+
|
38
|
+
* Ruby 1.8.2
|
39
|
+
|
40
|
+
Install
|
41
|
+
-------
|
42
|
+
|
43
|
+
GEM file
|
44
|
+
|
45
|
+
gem install lxl (remote)
|
46
|
+
gem install lxl-x.x.x.gem (local)
|
47
|
+
|
48
|
+
Tarball (setup.rb)
|
49
|
+
|
50
|
+
De-Compress archive and enter its top directory.
|
51
|
+
Then type:
|
52
|
+
|
53
|
+
$ ruby setup.rb config
|
54
|
+
$ ruby setup.rb setup
|
55
|
+
($ su)
|
56
|
+
# ruby setup.rb install
|
57
|
+
|
58
|
+
You can also install files into your favorite directory
|
59
|
+
by supplying setup.rb some options. Try "ruby setup.rb --help".
|
60
|
+
|
61
|
+
License
|
62
|
+
-------
|
63
|
+
|
64
|
+
LXL incorporates John Carter's LittleLexer for parsing.
|
65
|
+
Distributes under the same terms as LittleLexer.
|
66
|
+
http://www.rubyforge.org/projects/littlelexer/
|
67
|
+
|
68
|
+
Kevin Howe <kh@newclear.ca>
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/lib/lxl.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# LXL (Like Excel) is a mini-language that mimics Microsoft Excel formulas.
|
2
|
-
# Easily extended with new constants and functions.
|
2
|
+
# Easily extended with new constants and functions using LXL::Namespace.
|
3
3
|
#
|
4
4
|
# =Constants
|
5
5
|
#
|
@@ -19,6 +19,8 @@
|
|
19
19
|
# a > b # Greater than
|
20
20
|
# a <= b # Less than or equal to
|
21
21
|
# a >= b # Greater than or equal to
|
22
|
+
# a & b # String concentation
|
23
|
+
# n ^ n # Exponential
|
22
24
|
#
|
23
25
|
# =Logical Functions
|
24
26
|
#
|
@@ -42,13 +44,47 @@
|
|
42
44
|
# =Notes
|
43
45
|
#
|
44
46
|
# * The number zero is interpereted as FALSE
|
47
|
+
# * Percentages are translated: 25% => 0.25
|
48
|
+
# * Ranges are translated: A1:D5 => LXL::Range.new("A1", "D5")
|
45
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:
|
46
52
|
#
|
53
|
+
# LXL.eval("5+5") # => '5+5'
|
54
|
+
# LXL.eval("=5+5") # => 10
|
55
|
+
#
|
56
|
+
module LXL
|
57
|
+
|
58
|
+
module_function
|
59
|
+
|
60
|
+
# False if nil, false or zero.
|
61
|
+
#
|
62
|
+
def to_b(value)
|
63
|
+
[nil,false,0].include?(value) ? false : true
|
64
|
+
end
|
65
|
+
|
66
|
+
# Evaluate a formula.
|
67
|
+
#
|
68
|
+
def eval(formula)
|
69
|
+
LXL::Parser.eval(formula)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Create a new Parser.
|
73
|
+
#
|
74
|
+
def new(*args)
|
75
|
+
LXL::Parser.new(*args)
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
47
80
|
# =Lexical Types
|
48
81
|
#
|
49
|
-
# w Whitespace (includes
|
82
|
+
# w Whitespace (includes commas)
|
83
|
+
# ; Semi-Colon (statement separator)
|
50
84
|
# o Operator
|
51
85
|
# s String
|
86
|
+
# r Range
|
87
|
+
# p Percentage
|
52
88
|
# f Float
|
53
89
|
# i Integer
|
54
90
|
# t Token
|
@@ -58,146 +94,95 @@
|
|
58
94
|
# F Function
|
59
95
|
# C Constant
|
60
96
|
#
|
61
|
-
module LXL
|
62
|
-
|
63
|
-
module_function
|
64
|
-
|
65
|
-
# Evaluate a formula
|
66
|
-
#
|
67
|
-
def eval(formula)
|
68
|
-
LXL::Parser.eval(formula)
|
69
|
-
end
|
70
|
-
|
71
|
-
end
|
72
|
-
|
73
97
|
class LXL::Parser
|
74
98
|
|
75
|
-
attr_reader :
|
99
|
+
attr_reader :namespace, :lexer
|
76
100
|
|
77
|
-
RUBY_OPERATORS = ['+', '-', '*', '/', '<=', '>=', '==', '!=', '<', '>']
|
78
|
-
EXCEL_OPERATORS = ['+', '-', '*', '/', '<=', '>=', '=', '<>', '<', '>']
|
101
|
+
RUBY_OPERATORS = ['+', '-', '*', '/', '<=', '>=', '==', '!=', '<', '>', '+', '**']
|
102
|
+
EXCEL_OPERATORS = ['+', '-', '*', '/', '<=', '>=', '=', '<>', '<', '>', '&', '^' ]
|
79
103
|
|
80
|
-
# Evaluate a formula
|
104
|
+
# Evaluate a formula.
|
81
105
|
#
|
82
106
|
def self.eval(formula)
|
83
107
|
self.new.eval(formula)
|
84
108
|
end
|
85
|
-
|
86
|
-
#
|
109
|
+
|
110
|
+
# Default namespace class.
|
87
111
|
#
|
88
|
-
def
|
89
|
-
|
90
|
-
|
91
|
-
@constants = Hash.new
|
92
|
-
register_constant(:TRUE, true)
|
93
|
-
register_constant(:FALSE, false)
|
94
|
-
register_constant(:NULL, nil)
|
95
|
-
|
96
|
-
# Functions
|
97
|
-
@functions = Hash.new
|
98
|
-
register_function(:AND) { |a,b| to_b(a) && to_b(b) }
|
99
|
-
register_function(:OR) { |a,b| to_b(a) || to_b(b) }
|
100
|
-
register_function(:IF) { |v,a,b| to_b(v) ? a : b }
|
101
|
-
register_function(:LIST) { |*args| args }
|
102
|
-
register_function(:IN) { |n,h| h.respond_to?(:include?) ? h.include?(n) : false }
|
103
|
-
register_function(:TODAY) { Date.today.ajd.to_f }
|
104
|
-
register_function(:NOW) { DateTime.now.ajd.to_f }
|
105
|
-
register_function(:DATE) { |y,m,d| Date.new(y,m,d).ajd.to_f }
|
106
|
-
register_function(:DATETIME) { |value| DateTime.parse(value).ajd.to_f }
|
107
|
-
register_function(:TIME) { |h,m,s|
|
108
|
-
DateTime.valid_time?(h,m,s) ? DateTime.valid_time?(h,m,s).to_f : raise(ArgumentError, 'invalid time')
|
109
|
-
}
|
112
|
+
def self.namespace_class
|
113
|
+
LXL::Namespace
|
114
|
+
end
|
110
115
|
|
111
|
-
|
116
|
+
# Initialize namespace and parser.
|
117
|
+
#
|
118
|
+
def initialize(namespace=self.class.namespace_class.new)
|
119
|
+
@namespace = namespace
|
112
120
|
ops = EXCEL_OPERATORS.collect { |v| Regexp.quote(v) }.join('|')
|
113
|
-
#
|
114
121
|
@lexer = LXL::LittleLexer.new([
|
115
122
|
[/\A[\s,]+/, ?w], # Whitespace (includes Commas)
|
123
|
+
[/\A;/, ?;], # Semi-Colon (statement separator)
|
116
124
|
[/\A(#{ops})/, ?o], # Operator
|
117
125
|
[/\A("([^"]|"")*")/m, ?s], # String
|
118
|
-
[/\A
|
119
|
-
[/\A
|
126
|
+
[/\A\w+:\w+/, ?r], # Range
|
127
|
+
[/\A\d+(\.\d+)?%/, ?p], # Percentage
|
128
|
+
[/\A\d+\.\d+/, ?f], # Float
|
129
|
+
[/\A\d+/, ?i], # Integer
|
120
130
|
[/\A\w+/, ?t], # Token
|
121
131
|
[/\A\(/, ?(], # Open (
|
122
132
|
[/\A\)/, ?)], # Close )
|
123
133
|
], false)
|
124
134
|
end
|
125
135
|
|
126
|
-
# Evaluate formula
|
136
|
+
# Evaluate a formula.
|
127
137
|
#
|
128
138
|
def eval(formula)
|
129
|
-
formulas =
|
130
|
-
|
139
|
+
formulas = [[]]
|
140
|
+
formula = formula.to_s.gsub(/(\n)(\s*=)/, '\1;\2') # infer statement separators
|
141
|
+
types,tokens = tokenize(formula)
|
142
|
+
tokens.each_index { |i| tokens[i] == ';' ? formulas << [] : formulas.last << [types[i], tokens[i]] }
|
143
|
+
formulas.collect! { |f|
|
144
|
+
types = f.collect { |t| t.first }
|
145
|
+
tokens = f.collect { |t| t.last }
|
146
|
+
token = tokens.join.strip
|
147
|
+
if token =~ /^=/
|
148
|
+
tokens.each_index { |i| tokens[i] = translate_quotes(tokens[i]) if types[i] == :s }
|
149
|
+
token = tokens.join.strip.gsub(/^=+/, '')
|
150
|
+
else
|
151
|
+
token = translate_quotes(quote(tokens.join.strip))
|
152
|
+
end
|
153
|
+
}
|
154
|
+
formulas.delete_if { |f| f == '""' }
|
155
|
+
formulas.collect! { |f| Kernel.eval(f, binding) }
|
131
156
|
formulas.size == 1 ? formulas.first : formulas
|
132
157
|
end
|
133
158
|
|
134
|
-
# Register a function
|
135
|
-
#
|
136
|
-
# * Converts name to symbol
|
137
|
-
# * Wraps function with a debugging procedure
|
138
|
-
#
|
139
|
-
def register_function(name, &block)
|
140
|
-
name = name(name)
|
141
|
-
@functions[name] = debug(name, &block)
|
142
|
-
end
|
143
|
-
|
144
|
-
# Register a constant
|
145
|
-
#
|
146
|
-
# * Converts name to symbol
|
147
|
-
#
|
148
|
-
def register_constant(name, value)
|
149
|
-
name = name(name)
|
150
|
-
@constants[name] = value
|
151
|
-
end
|
152
|
-
|
153
|
-
# Register a constant for each symbol of the same name and value
|
154
|
-
#
|
155
|
-
def register_symbols(*symbols)
|
156
|
-
symbols.each { |s| register_constant(s, s) }
|
157
|
-
end
|
158
|
-
|
159
|
-
# Register each symbol as a Deferred function call
|
160
|
-
#
|
161
|
-
def register_deferred(*symbols)
|
162
|
-
symbols.each { |s| register_function(s) { |*args| LXL::Deferred.new(s, *args) } }
|
163
|
-
end
|
164
|
-
|
165
159
|
protected
|
166
160
|
|
167
|
-
#
|
168
|
-
#
|
169
|
-
def name(obj)
|
170
|
-
obj.to_s.upcase.to_sym
|
171
|
-
end
|
172
|
-
|
173
|
-
# Wrap a procedure in a debugging procedure
|
161
|
+
# Quote a text value.
|
174
162
|
#
|
175
|
-
#
|
163
|
+
# quote('a "quoted" value.')
|
164
|
+
# # => '"a ""quoted"" value."'
|
176
165
|
#
|
177
|
-
def
|
178
|
-
|
179
|
-
Proc.new { |*args|
|
180
|
-
if block.arity >= 0 and block.arity != args.size
|
181
|
-
raise ArgumentError, "wrong number of arguments (#{args.size} for #{block.arity}) for #{name}"
|
182
|
-
end
|
183
|
-
block.call(*args)
|
184
|
-
}
|
166
|
+
def quote(text)
|
167
|
+
'"'+text.to_s.gsub('"','""')+'"'
|
185
168
|
end
|
186
169
|
|
187
|
-
#
|
170
|
+
# Translate "" to \" in quoted string values.
|
188
171
|
#
|
189
|
-
|
190
|
-
|
191
|
-
|
172
|
+
# translate_quotes('"a ""quoted"" value."')
|
173
|
+
# # => '"a \"quoted\" value."'
|
174
|
+
#
|
175
|
+
def translate_quotes(text)
|
176
|
+
text.to_s.gsub(/([^\\])""/,'\1\"')
|
192
177
|
end
|
193
178
|
|
194
|
-
# Tokenize formula into [TypesArray, TokensArray]
|
179
|
+
# Tokenize a formula into <tt>[TypesArray, TokensArray]</tt>.
|
195
180
|
#
|
196
181
|
def tokenize(formula)
|
197
182
|
ops = Hash[*EXCEL_OPERATORS.zip(RUBY_OPERATORS).flatten]
|
198
183
|
|
199
184
|
# Parse formula
|
200
|
-
types,tokens = @lexer.scan(formula.
|
185
|
+
types,tokens = @lexer.scan(formula.to_s)
|
201
186
|
raise SyntaxError, 'unbalanced parentheses' unless balanced?(tokens)
|
202
187
|
|
203
188
|
# Parse tokens
|
@@ -205,23 +190,25 @@ class LXL::Parser
|
|
205
190
|
type, token = types[i], tokens[i]
|
206
191
|
tokens[i] = case type
|
207
192
|
when :o then ops[token]
|
208
|
-
when :
|
193
|
+
when :r then "LXL::Range.new(*#{token.split(':').inspect})"
|
194
|
+
when :p then token.to_f/100
|
209
195
|
when :f then token.to_f
|
210
196
|
when :i then token.to_i
|
211
197
|
when :t then
|
212
|
-
|
213
|
-
|
198
|
+
upper = token.to_s.upcase.to_sym
|
199
|
+
lower = token.to_s.downcase.to_sym
|
200
|
+
raise NoMethodError, "protected method `#{token}` called for #{self}" if @namespace.const_get(:METHODS).include?(token.to_s)
|
201
|
+
if @namespace.respond_to?(lower)
|
214
202
|
if tokens[i+1] != '('
|
215
203
|
raise ArgumentError, "wrong number of arguments for #{token}"
|
216
204
|
else
|
217
205
|
types[i] = :F
|
218
|
-
"@
|
206
|
+
"@namespace.method(:#{lower}).call"
|
219
207
|
end
|
220
|
-
elsif @
|
208
|
+
elsif @namespace.const_defined?(upper)
|
221
209
|
types[i] = :C
|
222
|
-
"@
|
223
|
-
else
|
224
|
-
raise NameError, "unknown constant #{token}"
|
210
|
+
"@namespace.const_get(:#{upper})"
|
211
|
+
else token
|
225
212
|
end
|
226
213
|
else token
|
227
214
|
end
|
@@ -230,21 +217,47 @@ class LXL::Parser
|
|
230
217
|
[types,tokens]
|
231
218
|
end
|
232
219
|
|
233
|
-
# Check that parentheses are balanced
|
220
|
+
# Check that parentheses are balanced.
|
234
221
|
#
|
235
222
|
def balanced?(tokens)
|
236
223
|
tokens.find_all { |t| ['(', ')'].include?(t) }.size % 2 == 0
|
237
224
|
end
|
238
225
|
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
226
|
+
end
|
227
|
+
|
228
|
+
# Excel-style ranges.
|
229
|
+
#
|
230
|
+
# Range.new("B3", "D5").collect
|
231
|
+
# # => ["B3", "B4", "B5", "B6", "B7", "B8", "B9",
|
232
|
+
# "C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9",
|
233
|
+
# "D0", "D1", "D2", "D3", "D4", "D5"]
|
234
|
+
#
|
235
|
+
# LXL::Range.new("B3", "D5").collect
|
236
|
+
# # => ["B3", "B4", "B5", "C3", "C4", "C5", "D3", "D4", "D5"]
|
237
|
+
#
|
238
|
+
class LXL::Range < Range
|
239
|
+
|
240
|
+
def each
|
241
|
+
a,b = self.begin.to_s, self.end.to_s
|
242
|
+
if a =~ /\D/ and b =~ /\D/
|
243
|
+
r = [a,b].collect{ |x| x.upcase }.sort
|
244
|
+
a_col,b_col = r.collect { |x| x.gsub(/\d/, '') }
|
245
|
+
a_cel,b_cel = r.collect { |x| x.gsub(/\D/, '') }
|
246
|
+
range = Range.new(a_col,b_col).each{ |col| Range.new(a_cel,b_cel).each { |cel| yield col+cel } }
|
247
|
+
else
|
248
|
+
super
|
249
|
+
end
|
243
250
|
end
|
244
251
|
|
245
252
|
end
|
246
253
|
|
247
|
-
#
|
254
|
+
# Snapshots the symbol/arguments of a function call for later use.
|
255
|
+
#
|
256
|
+
# class MyNamespace < LXL::Namespace
|
257
|
+
# register_deferred :foo
|
258
|
+
# end
|
259
|
+
# LXL.new(MyNamespace.new).eval('=FOO(1, "two", 3)').inspect
|
260
|
+
# # => <LXL::Deferred @args=[1, "two", 3] @symbol=:foo>
|
248
261
|
#
|
249
262
|
class LXL::Deferred
|
250
263
|
|
@@ -257,7 +270,112 @@ class LXL::Deferred
|
|
257
270
|
|
258
271
|
end
|
259
272
|
|
260
|
-
#
|
273
|
+
# Namespace lookup for constants and functions (minimal methods).
|
274
|
+
#
|
275
|
+
class LXL::EmptyNamespace
|
276
|
+
|
277
|
+
# Remove all unessecary methods
|
278
|
+
['constants', 'const_defined?', 'const_get', 'const_set'].each { |m| define_method(m) { |s| self.class.send(m,s) } }
|
279
|
+
METHODS = ['__id__', '__send__', 'constants', 'const_defined?', 'const_get', 'const_set', 'class', 'instance_methods', 'method', 'methods', 'respond_to?', 'to_s']
|
280
|
+
(instance_methods-METHODS).sort.each { |m| undef_method m }
|
281
|
+
|
282
|
+
# Ensure all constants are included (inherited, extended, included, etc).
|
283
|
+
#
|
284
|
+
def self.const_defined?(symbol) #:nodoc:
|
285
|
+
constants.include?(symbol.to_s)
|
286
|
+
end
|
287
|
+
|
288
|
+
# Register lowercase methods which return a Deferred function call.
|
289
|
+
#
|
290
|
+
# class MyNamespace < LXL::Namespace
|
291
|
+
# register_deferred :foo
|
292
|
+
# end
|
293
|
+
# LXL.new(MyNamespace.new).eval('=FOO(1, "two", 3)').inspect
|
294
|
+
# # => <LXL::Deferred @args=[1, "two", 3] @symbol=:foo>
|
295
|
+
#
|
296
|
+
def self.register_deferred(*symbols)
|
297
|
+
symbols.collect! { |s| s.to_s.downcase.to_sym }
|
298
|
+
symbols.each { |s| define_method(s) { |*args| LXL::Deferred.new(s, *args) } }
|
299
|
+
end
|
300
|
+
|
301
|
+
# Register uppercase constants of the same name and value.
|
302
|
+
#
|
303
|
+
# class MyNamespace < LXL::Namespace
|
304
|
+
# register_symbols :foo, :bar
|
305
|
+
# end
|
306
|
+
# LXL.new(MyNamespace.new).eval('=LIST(FOO, BAR)').inspect
|
307
|
+
# # => [:FOO, :BAR]
|
308
|
+
#
|
309
|
+
def self.register_symbols(*symbols)
|
310
|
+
symbols.collect! { |s| s.to_s.upcase.to_sym }
|
311
|
+
symbols.each { |s| const_set(s, s) }
|
312
|
+
end
|
313
|
+
|
314
|
+
end
|
315
|
+
|
316
|
+
# Constants and functions defined here will become available to formulas.
|
317
|
+
#
|
318
|
+
# class MyLXLNamespace < LXL::Namespace
|
319
|
+
# NAME = 'John Doe'
|
320
|
+
# def upper(text) text.to_s.upcase end
|
321
|
+
# end
|
322
|
+
#
|
323
|
+
# class MyLXL < LXL::Parser
|
324
|
+
# def self.namespace_class() MyLXLNamespace end
|
325
|
+
# end
|
326
|
+
#
|
327
|
+
# MyLXL.eval('=UPPER(NAME)')
|
328
|
+
# # => JOHN DOE
|
329
|
+
#
|
330
|
+
class LXL::Namespace < LXL::EmptyNamespace
|
331
|
+
|
332
|
+
TRUE = true
|
333
|
+
FALSE = false
|
334
|
+
NULL = nil
|
335
|
+
|
336
|
+
def and(a,b)
|
337
|
+
LXL.to_b(a) && LXL.to_b(b)
|
338
|
+
end
|
339
|
+
|
340
|
+
def or(a,b)
|
341
|
+
LXL.to_b(a) || LXL.to_b(b)
|
342
|
+
end
|
343
|
+
|
344
|
+
def if(c,a,b)
|
345
|
+
LXL.to_b(c) ? a : b
|
346
|
+
end
|
347
|
+
|
348
|
+
def list(*items)
|
349
|
+
items
|
350
|
+
end
|
351
|
+
|
352
|
+
def in(n,h)
|
353
|
+
h.respond_to?(:include?) ? h.include?(n) : false
|
354
|
+
end
|
355
|
+
|
356
|
+
def today
|
357
|
+
Date.today.ajd.to_f
|
358
|
+
end
|
359
|
+
|
360
|
+
def now
|
361
|
+
DateTime.now.ajd.to_f
|
362
|
+
end
|
363
|
+
|
364
|
+
def date(y,m,d)
|
365
|
+
Date.new(y,m,d).ajd.to_f
|
366
|
+
end
|
367
|
+
|
368
|
+
def datetime(text)
|
369
|
+
DateTime.parse(text.to_s).ajd.to_f
|
370
|
+
end
|
371
|
+
|
372
|
+
def time(h,m,s)
|
373
|
+
DateTime.valid_time?(h,m,s) ? DateTime.valid_time?(h,m,s).to_f : raise(ArgumentError, 'invalid time')
|
374
|
+
end
|
375
|
+
|
376
|
+
end
|
377
|
+
|
378
|
+
# John Carter's LittleLexer.
|
261
379
|
#
|
262
380
|
# http://www.rubyforge.org/projects/littlelexer
|
263
381
|
#
|
@@ -321,21 +439,25 @@ if $0 == __FILE__
|
|
321
439
|
|
322
440
|
formulas = %{
|
323
441
|
This is some text;
|
324
|
-
="This is some ""quoted"" text"
|
325
|
-
=((1+2)*(10-6))/2
|
326
|
-
=datetime("2004-11-22 11:11:00")=DATE(2004,11,22)+TIME(11,11,00)
|
327
|
-
=IN(" is ", "this is a string")
|
328
|
-
=LIST(1, "two", 3.0)
|
329
|
-
=IN("b", LIST("a", "b", "c"))
|
330
|
-
=AND(TRUE, NULL)
|
331
|
-
=OR(TRUE, FALSE)
|
332
|
-
=IF(1+1=2, "yes", "no")
|
442
|
+
="This is some ""quoted"" text"
|
443
|
+
=((1+2)*(10-6))/2
|
444
|
+
=datetime("2004-11-22 11:11:00")=DATE(2004,11,22)+TIME(11,11,00)
|
445
|
+
=IN(" is ", "this is a string")
|
446
|
+
=LIST(1, "two", 3.0)
|
447
|
+
=IN("b", LIST("a", "b", "c"))
|
448
|
+
=AND(TRUE, NULL)
|
449
|
+
=OR(TRUE, FALSE)
|
450
|
+
=IF(1+1=2, "yes", "no")
|
451
|
+
="this" & " and " & "that"
|
452
|
+
=2^3
|
453
|
+
=25%+25.2%
|
333
454
|
}
|
334
|
-
|
455
|
+
|
335
456
|
# single formula
|
336
457
|
puts LXL.eval('=5+5').inspect # => 10
|
337
458
|
|
338
459
|
# multiple formulas separated by semi-colon
|
339
|
-
puts LXL.eval(formulas).inspect
|
460
|
+
puts LXL.eval(formulas).inspect
|
461
|
+
# => ["This is some text", "This is some \"quoted\" text", 6, true, true, [1, "two", 3.0], true, false, true, "yes", "this and that", 8, 0.502]
|
340
462
|
|
341
463
|
end
|