rparsec-ruby19 1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|