rparsec-ruby19 1.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/rparsec.rb +3 -0
- data/rparsec/context.rb +83 -0
- data/rparsec/error.rb +28 -0
- data/rparsec/expressions.rb +184 -0
- data/rparsec/functors.rb +274 -0
- data/rparsec/id_monad.rb +17 -0
- data/rparsec/keywords.rb +114 -0
- data/rparsec/locator.rb +40 -0
- data/rparsec/misc.rb +130 -0
- data/rparsec/monad.rb +62 -0
- data/rparsec/operators.rb +103 -0
- data/rparsec/parser.rb +894 -0
- data/rparsec/parser_monad.rb +23 -0
- data/rparsec/parsers.rb +623 -0
- data/rparsec/token.rb +43 -0
- data/test/src/expression_test.rb +124 -0
- data/test/src/full_parser_test.rb +95 -0
- data/test/src/functor_test.rb +66 -0
- data/test/src/import.rb +5 -0
- data/test/src/keyword_test.rb +28 -0
- data/test/src/operator_test.rb +21 -0
- data/test/src/parser_test.rb +53 -0
- data/test/src/perf_benchmark.rb +25 -0
- data/test/src/s_expression_test.rb +33 -0
- data/test/src/scratch.rb +41 -0
- data/test/src/simple_monad_test.rb +22 -0
- data/test/src/simple_parser_test.rb +423 -0
- data/test/src/sql.rb +268 -0
- data/test/src/sql_parser.rb +258 -0
- data/test/src/sql_test.rb +128 -0
- data/test/src/tests.rb +13 -0
- metadata +95 -0
data/rparsec/token.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rparsec/misc'
|
2
|
+
|
3
|
+
module RParsec
|
4
|
+
|
5
|
+
#
|
6
|
+
# Represents a token during lexical analysis.
|
7
|
+
#
|
8
|
+
class Token
|
9
|
+
extend DefHelper
|
10
|
+
|
11
|
+
def_ctor :kind, :text, :index
|
12
|
+
|
13
|
+
#
|
14
|
+
# The type of the token
|
15
|
+
#
|
16
|
+
attr_reader :kind
|
17
|
+
|
18
|
+
#
|
19
|
+
# The text of the matched range
|
20
|
+
#
|
21
|
+
attr_reader :text
|
22
|
+
|
23
|
+
#
|
24
|
+
# The starting index of the matched range
|
25
|
+
#
|
26
|
+
attr_reader :index
|
27
|
+
|
28
|
+
#
|
29
|
+
# The length of the token.
|
30
|
+
#
|
31
|
+
def length
|
32
|
+
@text.length
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
# String representation of the token.
|
37
|
+
#
|
38
|
+
def to_s
|
39
|
+
"#{@kind}: #{@text}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end # module
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'import'
|
2
|
+
import :parsers, :functors, :expressions
|
3
|
+
require 'parser_test'
|
4
|
+
|
5
|
+
class ExpressionParserTest < ParserTestCase
|
6
|
+
include Functors
|
7
|
+
def setup
|
8
|
+
@use_around = true
|
9
|
+
end
|
10
|
+
def around(p)
|
11
|
+
return p unless @use_around
|
12
|
+
delim = whitespace.many_
|
13
|
+
p << delim;
|
14
|
+
end
|
15
|
+
def negate
|
16
|
+
around(char('-')) >> value(Neg)
|
17
|
+
end
|
18
|
+
def positive
|
19
|
+
around(char('+')) >> value(Id)
|
20
|
+
end
|
21
|
+
def increment
|
22
|
+
around(string('++') >> value(Inc))
|
23
|
+
end
|
24
|
+
def decrement
|
25
|
+
around(string('--')) >> value(Dec)
|
26
|
+
end
|
27
|
+
def plus
|
28
|
+
around(char('+')) >> value(Plus)
|
29
|
+
end
|
30
|
+
def minus
|
31
|
+
around(char('-')) >> value(Minus)
|
32
|
+
end
|
33
|
+
def mul
|
34
|
+
around(char('*')) >> value(Mul)
|
35
|
+
end
|
36
|
+
def space_mul
|
37
|
+
not_among('+-*/%').peek >> value(Mul)
|
38
|
+
end
|
39
|
+
def div
|
40
|
+
around(char('/')) >> value(Div)
|
41
|
+
end
|
42
|
+
def rdiv
|
43
|
+
around(str('//')) >> value(Div)
|
44
|
+
end
|
45
|
+
def mod
|
46
|
+
around(char('%')) >> value(Mod)
|
47
|
+
end
|
48
|
+
def lparen
|
49
|
+
around(char('('))
|
50
|
+
end
|
51
|
+
def rparen
|
52
|
+
around(char(')'))
|
53
|
+
end
|
54
|
+
def int
|
55
|
+
around(integer).map(&To_i)
|
56
|
+
end
|
57
|
+
def testPrefix
|
58
|
+
parser = whitespace.many_ >> int.prefix(negate) << eof
|
59
|
+
assertParser(' - -3 ', 3, parser)
|
60
|
+
assertParser(' 3', 3, parser)
|
61
|
+
assertParser('3 ', 3, parser)
|
62
|
+
assertParser('-3', -3, parser)
|
63
|
+
assertError(' - -', 'integer expected', parser, 4)
|
64
|
+
assertError(' - -5 3', 'EOF expected', parser, 6)
|
65
|
+
end
|
66
|
+
def testPostfix
|
67
|
+
parser = whitespace.many_ >> int.postfix(increment) << eof
|
68
|
+
assertParser(' 3++ ++', 5, parser)
|
69
|
+
assertParser('3++++ ', 5, parser)
|
70
|
+
assertParser(' 3', 3, parser)
|
71
|
+
assertError(' ++', 'integer expected', parser, 1)
|
72
|
+
assertError('5++ 3', 'EOF expected', parser, 4)
|
73
|
+
end
|
74
|
+
def testInfixn
|
75
|
+
parser = whitespace.many_ >> int.infixn(plus) << eof
|
76
|
+
assertParser(' 1 + 2 ', 3, parser)
|
77
|
+
assertParser('1 + 2 ', 3, parser)
|
78
|
+
assertError('1+2 +3', 'EOF expected', parser, 4)
|
79
|
+
end
|
80
|
+
def testInfixl
|
81
|
+
parser = whitespace.many_ >> int.infixl(minus) << eof
|
82
|
+
assertParser(' 1-2 -3 ', -4, parser)
|
83
|
+
assertParser('1 - 2 ', -1, parser)
|
84
|
+
assertParser('1 ', 1, parser)
|
85
|
+
assertError('1-2-3-', 'integer expected', parser, 6)
|
86
|
+
end
|
87
|
+
def testInfixr
|
88
|
+
parser = whitespace.many_ >> int.infixr(minus) << eof
|
89
|
+
assertParser(' 1-2 -3 ', 2, parser)
|
90
|
+
assertParser(' 1-2 -3-4 ', -2, parser)
|
91
|
+
assertParser('1 - 2 ', -1, parser)
|
92
|
+
assertParser('1 ', 1, parser)
|
93
|
+
assertError('1-2-3-', 'integer expected', parser, 6)
|
94
|
+
end
|
95
|
+
def testExpression
|
96
|
+
@use_around = false
|
97
|
+
ops = OperatorTable.new do |tbl|
|
98
|
+
tbl.infixl(plus, 20)
|
99
|
+
tbl.infixl(minus, 20)
|
100
|
+
tbl.infixl(mul, 40)
|
101
|
+
tbl.infixl(space_mul, 40)
|
102
|
+
tbl.infixl(div, 40)
|
103
|
+
tbl.prefix(negate, 60)
|
104
|
+
tbl.prefix(positive, 60)
|
105
|
+
tbl.postfix(increment, 50)
|
106
|
+
tbl.postfix(decrement, 50)
|
107
|
+
tbl.infixr(rdiv, 40)
|
108
|
+
end
|
109
|
+
expr = nil
|
110
|
+
term = int | char(?() >> lazy{expr} << char(?))
|
111
|
+
delim = whitespace.many_
|
112
|
+
expr = delim >> Expressions.build(term, ops, delim)
|
113
|
+
|
114
|
+
assertParser('1', 1, expr)
|
115
|
+
assertParser('1+2', 3, expr)
|
116
|
+
assertParser('(1-2)', -1, expr)
|
117
|
+
assertParser('2-3* 2', -4, expr)
|
118
|
+
assertParser("\n ((2-3 )*-+2--) ", 3, expr)
|
119
|
+
assertParser('((2-3 )*-+2--/2//2) ', 3, expr)
|
120
|
+
assertParser('((2-3 )*-+2--/2//2--) ', 1, expr)
|
121
|
+
assertParser('((2-3 )*-+2--//4//2) ', 2, expr)
|
122
|
+
assertParser('((2-3 )*-+2--/2/2) ', 0, expr)
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'import'
|
2
|
+
import :parsers, :keywords, :operators, :functors, :expressions
|
3
|
+
require 'parser_test'
|
4
|
+
|
5
|
+
class FullParserTest < ParserTestCase
|
6
|
+
def calculate_simple_cases(val, cases, default)
|
7
|
+
cases.each do |cond, consequence|
|
8
|
+
# cond, consequence = *cs
|
9
|
+
return consequence if cond == val
|
10
|
+
end
|
11
|
+
default
|
12
|
+
end
|
13
|
+
def calculate_full_cases(cases, default)
|
14
|
+
cases.each do |cond, consequence|
|
15
|
+
return consequence if cond
|
16
|
+
end
|
17
|
+
default
|
18
|
+
end
|
19
|
+
def parser
|
20
|
+
keywords = Keywords.case_sensitive(%w{case when else end and or not true false})
|
21
|
+
ops = Operators.new(%w{+ - * / % ++ -- == > < >= <= != : ( )})
|
22
|
+
lexer = integer.token(:int)|keywords.lexer|ops.lexer
|
23
|
+
delim = whitespaces |comment_line('#')
|
24
|
+
lexeme = lexer.lexeme(delim) << eof
|
25
|
+
expr = nil
|
26
|
+
lazy_expr = lazy{expr}
|
27
|
+
compare = ops['>'] >> Gt | ops['<'] >> Lt | ops['>='] >> Ge | ops['<='] >> Le |
|
28
|
+
ops['=='] >> Eq | ops['!='] >> Ne
|
29
|
+
comparison = sequence(lazy_expr, compare, lazy_expr) {|e1,f,e2|f.call(e1,e2)}
|
30
|
+
bool = nil
|
31
|
+
lazy_bool = lazy{bool}
|
32
|
+
bool_term = keywords[:true] >> true | keywords[:false] >> false |
|
33
|
+
comparison | ops['('] >> lazy_bool << ops[')']
|
34
|
+
bool_table = OperatorTable.new.
|
35
|
+
infixl(keywords[:or] >> Or, 20).
|
36
|
+
infixl(keywords[:and] >> And, 30).
|
37
|
+
infixl(keywords[:not] >> Not, 30)
|
38
|
+
|
39
|
+
bool = Expressions.build(bool_term, bool_table)
|
40
|
+
simple_case = sequence(keywords[:when], lazy_expr, ops[':'], lazy_expr) do |w,cond,t,val|
|
41
|
+
[cond, val]
|
42
|
+
end
|
43
|
+
full_case = sequence(keywords[:when], bool, ops[':'], lazy_expr) do |w,cond,t,val|
|
44
|
+
[cond, val]
|
45
|
+
end
|
46
|
+
default_case = (keywords[:else] >> lazy_expr).optional
|
47
|
+
simple_when_then = sequence(lazy_expr, simple_case.many, default_case,
|
48
|
+
keywords[:end]) do |val, cases, default|
|
49
|
+
calculate_simple_cases(val, cases, default)
|
50
|
+
end
|
51
|
+
full_when_then = sequence(full_case.many, default_case, keywords[:end]) do |cases, default|
|
52
|
+
calculate_full_cases(cases, default)
|
53
|
+
end
|
54
|
+
case_expr = keywords[:case] >> (simple_when_then | full_when_then)
|
55
|
+
|
56
|
+
term = token(:int, &To_i) | (ops['('] >> lazy_expr << ops[')']) | case_expr
|
57
|
+
table = OperatorTable.new.
|
58
|
+
infixl(ops['+'] >> Plus, 20).
|
59
|
+
infixl(ops['-'] >> Minus, 20).
|
60
|
+
infixl(ops['*'] >> Mul, 30).
|
61
|
+
infixl(ops['/'] >> Div, 30).
|
62
|
+
postfix(ops['++'] >> Inc, 40).
|
63
|
+
postfix(ops['--'] >> Dec, 40).
|
64
|
+
prefix(ops['-'] >> Neg, 50)
|
65
|
+
expr = Expressions.build(term, table)
|
66
|
+
lexeme.nested(expr << eof)
|
67
|
+
end
|
68
|
+
def verify(code, expected=eval(code))
|
69
|
+
assertParser(code, expected, self.parser)
|
70
|
+
end
|
71
|
+
def testNumber
|
72
|
+
verify(' 1')
|
73
|
+
end
|
74
|
+
def testSimpleCalc
|
75
|
+
verify('1 - 2')
|
76
|
+
end
|
77
|
+
def testComplexCalculationWithComment
|
78
|
+
verify('2*15/-(5- -2) #this is test')
|
79
|
+
end
|
80
|
+
def testSimpleCaseWhen
|
81
|
+
verify('case 1 when 1: 0 else 1 end')
|
82
|
+
end
|
83
|
+
def testSimpleCaseWhenWithRegularCalc
|
84
|
+
verify('case 1 when 1*1: (1-2) when 3:4 end+1')
|
85
|
+
end
|
86
|
+
def testFullCaseWhen
|
87
|
+
assertParser('3*case when 1==0 and 1==1: 1 when 1==1 : 2 end', 6, parser)
|
88
|
+
begin
|
89
|
+
parser.parse('3*case when (1==0 and 1==1): 1 when 1==1 then 2 end')
|
90
|
+
fail('should have failed')
|
91
|
+
rescue ParserException => e
|
92
|
+
assert(e.message.include?(': expected, then at line 1, col 42'))
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'import'
|
2
|
+
require 'runit/testcase'
|
3
|
+
import :parsers, :functors
|
4
|
+
|
5
|
+
include RParsec
|
6
|
+
class Proc
|
7
|
+
include FunctorMixin
|
8
|
+
end
|
9
|
+
class Method
|
10
|
+
include FunctorMixin
|
11
|
+
end
|
12
|
+
|
13
|
+
class FunctorTestCase < RUNIT::TestCase
|
14
|
+
include Functors
|
15
|
+
def verify(expected, f, *args)
|
16
|
+
assert_equal(expected, f.call(*args))
|
17
|
+
end
|
18
|
+
def testConst
|
19
|
+
verify(1, const(1), 'a')
|
20
|
+
end
|
21
|
+
def testMethod
|
22
|
+
verify(true, 1.method(:kind_of?), Fixnum)
|
23
|
+
end
|
24
|
+
def testFlip
|
25
|
+
verify(1, Minus.flip, 1, 2)
|
26
|
+
end
|
27
|
+
def testCompose
|
28
|
+
verify(-3, Neg.compose(Plus), 1, 2)
|
29
|
+
verify(-3, Neg << Plus, 1, 2)
|
30
|
+
verify(-3, Plus >> Neg, 1, 2)
|
31
|
+
verify(3, Neg << Neg << Plus, 1, 2)
|
32
|
+
end
|
33
|
+
def testCurry
|
34
|
+
assert_equal(3, Plus.curry.call(1).call(2))
|
35
|
+
assert_equal(-1, Minus.curry.call(1).call(2))
|
36
|
+
end
|
37
|
+
def testReverse_curry
|
38
|
+
assert_equal(1, Minus.reverse_curry.call(1).call(2))
|
39
|
+
end
|
40
|
+
def testUncurry
|
41
|
+
verify(-1, Minus.curry.uncurry, 1, 2)
|
42
|
+
end
|
43
|
+
def testReverse_uncurry
|
44
|
+
verify(-1, Minus.reverse_curry.reverse_uncurry, 1, 2)
|
45
|
+
verify(1, Minus.reverse_curry.uncurry, 1, 2)
|
46
|
+
verify(1, Minus.curry.reverse_uncurry, 1, 2)
|
47
|
+
end
|
48
|
+
def testRepeat
|
49
|
+
cnt=0
|
50
|
+
inc = proc {cnt+=1}
|
51
|
+
n = 10
|
52
|
+
verify(n, (inc*n))
|
53
|
+
assert_equal(10, cnt)
|
54
|
+
end
|
55
|
+
def testPower
|
56
|
+
double = Mul.curry.call(2)
|
57
|
+
verify(8, double ** 3, 1)
|
58
|
+
verify(nil, double ** 0, 1)
|
59
|
+
end
|
60
|
+
def testNth
|
61
|
+
verify(2, nth(1), 1, 2, 3)
|
62
|
+
end
|
63
|
+
def testMethodIsMixedIn
|
64
|
+
verify(false, 1.method(:kind_of?).compose(Id), String)
|
65
|
+
end
|
66
|
+
end
|
data/test/src/import.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'import'
|
2
|
+
import :parsers, :keywords
|
3
|
+
require 'parser_test'
|
4
|
+
|
5
|
+
class KeywordTestCase < ParserTestCase
|
6
|
+
Insensitive = Keywords.case_insensitive(%w{select from where group by order having}){|x|x.downcase}
|
7
|
+
Sensitive = Keywords.case_sensitive(%w{new delete if else then void int}){|x|x}
|
8
|
+
def verifySensitiveKeyword(code, keyword)
|
9
|
+
assertParser(code, keyword, Sensitive.lexer.lexeme.nested(Sensitive.parser(keyword)))
|
10
|
+
assertParser(code, keyword, Sensitive.lexer.lexeme.nested(Sensitive.parser(keyword.to_sym)))
|
11
|
+
end
|
12
|
+
def verifyInsensitiveKeyword(code, keyword)
|
13
|
+
assertParser(code, keyword.downcase,
|
14
|
+
Insensitive.lexer.lexeme.nested(Insensitive.parser(keyword)))
|
15
|
+
assertParser(code, keyword.downcase,Insensitive.lexer.lexeme.nested(Insensitive.parser(keyword.downcase.to_sym)))
|
16
|
+
end
|
17
|
+
def testCaseSensitiveKeywords
|
18
|
+
verifySensitiveKeyword('new int void', 'new')
|
19
|
+
end
|
20
|
+
def testCaseInsensitiveKeywords
|
21
|
+
verifyInsensitiveKeyword('select from where', 'SeleCt')
|
22
|
+
end
|
23
|
+
def testBasics
|
24
|
+
assert(!Insensitive.case_sensitive?)
|
25
|
+
assert(Sensitive.case_sensitive?)
|
26
|
+
assert(:keyword, Sensitive.keyword_symbol)
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'import'
|
2
|
+
import :parsers, :operators, :functors
|
3
|
+
require 'parser_test'
|
4
|
+
|
5
|
+
class OperatorTestCase < ParserTestCase
|
6
|
+
Ops = Operators.new(%w{++ + - -- * / ~}, &Id)
|
7
|
+
def verifyToken(src, op)
|
8
|
+
verifyParser(src, op, Ops[op])
|
9
|
+
end
|
10
|
+
def verifyParser(src, expected, parser)
|
11
|
+
assertParser(src, expected, Ops.lexer.lexeme.nested(parser))
|
12
|
+
end
|
13
|
+
def testAll
|
14
|
+
verifyToken('++ -', '++')
|
15
|
+
verifyParser('++ + -- ++ - +', '-',
|
16
|
+
(Ops['++']|Ops['--']|Ops['+']).many_ >> Ops['-'])
|
17
|
+
end
|
18
|
+
def testSort
|
19
|
+
assert_equal(%w{+++ ++- ++ + --- -- -}, Operators.sort(%w{++ - + -- +++ ++- ---}))
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'import'
|
2
|
+
require 'rubyunit'
|
3
|
+
import :parsers, :functors
|
4
|
+
|
5
|
+
include RParsec
|
6
|
+
|
7
|
+
class ParserTestCase < RUNIT::TestCase
|
8
|
+
include Functors
|
9
|
+
include Parsers
|
10
|
+
def assertParser(code, expected, parser)
|
11
|
+
assert_equal(expected, parser.parse(code))
|
12
|
+
end
|
13
|
+
def assertError(code, expected, parser, index=0, line=1, col=1+index)
|
14
|
+
begin
|
15
|
+
parser.parse(code)
|
16
|
+
assert_fail("error should have happened")
|
17
|
+
rescue ParserException => e
|
18
|
+
assert_equal(index, e.index)
|
19
|
+
msg = expected
|
20
|
+
msg = add_encountered(msg, current(code,index)) << " at line #{line}, col #{col}." unless expected.include? 'at line'
|
21
|
+
assert_equal(msg, e.message)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
def assertGrammar(code, expected, lexer, grammar)
|
25
|
+
assertParser(code, expected, lexer.nested(grammar))
|
26
|
+
end
|
27
|
+
def assertGrammarError(code, expected, token_name, lexer, grammar, index=0, line=1, col=1+index)
|
28
|
+
parser = lexer.nested(grammar)
|
29
|
+
begin
|
30
|
+
parser.parse(code)
|
31
|
+
assert_fail("error should have happened")
|
32
|
+
rescue ParserException => e
|
33
|
+
assert_equal(index, e.index)
|
34
|
+
msg = expected
|
35
|
+
msg = "#{msg}, #{token_name}" << " at line #{line}, col #{col}." unless expected.include? 'at line'
|
36
|
+
assert_equal(msg, e.message)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
def current(code, index)
|
40
|
+
return "EOF" if code.length <= index
|
41
|
+
c = code[index]
|
42
|
+
if c.kind_of? Fixnum
|
43
|
+
"'"<<c<<"'"
|
44
|
+
else
|
45
|
+
c.to_s
|
46
|
+
end
|
47
|
+
end
|
48
|
+
def add_encountered(msg, encountered)
|
49
|
+
result = msg.dup
|
50
|
+
result << ', ' unless msg.strip.length == 0 || msg =~ /.*(\.|,)\s*$/
|
51
|
+
"#{result}#{encountered}"
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'import'
|
2
|
+
import :rparsec
|
3
|
+
require 'benchmark'
|
4
|
+
require 'rubyunit'
|
5
|
+
class PerfTestCase < RUNIT::TestCase
|
6
|
+
include Parsers
|
7
|
+
include Functors
|
8
|
+
def test1
|
9
|
+
code = "1+#{'1+' * 6000}1"
|
10
|
+
ops = OperatorTable.new do |tbl|
|
11
|
+
tbl.infixl(char(?+) >> Plus, 20)
|
12
|
+
tbl.infixl(char(?-) >> Minus, 20)
|
13
|
+
tbl.infixl(char(?*) >> Mul, 40)
|
14
|
+
tbl.infixl(char(?/) >> Div, 40)
|
15
|
+
tbl.prefix(char(?-) >> Neg, 60)
|
16
|
+
end
|
17
|
+
expr = nil
|
18
|
+
term = integer.map(&To_i) | char(?() >> lazy{expr} << char(?))
|
19
|
+
delim = whitespace.many_
|
20
|
+
expr = delim >> Expressions.build(term, ops, delim)
|
21
|
+
Benchmark.bm do |x|
|
22
|
+
x.report("parsing") {puts(expr.parse(code))}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|