satre 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +4 -1
- data/README.md +9 -7
- data/bin/exercise +64 -8
- data/lib/satre.rb +1 -3
- data/lib/satre/errors.rb +3 -0
- data/lib/satre/errors/argument_error.rb +2 -0
- data/lib/satre/errors/expression_error.rb +2 -0
- data/lib/satre/errors/parser_error.rb +2 -0
- data/lib/satre/formula.rb +3 -9
- data/lib/satre/formula/first_order_logic.rb +4 -0
- data/lib/satre/formula/first_order_logic/exists.rb +32 -0
- data/lib/satre/formula/first_order_logic/fol_formula.rb +35 -0
- data/lib/satre/formula/first_order_logic/forall.rb +32 -0
- data/lib/satre/formula/first_order_logic/relation.rb +38 -0
- data/lib/satre/formula/formula.rb +1 -13
- data/lib/satre/formula/propositional_logic.rb +9 -0
- data/lib/satre/formula/propositional_logic/and.rb +37 -0
- data/lib/satre/formula/propositional_logic/atom.rb +44 -0
- data/lib/satre/formula/propositional_logic/entails.rb +42 -0
- data/lib/satre/formula/{false.rb → propositional_logic/false.rb} +9 -2
- data/lib/satre/formula/propositional_logic/iff.rb +38 -0
- data/lib/satre/formula/propositional_logic/imp.rb +38 -0
- data/lib/satre/formula/propositional_logic/not.rb +34 -0
- data/lib/satre/formula/propositional_logic/or.rb +37 -0
- data/lib/satre/formula/{true.rb → propositional_logic/true.rb} +11 -2
- data/lib/satre/formula/term.rb +3 -0
- data/lib/satre/formula/term/function.rb +33 -0
- data/lib/satre/formula/term/term.rb +6 -0
- data/lib/satre/formula/term/variable.rb +24 -0
- data/lib/satre/parser.rb +4 -15
- data/lib/satre/parser/formula_parser.rb +74 -0
- data/lib/satre/parser/lexer.rb +55 -0
- data/lib/satre/parser/parser.rb +80 -0
- data/lib/satre/parser/term_parser.rb +85 -0
- data/lib/satre/version.rb +1 -1
- data/satre.gemspec +1 -0
- metadata +43 -20
- data/lib/satre/expression.rb +0 -6
- data/lib/satre/expression/add.rb +0 -26
- data/lib/satre/expression/const.rb +0 -23
- data/lib/satre/expression/expression.rb +0 -22
- data/lib/satre/expression/expression_parser.rb +0 -56
- data/lib/satre/expression/mul.rb +0 -30
- data/lib/satre/expression/var.rb +0 -14
- data/lib/satre/formula/and.rb +0 -26
- data/lib/satre/formula/atom.rb +0 -28
- data/lib/satre/formula/entails.rb +0 -29
- data/lib/satre/formula/iff.rb +0 -27
- data/lib/satre/formula/imp.rb +0 -25
- data/lib/satre/formula/not.rb +0 -22
- data/lib/satre/formula/or.rb +0 -25
- data/lib/satre/formula_parser.rb +0 -92
- data/lib/satre/lexer.rb +0 -47
@@ -0,0 +1,55 @@
|
|
1
|
+
module Satre
|
2
|
+
class Lexer
|
3
|
+
class << self
|
4
|
+
def matches
|
5
|
+
lambda { |pattern, str| str.split("").all? { |c| pattern.include? c } }
|
6
|
+
end
|
7
|
+
|
8
|
+
def matches(pattern, str)
|
9
|
+
str.split("").all? { |c| pattern.include? c }
|
10
|
+
end
|
11
|
+
|
12
|
+
def space?(str)
|
13
|
+
matches(" \t\n\r", str)
|
14
|
+
end
|
15
|
+
|
16
|
+
def punctuation?(str)
|
17
|
+
matches("()[]{}", str)
|
18
|
+
end
|
19
|
+
|
20
|
+
def symbolic?(str)
|
21
|
+
matches("~`!@#%$^&*-+<=>\\/|", str)
|
22
|
+
end
|
23
|
+
|
24
|
+
def numeric?(str)
|
25
|
+
matches("0123456789", str)
|
26
|
+
end
|
27
|
+
|
28
|
+
def alpanumeric?(str)
|
29
|
+
matches("abcdefghijklmnopqrstuvwxyz_'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", str)
|
30
|
+
end
|
31
|
+
|
32
|
+
def refute(*)
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
def lexwhile(prop, inp)
|
37
|
+
tokl = inp.split("").take_while { |c| self.send(prop, c) }.inject(:+)
|
38
|
+
tokl = "" if tokl.nil?
|
39
|
+
return tokl, inp[tokl.length..-1]
|
40
|
+
end
|
41
|
+
|
42
|
+
def lex(inp)
|
43
|
+
inp, rest = self.lexwhile(:space?, inp)
|
44
|
+
return [] if rest == "" || rest.nil?
|
45
|
+
c = rest[0]
|
46
|
+
cs = rest[1..-1]
|
47
|
+
prop = if self.alpanumeric?(c) then :alpanumeric?
|
48
|
+
elsif self.symbolic?(c) then :symbolic?
|
49
|
+
else :refute end
|
50
|
+
toktl, rest = self.lexwhile(prop, cs)
|
51
|
+
[c+toktl] + lex(rest)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'satre/parser/lexer'
|
2
|
+
require 'satre/formula'
|
3
|
+
require 'satre/errors'
|
4
|
+
|
5
|
+
module Satre
|
6
|
+
class Parser
|
7
|
+
class << self
|
8
|
+
def make_parser(parsing_function, input)
|
9
|
+
expr, rest = parsing_function.call(Lexer.lex(input))
|
10
|
+
fail(ParserError, "Unparsed input: #{rest}") unless rest == []
|
11
|
+
return expr
|
12
|
+
end
|
13
|
+
|
14
|
+
def is_const_name(string)
|
15
|
+
string.split('').all? { |c| Lexer.numeric?(c) } || string.nil?
|
16
|
+
end
|
17
|
+
|
18
|
+
###
|
19
|
+
# Generic parsing function
|
20
|
+
# sof: function -> takes the current input and combines it in some way with the items arravied at so far
|
21
|
+
# opupdate: function -> modifies the function appropriately when an new item is parsed.
|
22
|
+
###
|
23
|
+
def parse_ginfix(opsym, opupdate, sof, subparser, inp)
|
24
|
+
e1, inp1 = subparser.call(inp)
|
25
|
+
if inp1 != [] && inp1[0] == opsym
|
26
|
+
parse_ginfix(opsym, opupdate, opupdate.curry.call(sof, e1), subparser, inp1[1..-1])
|
27
|
+
else
|
28
|
+
return sof.call(e1), inp1
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# parse a list of items and combine them in a left associated manner
|
33
|
+
# returns a lambda
|
34
|
+
def parse_left_infix(opsym, opcon)
|
35
|
+
opupdate = ->(f, e1 ,e2) {opcon.call(f.call(e1), e2) }
|
36
|
+
sof = ->(x) { x }
|
37
|
+
method(:parse_ginfix).curry.call(opsym, opupdate, sof)
|
38
|
+
end
|
39
|
+
|
40
|
+
# parse a list of items and combine them in a right associated manner
|
41
|
+
# returns a lambda
|
42
|
+
def parse_right_infix(opsym, opcon)
|
43
|
+
opupdate = ->(f,e1,e2) { f.call(opcon.call(e1,e2)) }
|
44
|
+
sof = ->(x) { x }
|
45
|
+
method(:parse_ginfix).curry.call(opsym, opupdate, sof)
|
46
|
+
end
|
47
|
+
|
48
|
+
# parse a list of items and collect them in a list
|
49
|
+
def parse_list(opsym)
|
50
|
+
opupdate = ->(f,e1,e2) { f.call(e1) << e2 }
|
51
|
+
sof = ->(x) { [x] }
|
52
|
+
method(:parse_ginfix).curry.call(opsym, opupdate, sof)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Applies a function to the first element of a pair.
|
56
|
+
# The idea being to modify the returned abstract syntax tree while leaving
|
57
|
+
# the `unparsed input` alone
|
58
|
+
def papply(f, ast, rest)
|
59
|
+
return f.call(ast), rest
|
60
|
+
end
|
61
|
+
|
62
|
+
# Checks if the head of a list (typically the list of unparsed input)
|
63
|
+
# is some particular item, but also first checks that the list is nonempty
|
64
|
+
# before looking at its head
|
65
|
+
def nextin(inp, tok)
|
66
|
+
inp != [] && inp.first == tok
|
67
|
+
end
|
68
|
+
|
69
|
+
# Deals with the common situation of syntastic items enclosed in brackets
|
70
|
+
# It simply calls the subparser and then checks and eliminates
|
71
|
+
# the closing bracket. In principle, the therminating character can be anything,
|
72
|
+
# so this function could equally be used for other purposes.
|
73
|
+
def parse_bracketed(subparser, closing_character, inp)
|
74
|
+
ast, rest = subparser.call(inp)
|
75
|
+
return ast, rest[1..-1] if nextin(rest, closing_character)
|
76
|
+
fail(ExpressionError, "Expected closing character '#{closing_character}'")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'satre/parser/parser'
|
2
|
+
|
3
|
+
module Satre
|
4
|
+
class TermParser < Parser
|
5
|
+
class << self
|
6
|
+
|
7
|
+
# vs: set of bounds variables in the current scope
|
8
|
+
# inp: current input
|
9
|
+
def parse_atomic_term(vs, inp)
|
10
|
+
fail(ArgumentError, 'Term expected') if inp == []
|
11
|
+
head = inp.first
|
12
|
+
rest = inp[1..-1]
|
13
|
+
if head == '(' then parse_bracketed(method(:parse_term).curry.call(vs), ")", rest)
|
14
|
+
elsif head == '-' then papply( ->(t) { Function.new("-", [t])}, method(:parse_atomic_term).curry.call(vs), rest )
|
15
|
+
elsif Lexer.alpanumeric?(head) && rest[0] == '('
|
16
|
+
if rest[1] == ')'
|
17
|
+
papply( -> { Function.new(head, []) }, rest[2..-1])
|
18
|
+
else
|
19
|
+
ast, rest = parse_bracketed(parse_list(',').call(method(:parse_term).curry.call(vs)) , ')', rest[1..-1])
|
20
|
+
papply(->(args) { Function.new(head, args) }, ast, rest)
|
21
|
+
end
|
22
|
+
else
|
23
|
+
a = if is_const_name(head) && !vs.include?(head) then Function.new(head,[]) else Variable.new(head) end
|
24
|
+
return a, rest
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# build up parsing of general terms via parsing of the various infix operations
|
29
|
+
def parse_term(vs, inp)
|
30
|
+
colon = parse_right_infix("::", ->(e1,e2) { Function.new('::', [e1, e2]) })
|
31
|
+
plus = parse_right_infix("+", ->(e1,e2) { Function.new('+', [e1, e2]) })
|
32
|
+
minus = parse_left_infix("-", ->(e1,e2) { Function.new('-', [e1, e2]) })
|
33
|
+
mul = parse_right_infix("*", ->(e1,e2) { Function.new('*', [e1, e2]) })
|
34
|
+
div = parse_left_infix("/", ->(e1,e2) { Function.new('/', [e1, e2]) })
|
35
|
+
power = parse_left_infix("^", ->(e1,e2) { Function.new('^', [e1, e2]) })
|
36
|
+
atom = method(:parse_atomic_term).curry.call(vs)
|
37
|
+
colon.call(plus.call(minus.call(mul.call(div.call(power.call(atom))))), inp)
|
38
|
+
end
|
39
|
+
|
40
|
+
def is_const_name(string)
|
41
|
+
string.split('').all? { |c| Lexer.numeric?(c) } || string.nil?
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
def parse_atom(vs, inp)
|
46
|
+
FormulaParser.parse_infix_atom(vs, inp)
|
47
|
+
rescue ParserError
|
48
|
+
head = inp.first
|
49
|
+
rest = inp[1..-1]
|
50
|
+
if rest[0] == '('
|
51
|
+
rest = rest[1..-1]
|
52
|
+
if rest[0] == ')'
|
53
|
+
return Atom.new(Relation.new(head, [])), rest[1..-1]
|
54
|
+
else
|
55
|
+
ast, rest = parse_bracketed(parse_list(',').call(method(:parse_term).curry.call(vs)),')', rest)
|
56
|
+
papply( ->(args) { Atom.new(Relation.new(head,args))}, ast, rest)
|
57
|
+
end
|
58
|
+
else
|
59
|
+
return Atom.new(Relation.new(head, [])), rest if head != '('
|
60
|
+
fail(ParserError, 'Parse Atom')
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def parse_formula(ifn, afn, vs, inp)
|
65
|
+
parse_Entails = parse_right_infix("|=", ->(p,q) { Entails.new(p,q)})
|
66
|
+
parse_Iff = parse_right_infix("<=>", ->(p,q) { Iff.new(p,q)})
|
67
|
+
parse_Imp = parse_right_infix("==>", ->(p,q) { Imp.new(p,q)})
|
68
|
+
parse_Or = parse_right_infix("\\/", ->(p,q) { Or.new(p,q)})
|
69
|
+
parse_And = parse_right_infix("/\\", ->(p,q) { And.new(p,q)})
|
70
|
+
parse_Atom = method(:parse_atomic_formula).curry.call(ifn, afn, vs)
|
71
|
+
parse_Entails.call(parse_Imp.call(parse_Or.call(parse_And.call(parse_Iff.call(parse_Atom)))),inp)
|
72
|
+
end
|
73
|
+
|
74
|
+
def parse(inp)
|
75
|
+
make_parser(method(:parse_term).curry.call([]), inp)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class String
|
82
|
+
def to_term
|
83
|
+
Satre::TermParser.parse(self)
|
84
|
+
end
|
85
|
+
end
|
data/lib/satre/version.rb
CHANGED
data/satre.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: satre
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Roman C. Podolski
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-01-
|
11
|
+
date: 2016-01-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: terminal-table
|
@@ -164,6 +164,20 @@ dependencies:
|
|
164
164
|
- - ">="
|
165
165
|
- !ruby/object:Gem::Version
|
166
166
|
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: codeclimate-test-reporter
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
167
181
|
description: "\n I think therefore I am.\n \n Satre is a library for proportional
|
168
182
|
and first order logic.\n It was inspired by the book 'Handbook of practical logic
|
169
183
|
and automated reasoning' by Harrison, J (2009).\n \n This project originated at
|
@@ -188,27 +202,36 @@ files:
|
|
188
202
|
- bin/exercise
|
189
203
|
- bin/setup
|
190
204
|
- lib/satre.rb
|
191
|
-
- lib/satre/
|
192
|
-
- lib/satre/
|
193
|
-
- lib/satre/
|
194
|
-
- lib/satre/
|
195
|
-
- lib/satre/expression/expression_parser.rb
|
196
|
-
- lib/satre/expression/mul.rb
|
197
|
-
- lib/satre/expression/var.rb
|
205
|
+
- lib/satre/errors.rb
|
206
|
+
- lib/satre/errors/argument_error.rb
|
207
|
+
- lib/satre/errors/expression_error.rb
|
208
|
+
- lib/satre/errors/parser_error.rb
|
198
209
|
- lib/satre/formula.rb
|
199
|
-
- lib/satre/formula/
|
200
|
-
- lib/satre/formula/
|
201
|
-
- lib/satre/formula/
|
202
|
-
- lib/satre/formula/
|
210
|
+
- lib/satre/formula/first_order_logic.rb
|
211
|
+
- lib/satre/formula/first_order_logic/exists.rb
|
212
|
+
- lib/satre/formula/first_order_logic/fol_formula.rb
|
213
|
+
- lib/satre/formula/first_order_logic/forall.rb
|
214
|
+
- lib/satre/formula/first_order_logic/relation.rb
|
203
215
|
- lib/satre/formula/formula.rb
|
204
|
-
- lib/satre/formula/
|
205
|
-
- lib/satre/formula/
|
206
|
-
- lib/satre/formula/
|
207
|
-
- lib/satre/formula/
|
208
|
-
- lib/satre/formula/
|
209
|
-
- lib/satre/
|
210
|
-
- lib/satre/
|
216
|
+
- lib/satre/formula/propositional_logic.rb
|
217
|
+
- lib/satre/formula/propositional_logic/and.rb
|
218
|
+
- lib/satre/formula/propositional_logic/atom.rb
|
219
|
+
- lib/satre/formula/propositional_logic/entails.rb
|
220
|
+
- lib/satre/formula/propositional_logic/false.rb
|
221
|
+
- lib/satre/formula/propositional_logic/iff.rb
|
222
|
+
- lib/satre/formula/propositional_logic/imp.rb
|
223
|
+
- lib/satre/formula/propositional_logic/not.rb
|
224
|
+
- lib/satre/formula/propositional_logic/or.rb
|
225
|
+
- lib/satre/formula/propositional_logic/true.rb
|
226
|
+
- lib/satre/formula/term.rb
|
227
|
+
- lib/satre/formula/term/function.rb
|
228
|
+
- lib/satre/formula/term/term.rb
|
229
|
+
- lib/satre/formula/term/variable.rb
|
211
230
|
- lib/satre/parser.rb
|
231
|
+
- lib/satre/parser/formula_parser.rb
|
232
|
+
- lib/satre/parser/lexer.rb
|
233
|
+
- lib/satre/parser/parser.rb
|
234
|
+
- lib/satre/parser/term_parser.rb
|
212
235
|
- lib/satre/version.rb
|
213
236
|
- satre.gemspec
|
214
237
|
homepage: http://in.tum.de
|
data/lib/satre/expression.rb
DELETED
data/lib/satre/expression/add.rb
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
require 'satre/expression'
|
2
|
-
|
3
|
-
# A parster for propositional statements
|
4
|
-
# and simple mathematical expressions
|
5
|
-
module Satre
|
6
|
-
class Add < Expression
|
7
|
-
attr_reader :p
|
8
|
-
attr_reader :q
|
9
|
-
|
10
|
-
def initialize(p,q)
|
11
|
-
fail(ArgumentError, 'p must be a expression') unless p.is_a?(Expression)
|
12
|
-
fail(ArgumentError, 'q must be a expression') unless p.is_a?(Expression)
|
13
|
-
@p = p.dup.freeze
|
14
|
-
@q = q.dup.freeze
|
15
|
-
super "Add (#{@p},#{@q})"
|
16
|
-
end
|
17
|
-
|
18
|
-
def eval
|
19
|
-
p.eval + q.eval
|
20
|
-
end
|
21
|
-
|
22
|
-
def self.parse(e)
|
23
|
-
fail 'not yet implemented'
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
@@ -1,23 +0,0 @@
|
|
1
|
-
require 'satre/expression'
|
2
|
-
|
3
|
-
# A parster for propositional statements
|
4
|
-
# and simple mathematical expressions
|
5
|
-
module Satre
|
6
|
-
class Const < Expression
|
7
|
-
attr_reader :i
|
8
|
-
|
9
|
-
def initialize(i)
|
10
|
-
ArgumentError 'argument must be an integer' unless i.is_a? Integer
|
11
|
-
@i = i
|
12
|
-
super "Const #{i}"
|
13
|
-
end
|
14
|
-
|
15
|
-
def eval
|
16
|
-
i
|
17
|
-
end
|
18
|
-
|
19
|
-
def self.parse(e)
|
20
|
-
fail 'not yet implemented'
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
@@ -1,22 +0,0 @@
|
|
1
|
-
module Satre
|
2
|
-
class Expression
|
3
|
-
attr_reader :base
|
4
|
-
|
5
|
-
def initialize(base)
|
6
|
-
fail(ArgumentError, 'argument must be a string') unless base.is_a?(String)
|
7
|
-
@base = base.dup.freeze
|
8
|
-
end
|
9
|
-
|
10
|
-
def to_s
|
11
|
-
base
|
12
|
-
end
|
13
|
-
|
14
|
-
def eval # abstract method
|
15
|
-
fail 'abstract'
|
16
|
-
end
|
17
|
-
|
18
|
-
def simplify
|
19
|
-
fail 'not yet implemented'
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
@@ -1,56 +0,0 @@
|
|
1
|
-
require 'satre/parser'
|
2
|
-
|
3
|
-
module Satre
|
4
|
-
class ExpressionParser < Parser
|
5
|
-
class << self
|
6
|
-
def parse_expression
|
7
|
-
lambda do |inp|
|
8
|
-
e1, i1 = parse_product(inp)
|
9
|
-
if i1[0] == '+'
|
10
|
-
e2, i2 = parse_expression.call(i1.drop(1))
|
11
|
-
return Add.new(e1, e2), i2
|
12
|
-
end
|
13
|
-
return e1, i1
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
def parse_product(inp)
|
18
|
-
e1, i1 = parse_atom(inp)
|
19
|
-
if i1[0] == '*'
|
20
|
-
e2,i2 = parse_expression.call(i1.drop(1))
|
21
|
-
return Mul.new(e1, e2), i2
|
22
|
-
end
|
23
|
-
return e1, i1
|
24
|
-
end
|
25
|
-
|
26
|
-
def parse_atom(inp)
|
27
|
-
fail(ArgumentError, 'Expected an expression at end of input') if inp == []
|
28
|
-
if inp[0] == '('
|
29
|
-
e2, i2 = parse_expression.call(inp.drop(1))
|
30
|
-
if i2[0] == ')'
|
31
|
-
return e2, i2.drop(1)
|
32
|
-
else
|
33
|
-
fail(ExpressionError, 'Expected closing bracket')
|
34
|
-
end
|
35
|
-
else
|
36
|
-
if Lexer.numeric?.call(inp[0])
|
37
|
-
return Const.new(inp[0].to_i), inp.drop(1)
|
38
|
-
else
|
39
|
-
return Var.new(inp[0]), inp.drop(1)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
def parse(inp)
|
45
|
-
make_parser.curry.call(parse_expression).call(inp)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
class String
|
53
|
-
def to_expression
|
54
|
-
Satre::ExpressionParser.parse(self)
|
55
|
-
end
|
56
|
-
end
|