import unittest import simplepcp as sp # Unittest for simplepcp.py (demo precedence climbing parser) # =========================================================== # Licence (2-clause BSD License) (see minimalpcp). # Copyright 2017 JoeCreu # See minimalpcp.py and minimalpcp.pdf for a description of the # minimalpcp parser; minimalpcp is similar, but still simpler. See the # comments in simplepcp.py. # The items in test 2 ('test_02_annika_aasa_1992') are from Annika # Aasa, 'User Defined Syntax', Göteborg (1992), pp. 69 to 79. The # order of precedences (binding powers) has been inverted (Aasa uses # smaller numbers for higher precedences). Here, greater binding power # numbers always mean higher precedence. Moreover, the symbol & is used # instead of $ (because $ is considered as an alphabetic character). # 'test_03_predef_data' uses the test data contained in simplepcp.py. # Python versions 3.* should work, possibly 2.7.* will work. # JoeCreu, 2017-06-13 # Usage: # python3 simplepcp_test.py -v # The test items are of the form # self.assertEqual(sp.parse([string_to_be_parsed], # [syntax]), # [parse_result]) # [syntax] is a Python dictionary of "operator:binding information"- # pairs or a list containing such a dictionary and control structure # definitions. "Binding-information" is a tuple containing parsing # information for an operator. In case of infix operators, "binding # information" contains at least the left and the right binding power # of the operator; it may contain addition information. # [parse_result] is a string containing the parse tree in the form of a # lisp-like nested list structure. See definition of a syntax example # (synA) in simplepcp.py; synA is used here in tests 3 to 10. In the # parse results, dummy operands and operators are not removed. class testsimplepcp(unittest.TestCase) : def test_01_literal(self) : # In test_01_literal the syntax is given literally. self.assertEqual(sp.parse("a + b",{"+":(6,7)}),"(+ a b)") self.assertEqual(sp.parse("a + 3 * b",{"+":(6,7),"*":(8,9)}), "(+ a (* 3 b))") self.assertEqual(sp.parse("a + not b !", {"+":(6,7),"not":("pre",3),"!":("post",11)}), "(+ a (not $PREDUMMY (! b $POSTDUMMY)))") self.assertEqual(sp.parse("not A and B", {"and":(1,2),"not":("pre",3),"!":("post",11)}), "(and (not $PREDUMMY A) B)") self.assertEqual(sp.parse("not A*B", {"*":(5,6),"not":("pre",3),"!":("post",11)}), "(not $PREDUMMY (* A B))") self.assertEqual(sp.parse("2^not A * B", {"*":(5,6),"^":(8,7),"not":("pre",3)}), "(^ 2 (not $PREDUMMY (* A B)))") self.assertEqual(sp.parse("2^not A and B", {"and":(4,5),"^":(8,7),"not":("pre",6)}), "(and (^ 2 (not $PREDUMMY A)) B)") self.assertEqual(sp.parse("2 and not A^B", {"and":(4,5),"^":(8,7),"not":("pre",6)}), "(and 2 (not $PREDUMMY (^ A B)))") self.assertEqual(sp.parse("a!*b!", {"*":(6,7),"!":("post",5)}), "(! (* (! a $POSTDUMMY) b) $POSTDUMMY)") self.assertEqual(sp.parse("not A and not not B", {"and":(4,5),"^":(8,7),"not":("pre",6)}), "(and (not $PREDUMMY A) " + "(not $PREDUMMY (not $PREDUMMY B)))") def test_02_annika_aasa_1992(self) : self.assertEqual(sp.parse("2*3!+4", {"+":(2,2),"*":(3,3),"!":("post",5)}), "(+ (* 2 (! 3 $POSTDUMMY)) 4)") self.assertEqual(sp.parse("3+&4", {"+":(2,2),"&":("pre",1),"#":("pre",3)}), "(+ 3 (& $PREDUMMY 4))") self.assertEqual(sp.parse("&2+5", {"+":(2,2),"&":("pre",1),"#":("pre",3)}), "(& $PREDUMMY (+ 2 5))") self.assertEqual(sp.parse("#6+7", {"+":(2,2),"$":("pre",1),"#":("pre",3)}), "(+ (# $PREDUMMY 6) 7)") self.assertEqual(sp.parse("#&2+7", {"+":(2,2),"&":("pre",1),"#":("pre",3)}), "(# $PREDUMMY (& $PREDUMMY (+ 2 7)))") self.assertEqual(sp.parse("7?+8", {"?":("post",1),"+":(2,2),"!":("post",3), "*":(4,4)}), "(+ (? 7 $POSTDUMMY) 8)") self.assertEqual(sp.parse("3?!", {"?":("post",1),"+":(2,2),"!":("post",3), "*":(4,4)}), "(! (? 3 $POSTDUMMY) $POSTDUMMY)") self.assertEqual(sp.parse("9+6?*8", {"?":("post",1),"+":(2,2),"!":("post",3),"*":(4,4)}), "(* (? (+ 9 6) $POSTDUMMY) 8)") self.assertEqual(sp.parse("3*4?", {"?":("post",1),"+":(2,2),"!":(3,100),"*":(4,4)}), "(? (* 3 4) $POSTDUMMY)") self.assertEqual(sp.parse("4?*3", {"?":("post",1),"+":(2,2),"!":("post",3),"*":(4,4)}), "(* (? 4 $POSTDUMMY) 3)") self.assertEqual(sp.parse("5+4?*3", {"?":("post",1),"+":(2,2),"!":("post",3),"*":(4,4)}), "(* (? (+ 5 4) $POSTDUMMY) 3)") def test_03_predef_data(self) : self.assertEqual(sp.parse(sp.ts1,sp.synA), "(* 12 x)") self.assertEqual(sp.parse(sp.ts2,sp.synA), "(* 3 (not $PREDUMMY (! a $POSTDUMMY)))") self.assertEqual(sp.parse(sp.ts3,sp.synA), "(= res (- (+ (* 4 3) (^ n 12)) x2))") self.assertEqual(sp.parse(sp.ts4,sp.synA), "(= res (- (+ (* 4 3 a) (^ n (^ 3 2))) x2))") self.assertEqual(sp.parse(sp.ts5,sp.synA), "(:= f (-> x (and A B)))") self.assertEqual(sp.parse(sp.ts6,sp.synA), "(:= f (-> x (and (not $PREDUMMY A) (not $PREDUMMY " + "(! B $POSTDUMMY)))))") def test_04_andy_chu_arith(self) : self.assertEqual(sp.parse('1 + 2 + 3',sp.synA), '(+ 1 2 3)') self.assertEqual(sp.parse('1 + 2*3',sp.synA), '(+ 1 (* 2 3))') self.assertEqual(sp.parse('4*(2+3)',sp.synA), '(* 4 (+ 2 3))') self.assertEqual(sp.parse('(2 + 3)*4',sp.synA), '(* (+ 2 3) 4)') self.assertEqual(sp.parse('1<2',sp.synA), '(< 1 2)') self.assertEqual(sp.parse('x = 3',sp.synA), '(= x 3)') self.assertEqual(sp.parse('x = 2*3',sp.synA), '(= x (* 2 3))') self.assertEqual(sp.parse('x*y - y*z',sp.synA), '(- (* x y) (* y z))') self.assertEqual(sp.parse('x/y - y%z',sp.synA), '(- (/ x y) (% y z))') self.assertEqual(sp.parse('2**3**2',sp.synA), '(** 2 (** 3 2))') self.assertEqual(sp.parse('a = b = 10',sp.synA), '(= a (= b 10))') self.assertEqual(sp.parse('x = ((y*4) - 2)',sp.synA), '(= x (- (* y 4) 2))') self.assertEqual(sp.parse('x - - y',sp.synA), '(- x ($PRE- $PREDUMMY y))') # In the following two items my (JC) parsing is different from Chu's # I consider my result correct (the result is what I want it to be). self.assertEqual(sp.parse("-1 * -2",sp.synA), "($PRE- $PREDUMMY (* 1 -2))") self.assertEqual(sp.parse("-x * -y",sp.synA), "($PRE- $PREDUMMY (* x ($PRE- $PREDUMMY y)))") self.assertEqual(sp.parse('x - - 234',sp.synA), '(- x -234)') self.assertEqual(sp.parse('x += y += 3',sp.synA), '(+= x (+= y 3))') self.assertEqual(sp.parse('x[1,2]',sp.synA), '($index x ([ 1 2))') self.assertEqual(sp.parse('+ 1 - + 2',sp.synA), '(- 1 2)') self.assertEqual(sp.parse('- 3 ^ - 5*a',sp.synA), '($PRE- $PREDUMMY (^ 3 ($PRE- $PREDUMMY (* 5 a))))') def test_05_andy_chu_ternary(self) : self.assertEqual(sp.parse("a>b ? 0 : 1",sp.synA), "($post? ($pre? (> a b) (? 0)) 1)") self.assertEqual(sp.parse("a>b ? x+1 : y+1",sp.synA), "($post? ($pre? (> a b) (? (+ x 1))) " + "(+ y 1))") self.assertEqual(sp.parse( "1 ? true1 : 2 ? true2 : false",sp.synA), "($post? ($pre? ($post? ($pre? 1 (? true1)) 2)" + " (? true2)) false)") self.assertEqual(sp.parse("1 ? true1 : (2 ? true2 : false)", sp.synA), "($post? ($pre? 1 (? true1)) " + "($post? ($pre? 2 (? true2)) false))") self.assertEqual(sp.parse( "1 ? (2 ? true : false1) : false2",sp.synA), "($post? ($pre? 1 (? ($post? ($pre? 2 (? true))" + " false1))) false2)") self.assertEqual(sp.parse("x ? 1 : 2, y ? 3 : 4",sp.synA), "(, ($post? ($pre? x (? 1)) 2) " + "($post? ($pre? y (? 3)) 4))") # The following is from JMBourguet on # https://www.reddit.com/r/oilshell/comments/5l70p7 # /pratt_parsing_and_precedence_climbing_are_the/ self.assertEqual(sp.parse("a,b ? c,d : e,f",sp.synA), "(, a ($post? ($pre? b (? c d)) e) f)") def test_06_andy_chu_unary(self) : self.assertEqual(sp.parse("! x",sp.synA), "($PRE! $PREDUMMY x)") self.assertEqual(sp.parse("-- x",sp.synA), "($PRE-- $PREDUMMY x)") self.assertEqual(sp.parse("x!",sp.synA), "(! x $POSTDUMMY)") self.assertEqual(sp.parse("x--",sp.synA), "(-- x $POSTDUMMY)") self.assertEqual(sp.parse("x[1]--",sp.synA), "(-- ($index x ([ 1)) $POSTDUMMY)") self.assertEqual(sp.parse("++x[1]",sp.synA), "($PRE++ $PREDUMMY ($index x ([ 1)))") self.assertEqual(sp.parse("!x--",sp.synA), "($PRE! $PREDUMMY (-- x $POSTDUMMY))") self.assertEqual(sp.parse("x++-y++",sp.synA), "(- (++ x $POSTDUMMY) (++ y $POSTDUMMY))") self.assertEqual(sp.parse("++x-++y",sp.synA), "(- ($PRE++ $PREDUMMY x) ($PRE++ $PREDUMMY y))") def test_07_andy_chu_array_comma(self) : self.assertEqual(sp.parse('x[1]',sp.synA), '($index x ([ 1))') self.assertEqual(sp.parse('x[a+b]',sp.synA), '($index x ([ (+ a b)))') self.assertEqual(sp.parse('x = 1, y = 2, z = 3',sp.synA), '(, (= x 1) (= y 2) (= z 3))') def test_08_andy_chu_funcall(self) : self.assertEqual(sp.parse( 'x = y(2) * 3 + y(4) * 5',sp.synA), '(= x (+ (* ($apply y 2) 3) (* ($apply y 4) 5)))') self.assertEqual(sp.parse('x(1,2) + y(3,4)',sp.synA), '(+ ($apply x 1 2) ($apply y 3 4))') self.assertEqual(sp.parse('f(g)(x)',sp.synA), '($apply ($apply f g) x)') self.assertEqual(sp.parse('f(g(x))',sp.synA), '($apply f ($apply g x))') self.assertEqual(sp.parse('f([:x,y,u:])',sp.synA), '($apply f ([: x y u))') self.assertEqual(sp.parse('begin a+b end(c)',sp.synA), '($apply (begin (+ a b)) c)') self.assertEqual(sp.parse('x(a,b,c[d])',sp.synA), '($apply x a b ($index c ([ d)))') self.assertEqual(sp.parse( 'x(1,2) * j + y(3,4) * k + z(5,6) * l',sp.synA), '(+ (* ($apply x 1 2) j) (* ($apply y 3 4) k) ' + '(* ($apply z 5 6) l))') self.assertEqual(sp.parse('print(test(2,3))',sp.synA), '($apply print ($apply test 2 3))') self.assertEqual(sp.parse('min(255,n * 2)',sp.synA), '($apply min 255 (* n 2))') self.assertEqual(sp.parse('c = pal[i * 8]',sp.synA), '(= c ($index pal ([ (* i 8))))') def test_09_parentheses_and_more_funcall(self) : self.assertEqual(sp.parse('f(a + b)',sp.synA), '($apply f (+ a b))') self.assertEqual(sp.parse('f((a + b))',sp.synA), '($apply f (+ a b))') self.assertEqual(sp.parse('3**(a + b)',sp.synA), '(** 3 (+ a b))') self.assertEqual(sp.parse('f((((not x))!))',sp.synA), '($apply f (! (not $PREDUMMY x) $POSTDUMMY))') self.assertEqual(sp.parse('3**(((a + b)))',sp.synA), '(** 3 (+ a b))') self.assertEqual(sp.parse('f(g(h(i(x))))',sp.synA), '($apply f ($apply g ($apply h ($apply i x))))') self.assertEqual(sp.parse('f(g(x),h(y))',sp.synA), '($apply f ($apply g x) ($apply h y))') self.assertEqual(sp.parse('f(g[i(x)])',sp.synA), '($apply f ($index g ([ ($apply i x))))') self.assertEqual(sp.parse('f(x)**3',sp.synA), '(** ($apply f x) 3)') self.assertEqual(sp.parse('f()**3',sp.synA), '(** ($apply f) 3)') self.assertEqual(sp.parse('f() + g(h())',sp.synA), '(+ ($apply f) ($apply g ($apply h)))') # The following items will probably make sense if (f x), a[i], # begin a end evaluate to functions or indexable objects. self.assertEqual(sp.parse('f()[g()]',sp.synA), '($index ($apply f) ([ ($apply g)))') self.assertEqual(sp.parse('f()()',sp.synA), '($apply ($apply f))') self.assertEqual(sp.parse('begin a end(x)',sp.synA), '($apply (begin a) x)') self.assertEqual(sp.parse('begin a end()',sp.synA), '($apply (begin a))') self.assertEqual(sp.parse('(f(x))(y)',sp.synA), '($apply ($apply f x) y)') self.assertEqual(sp.parse('(f(x))(g(y))',sp.synA), '($apply ($apply f x) ($apply g y))') self.assertEqual(sp.parse('(a[i])(g(y))',sp.synA), '($apply ($index a ([ i)) ($apply g y))') def test_10_control_structures(self) : # Control structures to be tested here are: ... ? ... : ..., # do ... while ..., begin ... end, [: ... :] self.assertEqual(sp.parse( 'do a = b , a ** -3 while a<0.1',sp.synA), "($postwhile (do (= a b) (** a -3)) " + "(< a 0.1))") self.assertEqual(sp.parse( 'begin do a = b , a ** -3 while a<0.1 end (x)',sp.synA), "($apply (begin ($postwhile (do (= a b) " + "(** a -3)) (< a 0.1))) x)") self.assertEqual(sp.parse( 'do a<10 ? a : (b := 2*a) while a % 3 = 1',sp.synA), "($postwhile (do ($post? ($pre? (< a 10) (? a)) " + "(:= b (* 2 a)))) (= (% a 3) 1))") self.assertEqual(sp.parse( 'do a = b , a ** -3 , ' + 'do x:=x+2 while x<7 while abs(a - b) < 0.1',sp.synA), "($postwhile (do (= a b) (** a -3)" + " ($postwhile (do (:= x (+ x 2))) (< x 7))) " + "(< ($apply abs (- a b)) 0.1))") self.assertEqual(sp.parse( 'ar := [: [: 3,s:],[:f(2),g(1+a[2.2]):]:]',sp.synA), "(:= ar ([: ([: 3 s) ([: ($apply f 2) " + "($apply g (+ 1 ($index a ([ 2.2)))))))") if __name__ == "__main__" : unittest.main()