Starting to stub out a syntax analyzer
This commit is contained in:
parent
50e7213d7b
commit
d60e690445
2 changed files with 149 additions and 4 deletions
|
@ -80,7 +80,6 @@ class SymbolToken(NamedTuple, TokenBase):
|
||||||
raw: str
|
raw: str
|
||||||
pos: Position
|
pos: Position
|
||||||
|
|
||||||
|
|
||||||
class KeywordToken(NamedTuple, TokenBase):
|
class KeywordToken(NamedTuple, TokenBase):
|
||||||
"""A read keyword."""
|
"""A read keyword."""
|
||||||
data: str
|
data: str
|
||||||
|
@ -335,7 +334,7 @@ class Parser(SexpParser):
|
||||||
buff = str(rtb)
|
buff = str(rtb)
|
||||||
return CommentToken(buff, buff, pos)
|
return CommentToken(buff, buff, pos)
|
||||||
|
|
||||||
## Parsing
|
## Parsing interface
|
||||||
def parses(buff: str,
|
def parses(buff: str,
|
||||||
parser: SexpParser = Parser,
|
parser: SexpParser = Parser,
|
||||||
source_name=None):
|
source_name=None):
|
||||||
|
@ -365,7 +364,7 @@ def parse(file: IO,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
## Loading
|
## Loading interface
|
||||||
def loads(buff: str,
|
def loads(buff: str,
|
||||||
parser: SexpParser = Parser,
|
parser: SexpParser = Parser,
|
||||||
source_name=None):
|
source_name=None):
|
||||||
|
@ -395,7 +394,7 @@ def load(file: IO,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
## Dumping
|
## Dumping interface
|
||||||
def dump(file: IO, obj):
|
def dump(file: IO, obj):
|
||||||
"""Given an object, dump its s-expression coding to the given file-like object."""
|
"""Given an object, dump its s-expression coding to the given file-like object."""
|
||||||
|
|
||||||
|
|
146
src/python/flowmetal/syntax_analyzer.py
Normal file
146
src/python/flowmetal/syntax_analyzer.py
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
"""
|
||||||
|
The parser just parses and tokenizes.
|
||||||
|
|
||||||
|
The [syntax] analyzer interprets a parse sequence into a syntax tree which can be checked, type inferred and compiled.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from io import StringIO, IO
|
||||||
|
from typing import NamedTuple
|
||||||
|
|
||||||
|
import flowmetal.parser as p
|
||||||
|
|
||||||
|
### Types
|
||||||
|
## We are not, in fact, sponsored by Typelevel LLC.
|
||||||
|
class TypeLevelExpr(ABC):
|
||||||
|
"""A base class for type-level expressions."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GenericExpr(TypeLevelExpr, NamedTuple):
|
||||||
|
"""'invocation' (application) of a generic type to Type[Level]Exprs."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TypeExpr(TypeLevelExpr, NamedTuple):
|
||||||
|
"""A bound (or yet to be bound) type level symbol."""
|
||||||
|
|
||||||
|
|
||||||
|
## Now down to reality
|
||||||
|
class ValueLevelExpr(ABC):
|
||||||
|
"""A base class for value-level expressions."""
|
||||||
|
|
||||||
|
|
||||||
|
class AscribeExpr(ValueLevelExpr, NamedTuple):
|
||||||
|
"""Ascribe a type (via type-level expression) to a value-level expression."""
|
||||||
|
|
||||||
|
|
||||||
|
class InvokeExpr(ValueLevelExpr, NamedTuple):
|
||||||
|
"""(a ⊢ (fn A ⊢ B) [...] ⊢ A) ⊢ B"""
|
||||||
|
|
||||||
|
|
||||||
|
class IfExpr(ValueLevelExpr, NamedTuple):
|
||||||
|
"""(if test a ⊢ A b ⊢ B) ⊢ (Variant A B)."""
|
||||||
|
|
||||||
|
|
||||||
|
class LetExpr(ValueLevelExpr, NamedTuple):
|
||||||
|
"""Let a single binding and wrap a body. Yes one. N-ary let is an abstraction."""
|
||||||
|
|
||||||
|
|
||||||
|
class DoExpr(ValueError, NamedTuple):
|
||||||
|
"""do a procedure ahem sequence of things.
|
||||||
|
|
||||||
|
(do a b c ... ω ⊢ Ω) ⊢ Ω
|
||||||
|
"""
|
||||||
|
|
||||||
|
ProcExpr = DoExpr # ain't broke don't fix it
|
||||||
|
|
||||||
|
class MappingExpr(ValueLevelExpr, NamedTuple):
|
||||||
|
"""Mappings require their own constructor expression due to local/symbol references."""
|
||||||
|
|
||||||
|
|
||||||
|
class SetExpr(ValueLevelExpr, NamedTuple):
|
||||||
|
"""Sets require their own constructor expression due to local/symbol references."""
|
||||||
|
|
||||||
|
|
||||||
|
class ListExpr(ValueLevelExpr, NamedTuple):
|
||||||
|
"""While round () lists are generally InvokeExprs, [] lists are constructors like sets and maps."""
|
||||||
|
|
||||||
|
|
||||||
|
## Reader implementation
|
||||||
|
class SexpAnalyzer(ABC):
|
||||||
|
"""A base class for Analyzers."""
|
||||||
|
|
||||||
|
|
||||||
|
class Analyzer(SexpAnalyzer):
|
||||||
|
"""A reference Analyzer implementation.
|
||||||
|
|
||||||
|
Walks a parsed token tree, building up a syntax tree.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def read(cls, token: p.TokenBase):
|
||||||
|
if isinstance(token, p.WhitespaceToken):
|
||||||
|
## Whitespace tokens are discarded when considering syntax
|
||||||
|
pass
|
||||||
|
|
||||||
|
elif isinstance(token, (p.StringToken, p.KeywordToken,
|
||||||
|
p.IntegerToken, p.RationalToken, p.FloatToken)):
|
||||||
|
## These are atoms we don't do much with
|
||||||
|
pass
|
||||||
|
|
||||||
|
elif isinstance(token, p.SetToken):
|
||||||
|
## Set tokens have their own syntax object to allow for lexical sets
|
||||||
|
pass
|
||||||
|
|
||||||
|
elif isinstance(token, p.MappingToken):
|
||||||
|
## As with sets, mappings have their own syntax object
|
||||||
|
pass
|
||||||
|
|
||||||
|
elif isinstance(token, p.ListToken):
|
||||||
|
## This is the fun one because it's where most of the notation is implemented
|
||||||
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def read_symexpr(cls, token: p.SymbolToken):
|
||||||
|
"""Emit a representation of using a binding."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def read_setexpr(cls, token: p.SetToken):
|
||||||
|
"""Emit a SetExpr """
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def
|
||||||
|
|
||||||
|
|
||||||
|
## Analysis interface
|
||||||
|
def reads(buff: str,
|
||||||
|
reader: SexpReader = Reader,
|
||||||
|
parser: p.SexpParser = p.Parser,
|
||||||
|
source_name=None):
|
||||||
|
"""Parse a single s-expression from a string, returning its token tree."""
|
||||||
|
|
||||||
|
return read(StringIO(buff), parser, source_name or f"<string {id(buff):x}>")
|
||||||
|
|
||||||
|
|
||||||
|
def readf(path: str,
|
||||||
|
reader: SexpReader = Reader,
|
||||||
|
parser: p.SexpParser = p.Parser):
|
||||||
|
"""Parse a single s-expression from the file named by a string, returning its token tree."""
|
||||||
|
|
||||||
|
with open(path, "r") as f:
|
||||||
|
return read(f, parser, path)
|
||||||
|
|
||||||
|
|
||||||
|
def read(file: IO,
|
||||||
|
reader: SexpReader = Reader,
|
||||||
|
parser: p.SexpParser = p.Parser,
|
||||||
|
source_name=None):
|
||||||
|
"""Parse a single sexpression from a file-like object, returning its token tree."""
|
||||||
|
|
||||||
|
return parser.parse(
|
||||||
|
PosTrackingBufferedReader(
|
||||||
|
file,
|
||||||
|
source_name=source_name
|
||||||
|
)
|
||||||
|
)
|
Loading…
Reference in a new issue