🎉 ✨ lexer for formula.
parent
36aad850ca
commit
5cecb1f1ed
|
@ -0,0 +1 @@
|
||||||
|
__pycache__
|
|
@ -0,0 +1,7 @@
|
||||||
|
import pytest
|
||||||
|
from fol.lexer import Lexer
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def lexer():
|
||||||
|
return Lexer()
|
|
@ -0,0 +1,13 @@
|
||||||
|
F ::= EXISTS
|
||||||
|
EXISTS ::= exists var FORALL | FORALL
|
||||||
|
FORALL ::= forall var IMP | IMP
|
||||||
|
IMP ::= OR imp OR
|
||||||
|
OR ::= AND (or AND)*
|
||||||
|
AND ::= NOT (and NOT)*
|
||||||
|
NOT ::= not? PRED
|
||||||
|
PRED ::= pred opar TERM (comma TERM)* cpar
|
||||||
|
|
||||||
|
TERM ::=
|
||||||
|
| var
|
||||||
|
| const
|
||||||
|
| var opar TERM (comma TERM)* cpar
|
|
@ -0,0 +1,159 @@
|
||||||
|
class Token:
|
||||||
|
def __init__(self, name, value=None):
|
||||||
|
self.name = name
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.value is not None:
|
||||||
|
return f'{self.name}[{self.value}]'
|
||||||
|
return f'{self.name}'
|
||||||
|
|
||||||
|
|
||||||
|
class Lexer:
|
||||||
|
def __init__(self):
|
||||||
|
self.text = ''
|
||||||
|
self.cursor = 0
|
||||||
|
|
||||||
|
def scan(self, text):
|
||||||
|
self.text = text
|
||||||
|
self.cursor = 0
|
||||||
|
|
||||||
|
def next(self):
|
||||||
|
self.scan_spaces()
|
||||||
|
|
||||||
|
scanners = [
|
||||||
|
self.scan_text('AND', '&'),
|
||||||
|
self.scan_text('OR', '|'),
|
||||||
|
self.scan_text('IMP', '->'),
|
||||||
|
self.scan_text('NOT', '~'),
|
||||||
|
self.scan_text('OPAR', '('),
|
||||||
|
self.scan_text('CPAR', ')'),
|
||||||
|
self.scan_text('COMMA', ','),
|
||||||
|
self.scan_keyword('exists'),
|
||||||
|
self.scan_keyword('forall'),
|
||||||
|
self.scan_var,
|
||||||
|
self.scan_const,
|
||||||
|
self.scan_pred,
|
||||||
|
]
|
||||||
|
|
||||||
|
for scanner in scanners:
|
||||||
|
tok = scanner()
|
||||||
|
if tok is not None:
|
||||||
|
return tok
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def scan_spaces(self):
|
||||||
|
while self.cursor < len(self.text) \
|
||||||
|
and self.text[self.cursor].isspace():
|
||||||
|
self.cursor += 1
|
||||||
|
|
||||||
|
def scan_var(self):
|
||||||
|
cursor = self.cursor
|
||||||
|
value = ''
|
||||||
|
|
||||||
|
while cursor < len(self.text) and self.text[cursor].islower():
|
||||||
|
value += self.text[cursor]
|
||||||
|
cursor += 1
|
||||||
|
|
||||||
|
if value != '':
|
||||||
|
self.cursor = cursor
|
||||||
|
return Token('VAR', value)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def scan_const(self):
|
||||||
|
cursor = self.cursor
|
||||||
|
value = ''
|
||||||
|
|
||||||
|
while cursor < len(self.text) and self.text[cursor].isupper():
|
||||||
|
value += self.text[cursor]
|
||||||
|
cursor += 1
|
||||||
|
|
||||||
|
if value != '' and self.is_sep(cursor):
|
||||||
|
self.cursor = cursor
|
||||||
|
return Token('CONST', value)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def scan_pred(self):
|
||||||
|
cursor = self.cursor
|
||||||
|
value = ''
|
||||||
|
|
||||||
|
while cursor < len(self.text) and not self.is_sep(cursor):
|
||||||
|
value += self.text[cursor]
|
||||||
|
cursor += 1
|
||||||
|
|
||||||
|
format = len(value) > 1 and value[0].isupper() \
|
||||||
|
and value[1:].islower()
|
||||||
|
|
||||||
|
if len(value) > 0 and format:
|
||||||
|
self.cursor = cursor
|
||||||
|
return Token('PRED', value)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def scan_keyword(self, name):
|
||||||
|
def scanner():
|
||||||
|
cursor = self.cursor
|
||||||
|
value = ''
|
||||||
|
|
||||||
|
if cursor >= len(self.text) or self.text[cursor] != '\\':
|
||||||
|
return None
|
||||||
|
|
||||||
|
cursor += 1
|
||||||
|
i = 0
|
||||||
|
|
||||||
|
while cursor < len(self.text) and i < len(name) \
|
||||||
|
and self.text[cursor] == name[i]:
|
||||||
|
cursor += 1
|
||||||
|
value += name[i]
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
if not self.is_sep(cursor):
|
||||||
|
return None
|
||||||
|
|
||||||
|
if value == name:
|
||||||
|
self.cursor = cursor
|
||||||
|
return Token(f'{name.upper()}')
|
||||||
|
|
||||||
|
return None
|
||||||
|
return scanner
|
||||||
|
|
||||||
|
def scan_text(self, name, text, tok_value=None):
|
||||||
|
def scanner():
|
||||||
|
cursor = self.cursor
|
||||||
|
value = ''
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
|
||||||
|
while cursor < len(self.text) and i < len(text) \
|
||||||
|
and self.text[cursor] == text[i]:
|
||||||
|
cursor += 1
|
||||||
|
value += text[i]
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
if value == text:
|
||||||
|
self.cursor = cursor
|
||||||
|
return Token(name, tok_value)
|
||||||
|
|
||||||
|
return None
|
||||||
|
return scanner
|
||||||
|
|
||||||
|
def is_sep(self, idx):
|
||||||
|
if idx < 0 or idx >= len(self.text):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return self.text[idx] in [
|
||||||
|
' ',
|
||||||
|
'\n',
|
||||||
|
'\t',
|
||||||
|
'-',
|
||||||
|
'\\',
|
||||||
|
'&',
|
||||||
|
'|',
|
||||||
|
'~',
|
||||||
|
'(',
|
||||||
|
')',
|
||||||
|
','
|
||||||
|
]
|
|
@ -0,0 +1,26 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(['text', 'oracles'], [
|
||||||
|
('hello world', ['VAR[hello]', 'VAR[world]']),
|
||||||
|
('HELLO WORLD', ['CONST[HELLO]', 'CONST[WORLD]']),
|
||||||
|
('\\exists \\forall', ['EXISTS', 'FORALL']),
|
||||||
|
('\\existsX', [None]),
|
||||||
|
('\\exists X', ['EXISTS', 'CONST[X]']),
|
||||||
|
(' A->B ', ['CONST[A]', 'IMP', 'CONST[B]']),
|
||||||
|
(' x&y ', ['VAR[x]', 'AND', 'VAR[y]']),
|
||||||
|
(' a|b ', ['VAR[a]', 'OR', 'VAR[b]']),
|
||||||
|
(' ~s ', ['NOT', 'VAR[s]']),
|
||||||
|
(' (k) ', ['OPAR', 'VAR[k]', 'CPAR']),
|
||||||
|
(' a,b ', ['VAR[a]', 'COMMA', 'VAR[b]']),
|
||||||
|
(' Pr(x) ', ['PRED[Pr]', 'OPAR', 'VAR[x]', 'CPAR']),
|
||||||
|
])
|
||||||
|
def test_token(lexer, text, oracles):
|
||||||
|
lexer.scan(text)
|
||||||
|
|
||||||
|
for oracle in oracles:
|
||||||
|
tok = lexer.next()
|
||||||
|
if tok is None:
|
||||||
|
assert oracle is None
|
||||||
|
else:
|
||||||
|
assert str(tok) == oracle
|
|
@ -0,0 +1,8 @@
|
||||||
|
flake8==7.0.0
|
||||||
|
iniconfig==2.0.0
|
||||||
|
mccabe==0.7.0
|
||||||
|
packaging==24.0
|
||||||
|
pluggy==1.5.0
|
||||||
|
pycodestyle==2.11.1
|
||||||
|
pyflakes==3.2.0
|
||||||
|
pytest==8.1.1
|
|
@ -0,0 +1,2 @@
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print('Sine Patre')
|
Loading…
Reference in New Issue