Skip to content

Commit

Permalink
unpolished ideas for dict literals
Browse files Browse the repository at this point in the history
  • Loading branch information
hoosierEE committed May 10, 2024
1 parent a3f2e03 commit 018d7ab
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 111 deletions.
10 changes: 8 additions & 2 deletions README.org
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,16 @@ Planned features:
- =x::y= assign (mutable) variable
- No modified assignment operators. Use =a::a+1= instead of =a+:1=.
Meanwhile, verb-plus-colon still invokes the unary version of that verb, so =a-:1= is =a (- 1)=.
- Lists and function arguments evaluate left-to-right.
- Lists and function arguments evaluate left-to-right, top-to-bottom.
For example, =f[a+b;a:1;b:2]= or =(a+b;a:1;b:2)= are valid in K but not in Element.
In Element, =e1;e2= is two separate expressions evaluated left to right, and a list =(e1;e2)= collects the results of each of those expressions into a common container (the list itself).
In Element, =a;b= always parses as two separate expressions evaluated left to right regardless of being wrapped in any kind of parentheses.
Alternatives: use =a:1;b:2;(a+b;a;b)= or =2_(a:1;b:2;a+b;a;b)=.
# - Dictionary literals
# - ={a:2;b:5}= ⇔ ={`a:2;`b:5}= ⇔ =(`a;`b)!(2;5)= ⇔ =`a`b!2 5=
# - get: ={a:2}[`a]=
# - set: not supported for dict literals ={a:1}[`a]::2 /error=
# - set: have to bind (with =::=) to a name first: =d::{a:2;b:3}; d[`a]::40; d[`c]::42=
# - immutable dict can't be modified, even to add new keys: =i:{a:1}; i[`b]:3 /error=

Aspirational features:
- module system: =require x=, =provide x=
Expand Down
185 changes: 81 additions & 104 deletions prototype/Eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,116 +4,93 @@
# use plain Python values instead of Val class
from Ast import Ast
from Builtin import ASSIGN,VERB
from Semantic import lift_prj,formalize
from Semantic import lamp,formalize
class Sym(str):pass
class Name(str):pass
Ty = dict(zip((str,Sym,Name,int,float,dict,list),'csnifDL'))
Yt = {b:a for a,b in Ty.items()}
class Val:
def __init__(s,t,v): s.t,s.v = t,v
def __repr__(s): return f'{s.v}:{s.t}'
class Num(float):pass
Ty = dict(zip((tuple,int,float,Num,str,Name,Ast),
'l i f d s n a'.split()))
# TG = {int:'d',float:'d',**Ty}#generic Num mapping
Yt = {v:k for k,v in Ty.items()}

class K:
def __init__(s,v:str):
s.t: type = tty(v) if type(v)==tuple else Ty[ty(v)]
s.v: object = tuple(map(Yt[s.t.lower()],v)) if s.t.isupper() else Yt[s.t](v)

def __getitem__(s,k):
return s.v[k]

def __repr__(s):
return f'{s.v}:{s.t}'


def tty(xs:tuple) -> type:
t = {ty(x) for x in xs}
return Ty[t.pop()].upper() if len(t)==1 else 'L'

def isnumeric(x:str) -> bool:
return x.replace('.','').replace('-','').isnumeric()

def ty(x:str|tuple|list) -> type:
if type(x) in (list,tuple):
c00 = x[0].node[0]
if c00 in '"`': t = (str,Sym)[c00=='`']
else: t = float if any('.' in c.node for c in x) else int
return t
if type(x)==str:
if x[0] in '"`': return (str,Sym)[x[0]=='`']
if isnumeric(x): return float if '.' in x else int
return Name
return type(x)

def v1(op:str,val:Val):
match (op,val.t):
case ['-','f'|'i']: return Val(val.t,-val.v)
case ['-','F'|'I']: return Val(val.t,[-x for x in val.v])
case [',',t] if t.islower(): return Val(t.upper(),[val.v])
case [',',t]: return Val('L',[val.v])
case ['@',t]: return t
case _: raise RuntimeError('nyi')

