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.
@@ -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