Skip to content

Commit

Permalink
support infix/prefix keywords
Browse files Browse the repository at this point in the history
  • Loading branch information
hoosierEE committed Jul 29, 2024
1 parent f5abc57 commit 12083da
Show file tree
Hide file tree
Showing 6 changed files with 33 additions and 14 deletions.
4 changes: 3 additions & 1 deletion prototype/Builtin.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# tuple[str]
# except BS (str) and LF (str)
ADVERB = "'","/","\\"
ANDOR = "and","or"
ASSIGN = ":","::"
BS = "\\"
CPAREN = ')','}',']'
ENDEXP = '',';','\n'
KEYWORD = "and","or","in","export","import"
LF = "\n"
OPAREN = '(','{','['
VERB = tuple("~!@#$%^&*-_=+|,.<>?")
Expand Down
12 changes: 7 additions & 5 deletions prototype/Parser.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .Ast import Ast
from .Builtin import ADVERB,ASSIGN,CPAREN,ENDEXP,LF,OPAREN,VERB,VERBM,WHITESPACE
from .Builtin import *
import collections as C
NIL = Ast('NIL')
Op = C.namedtuple('Op','name arity')
Expand All @@ -20,6 +20,7 @@ def balance(op) -> bool:#incremental parentheses check
if op in OPAREN: b.append(CPAREN[OPAREN.index(op)]); return 0
if op in CPAREN and (not b or op!=b.pop()): return 1
def err(i,m=''): return f'Parse: {m}{LF}{"".join(t[:i]).strip()}'

def reduce(until:str):#reduce until (until) matches
while s and str(s[-1].name) not in until: rt(*s.pop())

Expand All @@ -28,8 +29,8 @@ def rt(x,arity):#(r)educe (t)op of stack based on x's arity (and precedence)
if x in ENDEXP:
if len(k)>1 and k[1][0]==x: k = [k[0],*k[1][1]]
elif len(k)>0 and k[0][0]==x: k = [*k[0][1],*k[1:]]
# if x in ANDOR: d.append(Ast(x,*k)) #TODO: (and;or) short-circuiting operators
if noun(x): d.append(Ast('app',Ast(x),*k))
if x in KEYWORD: d.append(Ast(x,*k)) #TODO: (and;or) short-circuiting operators
elif noun(x): d.append(Ast('app',Ast(x),*k))
elif type(x)==Ast and k: d.append(Ast('app',x,*k))
else: d.append(Ast(x,*k))
debug('rt',x,k)
Expand Down Expand Up @@ -63,6 +64,7 @@ def loop(i=0) -> int|None:#return index of error-causing token (if any), else No
if s and s[-1].name=='{' and x.name=='[' and n!='}': s.append(Op(';',2))
else: break
elif c in ADVERB: x = s.pop(); s.append(Op(Ast(c,Ast(x.name)),x.arity)); x.arity==2 and pad(n)
elif c in KEYWORD: s.append(Op(c,1))
elif noun(c) or c[0] in '`"': d.append(Ast(c)); break
elif c in ASSIGN+VERB+VERBM and n in CPAREN+ENDEXP:
d.append(Ast(c)) if s and s[-1].name in OPAREN else rq(Ast('prj',Ast(c))); break
Expand Down Expand Up @@ -97,8 +99,8 @@ def loop(i=0) -> int|None:#return index of error-causing token (if any), else No
elif c in VERBM:
if n in CPAREN+ENDEXP: raise SyntaxError(err(i,"can't project a prefix op"))
else: s.append(Op(c,1))
elif c in VERB:
if c!='.' and s and s[-1]==Op('.',2): reduce('')#precedence: a.b+1 == (a.b)+1
elif c in KEYWORD+VERB:
if c!='.' and s and s[-1]==Op('.',2): reduce('') #precedence: a.b+1 == (a.b)+1
if n in CPAREN+ENDEXP: rq(Ast('prj',Ast(c),d.pop())); continue
else: s.append(Op(c,2))
else:
Expand Down
12 changes: 10 additions & 2 deletions prototype/README.org
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
This folder contains experiments used to guide Element development.
Currently the focus is on an Element interpreter, written in Python to prioritize quick iteration over run-time performance.

** Builtin.py
This file defines the language's keywords, punctuation, and built-in syntax.
** Ast.py
The =Ast= class defines an abstract syntax tree (AST) node and its pretty-printer.
An Ast node is a (required) name and (optional) children.
Expand Down Expand Up @@ -50,12 +52,18 @@ These two projections form a composition, and finally the leading =+= must be a
Again we preserve the partial application information by making the root node a =cmp=.

One thing the parser does not do is validate the shape or type of arguments to operands.
For example ="ab"/3= is an invalid expression in the language but parses as =((fld "ab") 3)=.
For example ="ab"/3= is not defined by the language but still parses as =((fld "ab") 3)=.
The evaluator will handle this task.

** Semantic.py
Semantic analysis passes are in this file.
Type inference occurs here, as do a few assorted odds and ends like converting projections into lambdas to make the internal representation more uniform.
For now, these passes are implemented in the traditional recursive style.
One day I would like to try a more data-oriented approach (e.g. using Apter trees), but as I'm more familiar with the recursive formulation it's going to be my first attempt.
Walk before you run, right?

** Eval.py
This section is TBD.

- Interesting paper: https://arxiv.org/pdf/1109.0781
- Interesting statically typed compiled language: https://aardappel.github.io/lobster/README_FIRST.html

Expand Down
3 changes: 2 additions & 1 deletion prototype/Semantic.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,6 @@ def formalize(a:Ast) -> Ast:
return Ast(a.node, *map(formalize,a.children))

def Sema(a:Ast) -> Ast|Val:
'''semantic analysis wrapper'''
'''wrapper function for all the semantic analysis passes, in the right order'''
# return infer(formalize(lamc(lamp(a))))
return (formalize(lamc(lamp(a))))
9 changes: 4 additions & 5 deletions prototype/Test.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from .Eval import *
from .Parser import *
from .Scanner import *
from .Semantic import *
from .Parser import Parse
from .Scanner import Scan

def test_expr(scan,parse):
x = """
input ⇒ expected output (in s-expr form)
Expand Down Expand Up @@ -200,7 +199,7 @@ def test_eval(scan,parse,evil):
print(f'{"expected:":>{len(i)}} {o}')


# def test(scan,parse,evil):
# def test_all(scan,parse,evil):
# print("test_expr:")
# test_expr(scan,parse)
# print("test_exception:")
Expand Down
7 changes: 7 additions & 0 deletions prototype/dev.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
'''import-just-about-everything file for hacking on this project'''

# Even though it's not a good general programming practice, wildcard imports
# make iterating and hacking on the source code easier. From "element/", start
# python, type "from prototype.dev import *"
# Now you have access to all the names.

from .Ast import Ast
from .Builtin import *
from .Scanner import Scan
Expand Down

0 comments on commit 12083da

Please sign in to comment.