def v2val(op:str,a:Val,b:Val) -> Val:
match (op,a.t,b.t):
#TODO: handle numeric types better, 'min' is brittle
case ['-','f'|'i','f'|'i']: return Val(min(a.t,b.t),a.v-b.v)
case ['-','f'|'i','F'|'I']: return Val(min(a.t,b.t).upper(),[a.v-x for x in b.v])
case ['-','F'|'I','f'|'i']: return Val(min(a.t,b.t).upper(),[x-b.v for x in a.v])
case ['-','F'|'I','F'|'I']: return Val(min(a.t,b.t).upper(),[x-y for x,y in zip(a.v,b.v)])

# case [',','f'|'i','f'|'i']: return Val(min(a.t,b.t).upper(),[a.v,b.v])
# case [',','F'|'I','f'|'i']: return Val(min(a.t,b.t),[*a.v,b.v])
# case [',','f'|'i','F'|'I']: return Val(min(a.t,b.t),[a.v,*b.v])
case [',',t,u] if t==u and t.islower(): return Val(t.upper(),[a.v,b.v])
case [',',t,u] if t==u and t.isupper(): return Val(t,[*a.v,*b.v])
case [',',t,u] if t.isupper() and u.islower(): return Val('L',[*a.v,b.v])
case [',',t,u] if t.islower() and u.isupper(): return Val('L',[*a.v,b.v])
case [',',t,u]: return Val('L',[*a.v,*b.v])

# case [',',t,u]:
# if hasattr(a.v,'__len__') and hasattr(b.v,'__len__'): return Val('L',[*a.v,*b.v])
# if hasattr(a.v,'__len__'): return Val('L',[*a.v,b.v])
# if hasattr(b.v,'__len__'): return Val('L',[a.v,*b.v])
# return Val(min(t,u),[a.v,b.v])
case ['@','L','i']: return Val(Ty[type(r:=a.v[b.v])],r)#TODO: outdex
case ['@',t,'i'] if t.isupper(): return Val(Ty[type(r:=a.v[b.v])],r)#TODO: outdex
case _: raise RuntimeError(f'nyi: {op} {a} {b}')

def v2(op:str,a,b):
if type(a)==type(b)==Val: return v2val(op,a,b)
raise RuntimeError(f'nyi: {op} not defined for {a}{op}{b}')

def evl(x:Ast,e=None) -> Val:
def ty(x:str) -> type:
match x:
case str():
if x[0] in '"`': return (str,Sym)[x[0]=='`']
if isnumeric(x): return float if '.' in x else int
return Name
case K(): return x.t
case _: return type(x)

def v1(op,x): return f'{op}:{x}'
def v2(op,x,y):
match op,ty(x),ty(y):
case ['-',tuple(),tuple()]: return tuple(v2(op,a,b) for a,b in zip(x,y))
case ['-',tuple(),float()]: return tuple(a-float(y) for a in x)
case ['-',float(),tuple()]: return tuple(float(x)-b for b in y)
case ['-',float(),float()]: return x-y
case [',',tuple(),tuple()]: return (*x,*y)
case [',',tuple(),_]: return (*x,y)
case [',',_,tuple()]: return (x,*y)
case [',',*_]: return (x,y)
raise RuntimeError(f'unrecognized use of ({op}) with args ({x}) and ({y})')

def evl(x:Ast,e=None) -> object:
e = e or {}
x = formalize(lift_prj(x))
x = formalize(lamp(x))
return Eval(e,x)

def Eval(e,x) -> Val:
if type(x)==Val: return x
if type(x)==str: return Val('v',x) if x in VERB else Val(Ty[t:=ty(x)],t(x))
if type(x)==Ast:
if not x.children:
r = Eval(e,x.node)
if r.t=='n': return e[r.v]
return r

if x.node=='vec':
t = ty(x.children)
return Val(Ty[t].upper(),[t(c.node) for c in x.children])

if x.node in ASSIGN:
n = x.children[0].node#name
r = Eval(e,x.children[1])#value
e[n] = r#NOTE: reference semantics make this update visible to caller
return r

if x.node in [*'(;']:#list or sequence
ks = [Eval(e,i) for i in x.children]
if x.node == ';': return ks[-1]#return the last of the sequence (progn)
return Val('L',ks)

if x.node in ('{','cmp','prj'): return x#defer eval of lambda, cmp, prj until/if they are applied.

if x.node in ('@','app'):#"app" always has 2 children TODO: what about "@"?
b = Eval(e,x.children[0])#body (...should it use {**e} instead of e?)
args = [Eval(e,xi) for xi in x.children[1].children] if x.children[1].node=='[' else [Eval(e,x.children[1])]
if type(b)==Ast and b.node=='{':
newenv = {a.node:v for a,v in zip(b.children[0].children,args)}
return Eval({**e,**newenv},b.children[1])
if type(b)==Val:
#FIXME: "a" not defined
# a = args[0] # not quite...
if b.t.isupper() and a.t=='a': return v2('@',b,a)

raise RuntimeError(f'nyi: {x}')

if type(x.node)==str and x.node[0] in VERB:
k = [Eval(e,c) for c in x.children[::(-1,1)[x.node in '(;']]]
return (0,v1,v2)[len(x.children)](x.node,*k[::-1])
else:
raise RuntimeError(f'ast not recognized: {x}')
raise RuntimeError('wat?!')
def Eval(e:dict,x:Ast) -> object:
if type(x)!=Ast: return x
if not x.children:
r = Eval(e,x.node)
return e[r] if ty(r)==Name else r

if x.node=='vec':
t = ty(x.children[0].node)
return tuple(t(c.node) for c in x.children)

if x.node in ASSIGN:
n = x.children[0].node#name
r = Eval(e,x.children[1])#value
e[n] = r#FIXME: reference semantics make this update visible to caller; want value semantics
return r

if x.node in [*'(;']:#list or sequence
ks = [Eval(e,i) for i in x.children]
return ks[-1] if x.node == ';' else ks#progn: last item only

if x.node in ('{','cmp','prj'): return x#defer eval of lambda, cmp, prj until/if they are applied.

if x.node in ('@','app'):#"app" always has 2 children TODO: what about "@"?
b = Eval(e,x.children[0])#body (...should it use {**e} instead of e?)
args = [Eval(e,xi) for xi in x.children[1].children] if x.children[1].node=='[' else [Eval(e,x.children[1])]
if type(b)==Ast and b.node=='{':
newenv = {a.node:v for a,v in zip(b.children[0].children,args)}
return Eval({**e,**newenv},b.children[1])
raise RuntimeError(f'nyi: {x}')

if type(x.node)==str and x.node[0] in VERB:
k = [Eval(e,c) for c in x.children[::(-1,1)[x.node in '(;']]]
return (0,v1,v2)[len(x.children)](x.node,*k[::-1])
else:
raise RuntimeError(f'ast not recognized: {x}')
14 changes: 9 additions & 5 deletions prototype/Semantic.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
'''
These transformations should happen before Eval.
e.g: formalize(lift_prj(Parse(Scan("x+1"))))
e.g: formalize(lamp(Parse(Scan("x+1"))))
Parse formalize
{1+x-3} ⇒ (lam (+ 1 (- x 3))) ⇒ (lam (prg x) (+ 1 (- x 3)))
{x-y} ⇒ (lam (+ x y)) ⇒ (lam (prg x y) (+ x y))
{3} ⇒ (lam 3) ⇒ (lam prg 3)
Parse lift_prj
Parse lamp
3- ⇒ (prj - 3) ⇒ (lam (prg x) (- 3 x))
- ⇒ (prj -) ⇒ (lam (prg x y) (- x y))
'''
from Ast import Ast
from Builtin import ASSIGN,VERB

def lift_prj(a:Ast) -> Ast:
def lamc(a:Ast) -> Ast:
'''composition ⇒ lambda'''
...

def lamp(a:Ast) -> Ast:
'''projection ⇒ lambda'''
ax,ay = Ast('x'),Ast('y')
match a.node,len(a.children):
Expand All @@ -23,8 +27,8 @@ def lift_prj(a:Ast) -> Ast:
return Ast('{',Ast('[',ax), Ast(v,ax))
return Ast('{',Ast('[',ax,ay), Ast(v,ax,ay))
case 'prj',2:
return Ast('{',Ast('[',ax), Ast(a.children[0].node,lift_prj(a.children[1]),ax))
return Ast(a.node, *map(lift_prj,a.children))
return Ast('{',Ast('[',ax), Ast(a.children[0].node,lamp(a.children[1]),ax))
return Ast(a.node, *map(lamp,a.children))

def get_params(a:Ast) -> str:
'''get x y z arguments from lambdas'''
Expand Down

0 comments on commit 018d7ab

Please sign in to comment.