rparsec-ruby19 1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,5 @@
1
+ # $: << "#{File.dirname(__FILE__)}/../.."
2
+
3
+ def import *names
4
+ names.each {|lib|require "rparsec/#{lib}"}
5
+ end
@@ -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