Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
T
trs
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Taddeüs Kroes
trs
Commits
aa09d9d7
Commit
aa09d9d7
authored
Dec 08, 2011
by
Sander Mathijs van Veen
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Implemented CONCAT grammar, code cleanup and fixed unit tests.
parent
e1cd6997
Changes
5
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
173 additions
and
110 deletions
+173
-110
src/parser.py
src/parser.py
+85
-60
tests/parser.py
tests/parser.py
+45
-13
tests/test_calc.py
tests/test_calc.py
+40
-17
tests/test_parser.py
tests/test_parser.py
+3
-14
tests/test_variables.py
tests/test_variables.py
+0
-6
No files found.
src/parser.py
View file @
aa09d9d7
...
@@ -18,6 +18,11 @@ sys.path.insert(1, PYBISON_PYREX)
...
@@ -18,6 +18,11 @@ sys.path.insert(1, PYBISON_PYREX)
from
bison
import
BisonParser
,
ParserSyntaxError
from
bison
import
BisonParser
,
ParserSyntaxError
# Check for n-ary operator in child nodes
def
combine
(
op
,
n
):
return
n
.
nodes
if
n
.
title
()
==
op
else
[
n
]
class
Parser
(
BisonParser
):
class
Parser
(
BisonParser
):
"""
"""
Implements the calculator parser. Grammar rules are defined in the method
Implements the calculator parser. Grammar rules are defined in the method
...
@@ -34,7 +39,7 @@ class Parser(BisonParser):
...
@@ -34,7 +39,7 @@ class Parser(BisonParser):
# of tokens of the lex script.
# of tokens of the lex script.
tokens
=
[
'NUMBER'
,
'IDENTIFIER'
,
tokens
=
[
'NUMBER'
,
'IDENTIFIER'
,
'PLUS'
,
'MINUS'
,
'TIMES'
,
'DIVIDE'
,
'POW'
,
'PLUS'
,
'MINUS'
,
'TIMES'
,
'DIVIDE'
,
'POW'
,
'LPAREN'
,
'RPAREN'
,
'COMMA'
,
'LPAREN'
,
'RPAREN'
,
'COMMA'
,
'CONCAT_POW'
,
'NEWLINE'
,
'QUIT'
,
'RAISE'
]
'NEWLINE'
,
'QUIT'
,
'RAISE'
]
# ------------------------------
# ------------------------------
...
@@ -87,7 +92,7 @@ class Parser(BisonParser):
...
@@ -87,7 +92,7 @@ class Parser(BisonParser):
# as a shell. In that case, it is useful that the shell prints the
# as a shell. In that case, it is useful that the shell prints the
# output of the evaluation.
# output of the evaluation.
if
self
.
interactive
and
values
[
1
]:
if
self
.
interactive
and
values
[
1
]:
print
'result:'
,
values
[
1
]
print
values
[
1
]
return
values
[
1
]
return
values
[
1
]
...
@@ -107,85 +112,101 @@ class Parser(BisonParser):
...
@@ -107,85 +112,101 @@ class Parser(BisonParser):
"""
"""
exp : NUMBER
exp : NUMBER
| IDENTIFIER
| IDENTIFIER
| exp PLUS exp
| exp MINUS exp
| exp TIMES exp
| exp DIVIDE exp
| MINUS exp %prec NEG
| exp POW exp
| LPAREN exp RPAREN
| LPAREN exp RPAREN
| symbolic
| unary
| binary
| concat
"""
"""
# rule: NUMBER
if
option
==
0
:
# rule: NUMBER
if
option
==
0
:
# TODO: A bit hacky, this achieves long integers and floats.
# TODO: A bit hacky, this achieves long integers and floats.
value
=
float
(
values
[
0
])
if
'.'
in
values
[
0
]
else
int
(
values
[
0
])
value
=
float
(
values
[
0
])
if
'.'
in
values
[
0
]
else
int
(
values
[
0
])
return
Leaf
(
value
)
return
Leaf
(
value
)
# rule: IDENTIFIER
if
option
==
1
:
# rule: IDENTIFIER
if
option
==
1
:
return
Leaf
(
values
[
0
])
return
Leaf
(
values
[
0
])
# rule: LPAREN exp RPAREN
if
option
==
2
:
# rule: LPAREN exp RPAREN
if
option
==
8
:
return
values
[
1
]
return
values
[
1
]
# rule: symbolic
if
option
in
[
3
,
4
,
5
]:
# rule: unary | binary | concat
if
option
==
9
:
return
values
[
0
]
return
values
[
0
]
# Check for n-ary operator in child nodes
raise
ParserSyntaxError
(
'Unsupported option %d in target "%s".'
combine
=
lambda
op
,
n
:
n
.
nodes
if
n
.
title
()
==
op
else
[
n
]
%
(
option
,
target
))
# rule: exp PLUS exp
def
on_unary
(
self
,
target
,
option
,
names
,
values
):
if
option
==
2
:
"""
return
Node
(
'+'
,
*
(
combine
(
'+'
,
values
[
0
])
+
combine
(
'+'
,
values
[
2
])))
unary : MINUS exp %prec NEG
"""
# rule: exp MINUS expo
if
option
==
0
:
# rule: NEG exp
if
option
==
3
:
return
Node
(
'-'
,
values
[
1
])
return
Node
(
'-'
,
*
(
combine
(
'-'
,
values
[
0
])
+
combine
(
'-'
,
values
[
2
])))
# rule: exp TIMES expo
raise
ParserSyntaxError
(
'Unsupported option %d in target "%s".'
if
option
==
4
:
%
(
option
,
target
))
return
Node
(
'*'
,
*
(
combine
(
'*'
,
values
[
0
])
+
combine
(
'*'
,
values
[
2
])))
# rule: exp DIVIDE expo
def
on_binary
(
self
,
target
,
option
,
names
,
values
):
if
option
==
5
:
"""
return
Node
(
'/'
,
values
[
0
],
values
[
2
])
binary : exp PLUS exp
| exp MINUS exp
| exp TIMES exp
| exp DIVIDE exp
| exp POW exp
"""
# rule: NEG expo
if
option
==
0
:
# rule: exp PLUS exp
if
option
==
6
:
return
Node
(
'+'
,
*
(
combine
(
'+'
,
values
[
0
])
return
Node
(
'-'
,
values
[
1
])
+
combine
(
'+'
,
values
[
2
])))
if
option
==
1
:
# rule: exp MINUS exp
return
Node
(
'-'
,
*
(
combine
(
'-'
,
values
[
0
])
+
combine
(
'-'
,
values
[
2
])))
if
option
==
2
:
# rule: exp TIMES exp
return
Node
(
'*'
,
*
(
combine
(
'*'
,
values
[
0
])
+
combine
(
'*'
,
values
[
2
])))
# rule: exp POW expo
if
option
==
3
:
# rule: exp DIVIDE exp
if
option
==
7
:
return
Node
(
'/'
,
values
[
0
],
values
[
2
])
if
option
==
4
:
# rule: exp POW exp
return
Node
(
'^'
,
values
[
0
],
values
[
2
])
return
Node
(
'^'
,
values
[
0
],
values
[
2
])
raise
ParserSyntaxError
(
'Unsupported option %d in target "%s".'
raise
ParserSyntaxError
(
'Unsupported option %d in target "%s".'
%
(
option
,
target
))
%
(
option
,
target
))
def
on_symbolic
(
self
,
target
,
option
,
names
,
values
):
def
on_concat
(
self
,
option
,
target
,
names
,
values
):
"""
"""
symbolic : NUMBER IDENTIFIER
concat : exp IDENTIFIER
| IDENTIFIER IDENTIFIER
| exp NUMBER
| symbolic IDENTIFIER
| exp LPAREN exp RPAREN
| IDENTIFIER NUMBER
| exp CONCAT_POW
| CONCAT_POW
"""
"""
# rule: NUMBER IDENTIFIER
# rule: IDENTIFIER IDENTIFIER
# rule: symbolic IDENTIFIER
if
option
in
[
0
,
1
,
2
]:
# 4x -> 4*x
# a b -> a * b
# a b c -> (a * b) * c
node
=
Node
(
'*'
,
Leaf
(
values
[
0
]),
Leaf
(
values
[
1
]))
return
node
# rule: IDENTIFIER NUMBER
if
option
in
[
0
,
1
]:
# rule: exp IDENTIFIER | exp NUMBER
# NOTE: xy -> x*y
# NOTE: (x)4 -> x*4
val
=
int
(
values
[
1
])
if
option
==
1
else
values
[
1
]
return
Node
(
'*'
,
*
(
combine
(
'*'
,
values
[
0
])
+
[
Leaf
(
val
)]))
if
option
==
2
:
# rule: exp LPAREN exp RPAREN
# NOTE: x(y) -> x*(y)
return
Node
(
'*'
,
*
(
combine
(
'*'
,
values
[
0
])
+
combine
(
'*'
,
values
[
2
])))
if
option
==
3
:
if
option
==
3
:
# x4 -> x^4
# NOTE: x4 -> x^4
return
Node
(
'^'
,
Leaf
(
values
[
0
]),
Leaf
(
values
[
1
]))
identifier
,
exponent
=
list
(
values
[
1
])
node
=
Node
(
'^'
,
Leaf
(
identifier
),
Leaf
(
int
(
exponent
)))
return
Node
(
'*'
,
values
[
0
],
node
)
if
option
==
4
:
# NOTE: x4 -> x^4
identifier
,
exponent
=
list
(
values
[
0
])
return
Node
(
'^'
,
Leaf
(
identifier
),
Leaf
(
int
(
exponent
)))
raise
ParserSyntaxError
(
'Unsupported option %d in target "%s".'
raise
ParserSyntaxError
(
'Unsupported option %d in target "%s".'
%
(
option
,
target
))
%
(
option
,
target
))
...
@@ -196,19 +217,22 @@ class Parser(BisonParser):
...
@@ -196,19 +217,22 @@ class Parser(BisonParser):
lexscript
=
r"""
lexscript
=
r"""
%{
%{
//int yylineno = 0;
//int yylineno = 0;
#include <stdio.h>
#include <string.h>
#include "Python.h"
#include "Python.h"
#define YYSTYPE void *
#define YYSTYPE void *
#include "tokens.h"
#include "tokens.h"
extern void *py_parser;
extern void *py_parser;
extern void (*py_input)(PyObject *parser, char *buf, int *result, int max_size);
extern void (*py_input)(PyObject *parser, char *buf, int *result,
#define returntoken(tok) yylval = PyString_FromString(strdup(yytext)); return (tok);
int max_size);
#define YY_INPUT(buf,result,max_size) { (*py_input)(py_parser, buf, &result, max_size); }
#define returntoken(tok) \
yylval = PyString_FromString(strdup(yytext)); return (tok);
#define YY_INPUT(buf,result,max_size) { \
(*py_input)(py_parser, buf, &result, max_size); \
}
%}
%}
%%
%%
[a-zA-Z][0-9]+ { returntoken(CONCAT_POW); }
[0-9]+ { returntoken(NUMBER); }
[0-9]+ { returntoken(NUMBER); }
[a-zA-Z] { returntoken(IDENTIFIER); }
[a-zA-Z] { returntoken(IDENTIFIER); }
"(" { returntoken(LPAREN); }
"(" { returntoken(LPAREN); }
...
@@ -224,7 +248,8 @@ class Parser(BisonParser):
...
@@ -224,7 +248,8 @@ class Parser(BisonParser):
[ \t\v\f] {}
[ \t\v\f] {}
[\n] {yylineno++; returntoken(NEWLINE); }
[\n] {yylineno++; returntoken(NEWLINE); }
. { printf("unknown char %c ignored, yytext=0x%lx\n", yytext[0], yytext); /* ignore bad chars */}
. { printf("unknown char %c ignored, yytext=%p\n",
yytext[0], yytext); /* ignore bad chars */}
%%
%%
...
...
tests/parser.py
View file @
aa09d9d7
import
sys
import
sys
from
external.graph_drawing.graph
import
generate_graph
from
external.graph_drawing.line
import
generate_line
class
ParserWrapper
(
object
):
class
ParserWrapper
(
object
):
def
__init__
(
self
,
base_class
,
**
kwargs
):
def
__init__
(
self
,
base_class
,
**
kwargs
):
self
.
input_buffer
=
[]
self
.
input_buffer
=
[]
self
.
last_buffer
=
''
self
.
input_position
=
0
self
.
input_position
=
0
self
.
closed
=
False
self
.
verbose
=
kwargs
.
get
(
'verbose'
,
False
)
self
.
verbose
=
kwargs
.
get
(
'verbose'
,
False
)
# Overwrite parser read() method
self
.
parser
=
base_class
(
file
=
self
,
read
=
self
.
read
,
**
kwargs
)
def
read
(
nbytes
):
buf
=
''
def
readline
(
self
,
nbytes
=
False
):
return
self
.
read
(
nbytes
)
def
read
(
self
,
nbytes
=
False
):
if
len
(
self
.
last_buffer
)
>=
nbytes
:
buf
=
self
.
last_buffer
[:
nbytes
]
self
.
last_buffer
=
self
.
last_buffer
[
nbytes
:]
return
buf
buf
=
self
.
last_buffer
try
:
try
:
buf
=
self
.
input_buffer
[
self
.
input_position
]
buf
+
=
self
.
input_buffer
[
self
.
input_position
]
if
self
.
verbose
:
if
self
.
verbose
:
print
'read:'
,
buf
print
'read:'
,
buf
except
IndexError
:
return
''
self
.
input_position
+=
1
self
.
input_position
+=
1
except
IndexError
:
self
.
closed
=
True
return
''
self
.
last_buffer
=
buf
[
nbytes
:]
return
buf
return
buf
self
.
parser
=
base_class
(
**
kwargs
)
def
close
(
self
):
self
.
parser
.
read
=
read
self
.
closed
=
True
self
.
input_position
=
len
(
self
.
input_buffer
)
def
run
(
self
,
input_buffer
,
*
args
,
**
kwargs
):
def
run
(
self
,
input_buffer
,
*
args
,
**
kwargs
):
map
(
self
.
append
,
input_buffer
)
map
(
self
.
append
,
input_buffer
)
return
self
.
parser
.
run
(
*
args
,
**
kwargs
)
return
self
.
parser
.
run
(
*
args
,
**
kwargs
)
def
append
(
self
,
input
):
def
append
(
self
,
input
):
self
.
closed
=
False
self
.
input_buffer
.
append
(
input
+
'
\
n
'
)
self
.
input_buffer
.
append
(
input
+
'
\
n
'
)
...
@@ -70,5 +88,19 @@ def run_expressions(base_class, expressions, keepfiles=1, fail=True,
...
@@ -70,5 +88,19 @@ def run_expressions(base_class, expressions, keepfiles=1, fail=True,
print
>>
sys
.
stderr
,
'error: %s = %s, but expected: %s'
\
print
>>
sys
.
stderr
,
'error: %s = %s, but expected: %s'
\
%
(
exp
,
str
(
res
),
str
(
out
))
%
(
exp
,
str
(
res
),
str
(
out
))
if
not
silent
and
hasattr
(
res
,
'nodes'
):
print
>>
sys
.
stderr
,
'result graph:'
print
>>
sys
.
stderr
,
generate_graph
(
res
)
print
>>
sys
.
stderr
,
'expected graph:'
print
>>
sys
.
stderr
,
generate_graph
(
out
)
if
fail
:
if
fail
:
raise
raise
def
graph
(
parser
,
*
exp
,
**
kwargs
):
return
generate_graph
(
ParserWrapper
(
parser
,
**
kwargs
).
run
(
exp
))
def
line
(
parser
,
*
exp
,
**
kwargs
):
return
generate_line
(
ParserWrapper
(
parser
,
**
kwargs
).
run
(
exp
))
tests/test_calc.py
View file @
aa09d9d7
import
unittest
import
unittest
from
src.calc
import
Parser
from
src.parser
import
Parser
from
src.node
import
ExpressionNode
as
N
,
ExpressionLeaf
as
L
from
tests.parser
import
ParserWrapper
,
run_expressions
from
tests.parser
import
ParserWrapper
,
run_expressions
class
TestCalc
(
unittest
.
TestCase
):
class
TestCalc
(
unittest
.
TestCase
):
def
setUp
(
self
):
pass
def
tearDown
(
self
):
pass
def
test_constructor
(
self
):
def
test_constructor
(
self
):
assert
ParserWrapper
(
Parser
,
keepfiles
=
1
).
run
([
'1+4'
])
==
5.0
assert
ParserWrapper
(
Parser
).
run
([
'1+4'
])
\
==
N
(
'+'
,
L
(
1
),
L
(
4
))
def
test_basic_on_exp
(
self
):
def
test_basic_on_exp
(
self
):
expressions
=
[(
'4'
,
4.0
),
expressions
=
[(
'4'
,
L
(
4
)
),
(
'3+4'
,
7.0
),
(
'3+4'
,
N
(
'+'
,
L
(
3
),
L
(
4
))
),
(
'3-4'
,
-
1.0
),
(
'3-4'
,
N
(
'-'
,
L
(
3
),
L
(
4
))
),
(
'3/4'
,
.
75
),
(
'3/4'
,
N
(
'/'
,
L
(
3
),
L
(
4
))
),
(
'-4'
,
-
4.0
),
(
'-4'
,
N
(
'-'
,
L
(
4
))
),
(
'3^4'
,
81.0
),
(
'3^4'
,
N
(
'^'
,
L
(
3
),
L
(
4
))
),
(
'(
4)'
,
4.0
)]
(
'(
2)'
,
L
(
2
)
)]
run_expressions
(
Parser
,
expressions
)
run_expressions
(
Parser
,
expressions
)
def
test_infinity
(
self
):
def
test_infinity
(
self
):
expressions
=
[(
'2^3000'
,
2
**
3000
),
expressions
=
[(
'2^3000'
,
N
(
'^'
,
L
(
2
),
L
(
3000
))
),
(
'2^-3000'
,
0.0
)]
(
'2^-3000'
,
N
(
'^'
,
L
(
2
),
N
(
'-'
,
L
(
3000
)))
)]
# ('2^99999999999', None),
# ('2^99999999999', None),
# ('2^-99999999999', 0.0)]
# ('2^-99999999999', 0.0)]
run_expressions
(
Parser
,
expressions
)
run_expressions
(
Parser
,
expressions
)
def
test_concat_easy
(
self
):
expressions
=
[
(
'xy'
,
N
(
'*'
,
L
(
'x'
),
L
(
'y'
))),
(
'2x'
,
N
(
'*'
,
L
(
2
),
L
(
'x'
))),
(
'x4'
,
N
(
'^'
,
L
(
'x'
),
L
(
4
))),
(
'xy4'
,
N
(
'*'
,
L
(
'x'
),
N
(
'^'
,
L
(
'y'
),
L
(
4
)))),
(
'(x)4'
,
N
(
'*'
,
L
(
'x'
),
L
(
4
))),
(
'(3+4)2'
,
N
(
'*'
,
N
(
'+'
,
L
(
3
),
L
(
4
)),
L
(
2
))),
]
run_expressions
(
Parser
,
expressions
)
def
test_concat_intermediate
(
self
):
expressions
=
[
(
'(3+4)(5+7)'
,
N
(
'*'
,
N
(
'+'
,
L
(
3
),
L
(
4
)),
N
(
'+'
,
L
(
5
),
L
(
7
)))),
(
'(a+b)(c+d)'
,
N
(
'*'
,
N
(
'+'
,
L
(
'a'
),
L
(
'b'
)),
N
(
'+'
,
L
(
'c'
),
L
(
'd'
)))),
(
'a+b(c+d)'
,
N
(
'+'
,
L
(
'a'
),
N
(
'*'
,
L
(
'b'
),
N
(
'+'
,
L
(
'c'
),
L
(
'd'
))))),
(
'ab(c)d'
,
N
(
'*'
,
L
(
'a'
),
L
(
'b'
),
L
(
'c'
),
L
(
'd'
))),
#('ab(c)d', N('*', L('a'), N('*', L('b'),
# N('*', L('c'), L('d'))))),
]
run_expressions
(
Parser
,
expressions
)
tests/test_parser.py
View file @
aa09d9d7
# vim: set fileencoding=utf-8 :
# vim: set fileencoding=utf-8 :
import
unittest
import
unittest
from
external.graph_drawing.graph
import
generate_graph
from
external.graph_drawing.line
import
generate_line
from
src.parser
import
Parser
from
src.parser
import
Parser
from
src.node
import
ExpressionNode
as
Node
,
ExpressionLeaf
as
Leaf
from
src.node
import
ExpressionNode
as
Node
,
ExpressionLeaf
as
Leaf
from
tests.parser
import
ParserWrapper
,
run_expressions
from
tests.parser
import
ParserWrapper
,
run_expressions
,
line
,
graph
def
graph
(
*
exp
,
**
kwargs
):
return
generate_graph
(
ParserWrapper
(
Parser
,
**
kwargs
).
run
(
exp
))
def
line
(
*
exp
,
**
kwargs
):
return
generate_line
(
ParserWrapper
(
Parser
,
**
kwargs
).
run
(
exp
))
class
TestParser
(
unittest
.
TestCase
):
class
TestParser
(
unittest
.
TestCase
):
...
@@ -26,11 +15,11 @@ class TestParser(unittest.TestCase):
...
@@ -26,11 +15,11 @@ class TestParser(unittest.TestCase):
run_expressions
(
Parser
,
[(
'a'
,
Leaf
(
'a'
))])
run_expressions
(
Parser
,
[(
'a'
,
Leaf
(
'a'
))])
def
test_graph
(
self
):
def
test_graph
(
self
):
assert
graph
(
'4a'
)
==
(
"""
assert
graph
(
Parser
,
'4a'
)
==
(
"""
*
*
╭┴╮
╭┴╮
4 a
4 a
"""
).
replace
(
'
\
n
'
,
'
\
n
'
)[
1
:
-
1
]
"""
).
replace
(
'
\
n
'
,
'
\
n
'
)[
1
:
-
1
]
def
test_line
(
self
):
def
test_line
(
self
):
self
.
assertEqual
(
line
(
'4a'
),
'4 * a'
)
self
.
assertEqual
(
line
(
Parser
,
'4a'
),
'4 * a'
)
tests/test_variables.py
View file @
aa09d9d7
...
@@ -7,12 +7,6 @@ from sympy import Symbol, symbols
...
@@ -7,12 +7,6 @@ from sympy import Symbol, symbols
class
TestVariables
(
unittest
.
TestCase
):
class
TestVariables
(
unittest
.
TestCase
):
def
setUp
(
self
):
pass
def
tearDown
(
self
):
pass
def
test_addition
(
self
):
def
test_addition
(
self
):
expressions
=
[(
'5 + 5'
,
5
+
5
)]
expressions
=
[(
'5 + 5'
,
5
+
5
)]
run_expressions
(
Parser
,
expressions
)
run_expressions
(
Parser
,
expressions
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment