Skip to content

Commit 42f6e21

Browse files
committed
implemented tree builder that builds tree given token stream
1 parent dd75bf2 commit 42f6e21

12 files changed

Lines changed: 205 additions & 5 deletions

evaluation_function/parsing/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
from .token import Token, TokenType
33
from .character_stream import CharacterStream
44
from .token_matcher import TokenMatcher, SingleCharTokenMatcher, AtomTokenMatcher, EOFTokenMatcher
5+
from .tree_builder import TreeBuilder, BuildError
6+
from .token_stream import TokenStream
7+
from .expression_builder import ExpressionBuilder
8+
from .primary_builder import PrimaryBuilder
9+
from .binary_operator_builder import BinaryOperatorBuilder
510

611
__all__ = [
712
"Tokenizer",
@@ -12,4 +17,10 @@
1217
"SingleCharTokenMatcher",
1318
"AtomTokenMatcher",
1419
"EOFTokenMatcher",
20+
"TreeBuilder",
21+
"BuildError",
22+
"TokenStream",
23+
"ExpressionBuilder",
24+
"PrimaryBuilder",
25+
"BinaryOperatorBuilder",
1526
]
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
from typing import Dict, Callable
2+
from ..domain.formula import Formula, Conjunction, Disjunction, Implication, Biconditional
3+
from .token_stream import TokenStream
4+
from .token import TokenType
5+
from .expression_builder import ExpressionBuilder
6+
7+
8+
class BinaryOperatorBuilder(ExpressionBuilder):
9+
_PRECEDENCE: Dict[TokenType, int] = {
10+
TokenType.BICONDITIONAL: 1,
11+
TokenType.IMPLICATION: 2,
12+
TokenType.DISJUNCTION: 3,
13+
TokenType.CONJUNCTION: 4,
14+
}
15+
16+
_OPERATOR_CONSTRUCTORS: Dict[TokenType, Callable[[Formula, Formula], Formula]] = {
17+
TokenType.CONJUNCTION: Conjunction,
18+
TokenType.DISJUNCTION: Disjunction,
19+
TokenType.IMPLICATION: Implication,
20+
TokenType.BICONDITIONAL: Biconditional,
21+
}
22+
23+
_RIGHT_ASSOCIATIVE: set = {TokenType.IMPLICATION}
24+
25+
def __init__(self, primary_builder: ExpressionBuilder):
26+
self._primary_builder = primary_builder
27+
28+
def build(self, stream: TokenStream) -> Formula:
29+
return self._build_binary_operator(stream, 1)
30+
31+
def _build_binary_operator(self, stream: TokenStream, min_precedence: int) -> Formula:
32+
left = self._primary_builder.build(stream)
33+
34+
while not stream.is_eof():
35+
token = stream.current_token
36+
if token is None:
37+
break
38+
39+
if token.type == TokenType.RIGHT_PAREN:
40+
break
41+
42+
if token.type not in self._PRECEDENCE:
43+
break
44+
45+
op_precedence = self._PRECEDENCE[token.type]
46+
if op_precedence < min_precedence:
47+
break
48+
49+
stream.advance()
50+
51+
if token.type in self._RIGHT_ASSOCIATIVE:
52+
right = self._build_binary_operator(stream, op_precedence)
53+
else:
54+
right = self._build_binary_operator(stream, op_precedence + 1)
55+
56+
constructor = self._OPERATOR_CONSTRUCTORS[token.type]
57+
left = constructor(left, right)
58+
59+
return left
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from abc import ABC, abstractmethod
2+
from ..domain.formula import Formula
3+
from .token_stream import TokenStream
4+
5+
6+
class ExpressionBuilder(ABC):
7+
@abstractmethod
8+
def build(self, stream: TokenStream) -> Formula:
9+
pass
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from ..domain.formula import Formula, Atom, Truth, Falsity, Negation
2+
from .token_stream import TokenStream
3+
from .token import TokenType
4+
from .expression_builder import ExpressionBuilder
5+
6+
7+
class PrimaryBuilder(ExpressionBuilder):
8+
def __init__(self, expression_builder: ExpressionBuilder):
9+
self._expression_builder = expression_builder
10+
11+
def build(self, stream: TokenStream) -> Formula:
12+
token = stream.current_token
13+
if token is None:
14+
raise ValueError("Unexpected end of input")
15+
16+
if token.type == TokenType.TRUTH:
17+
stream.advance()
18+
return Truth()
19+
20+
if token.type == TokenType.FALSITY:
21+
stream.advance()
22+
return Falsity()
23+
24+
if token.type == TokenType.NEGATION:
25+
stream.advance()
26+
operand = self.build(stream)
27+
return Negation(operand)
28+
29+
if token.type == TokenType.LEFT_PAREN:
30+
stream.advance()
31+
formula = self._expression_builder.build(stream)
32+
if stream.current_token is None or stream.current_token.type != TokenType.RIGHT_PAREN:
33+
raise ValueError(f"Expected ')' at position {stream.position}")
34+
stream.advance()
35+
return formula
36+
37+
if token.type == TokenType.ATOM:
38+
atom_name = token.value
39+
stream.advance()
40+
return Atom(atom_name)
41+
42+
raise ValueError(f"Unexpected token {token.type.name} at position {token.position}")
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from typing import List, Optional
2+
from .token import Token, TokenType
3+
4+
5+
class TokenStream:
6+
def __init__(self, tokens: List[Token]):
7+
self._tokens = tokens
8+
self._position = 0
9+
10+
@property
11+
def current_token(self) -> Optional[Token]:
12+
if self._position >= len(self._tokens):
13+
return None
14+
return self._tokens[self._position]
15+
16+
@property
17+
def position(self) -> int:
18+
return self._position
19+
20+
def advance(self):
21+
if self._position < len(self._tokens):
22+
self._position += 1
23+
24+
def peek(self, offset: int = 1) -> Optional[Token]:
25+
peek_pos = self._position + offset
26+
if peek_pos >= len(self._tokens):
27+
return None
28+
return self._tokens[peek_pos]
29+
30+
def is_eof(self) -> bool:
31+
return self._position >= len(self._tokens) or (
32+
self.current_token is not None and self.current_token.type == TokenType.EOF
33+
)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from typing import List, Optional
2+
from ..domain.formula import Formula
3+
from .token import Token, TokenType
4+
from .token_stream import TokenStream
5+
from .expression_builder import ExpressionBuilder
6+
from .primary_builder import PrimaryBuilder
7+
from .binary_operator_builder import BinaryOperatorBuilder
8+
9+
10+
class BuildError(Exception):
11+
def __init__(self, message: str, position: int):
12+
self.message = message
13+
self.position = position
14+
super().__init__(f"{message} at position {position}")
15+
16+
17+
class TreeBuilder:
18+
def __init__(self, tokens: List[Token], expression_builder: Optional[ExpressionBuilder] = None):
19+
self._stream = TokenStream(tokens)
20+
if expression_builder is None:
21+
self._expression_builder = self._create_default_builder()
22+
else:
23+
self._expression_builder = expression_builder
24+
25+
def _create_default_builder(self) -> ExpressionBuilder:
26+
binary_builder = BinaryOperatorBuilder(None)
27+
primary_builder = PrimaryBuilder(binary_builder)
28+
binary_builder._primary_builder = primary_builder
29+
return binary_builder
30+
31+
def build(self) -> Formula:
32+
if self._stream.is_eof():
33+
raise BuildError("Empty input", 0)
34+
35+
formula = self._expression_builder.build(self._stream)
36+
37+
if not self._stream.is_eof():
38+
token = self._stream.current_token
39+
if token is not None and token.type != TokenType.EOF:
40+
raise BuildError(
41+
f"Unexpected token {token.type.name} after expression",
42+
token.position
43+
)
44+
45+
return formula

examples/basic_formula_parsing.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ def main():
3030
("p ∨ q", Disjunction(p, q)),
3131
("p → q", Implication(p, q)),
3232
("p ↔ q", Biconditional(p, q)),
33+
("p ∧ q ∨ q", Disjunction(Conjunction(p, q), q)),
3334
]
3435

3536
passed = 0

examples/basic_usage.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from evaluation_function.propositional_logic import (
1+
from evaluation_function.domain import (
22
Atom,
33
Truth,
44
Falsity,

examples/complex_formula_parsing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from evaluation_function.evaluation import parse_response
22

3-
from evaluation_function.propositional_logic import (
3+
from evaluation_function.domain import (
44
Atom,
55
Truth,
66
Falsity,

examples/complex_formulas.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from evaluation_function.propositional_logic import (
1+
from evaluation_function.domain import (
22
Atom,
33
Negation,
44
Conjunction,

0 commit comments

Comments
 (0)