Let's pile Nim code into one file too, why not?

This commit is contained in:
Simon Forman 2022-09-14 09:45:34 -07:00
parent aad2da35cf
commit 94929e8520
1 changed files with 435 additions and 0 deletions

View File

@ -0,0 +1,435 @@
import rdstdin, strutils
import bigints, fp
type
JoyListType* = List[JoyType]
JoyMapType* = Map[string, JoyListType]
JoyTypeType* = enum
joyAtom,
joyFalse,
joyInt,
joyList,
joyTrue
JoyType* = ref object
case kind*: JoyTypeType
of joyAtom: atomVal*: string
of joyFalse, joyTrue: nil
of joyInt: intVal*: BigInt
of joyList: listVal*: JoyListType
Token = string
ParseError* = object of ValueError
UnknownWordError* = object of ValueError
# Singleton values for Boolean type.
let j_true* = JoyType(kind: joyTrue)
let j_false* = JoyType(kind: joyFalse)
# Singleton values for Symbols.
let j_loop* = JoyType(kind: joyAtom, atomVal: "loop")
let empty_list = JoyType(kind: joyList, listVal: Nil[JoyType]())
# ███████╗██╗ ██╗██████╗ ██████╗ ███████╗███████╗███████╗██╗ ██████╗ ███╗ ██╗
# ██╔════╝╚██╗██╔╝██╔══██╗██╔══██╗██╔════╝██╔════╝██╔════╝██║██╔═══██╗████╗ ██║
# █████╗ ╚███╔╝ ██████╔╝██████╔╝█████╗ ███████╗███████╗██║██║ ██║██╔██╗ ██║
# ██╔══╝ ██╔██╗ ██╔═══╝ ██╔══██╗██╔══╝ ╚════██║╚════██║██║██║ ██║██║╚██╗██║
# ███████╗██╔╝ ██╗██║ ██║ ██║███████╗███████║███████║██║╚██████╔╝██║ ╚████║
# ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝
#
# As elegant as it is to model the expression as a stack, it's not very
# efficient, as concatenating definitions and other quoted programs to
# the expression is a common and expensive operation.
#
# Instead, let's keep a stack of sub-expressions, reading from them
# one-by-one, and prepending new sub-expressions to the stack rather than
# concatenating them.
proc push_int(n: BigInt, stack: JoyListType): JoyListType =
JoyType(kind: joyInt, intVal: n) ^^ stack
proc push_list(el: JoyListType, stack: JoyListType): JoyListType =
JoyType(kind: joyList, listVal: el) ^^ stack
proc pop_int(stack: JoyListType): (BigInt, JoyListType) =
if stack.isEmpty:
raise newException(ValueError, "Not enough values on stack.")
let a = stack.head
case a.kind:
of joyInt:
return (a.intVal, stack.tail)
else:
raise newException(ValueError, "Not an integer.")
proc pop_list(stack: JoyListType): (JoyListType, JoyListType) =
if stack.isEmpty:
raise newException(ValueError, "Not enough values on stack.")
let a = stack.head
case a.kind:
of joyList:
return (a.listVal, stack.tail)
else:
raise newException(ValueError, "Not a list.")
proc pop_bool(stack: JoyListType): (bool, JoyListType) =
if stack.isEmpty:
raise newException(ValueError, "Not enough values on stack.")
let a = stack.head
case a.kind:
of joyTrue:
return (true, stack.tail)
of joyFalse:
return (false, stack.tail)
else:
raise newException(ValueError, "Not a list.")
proc push_bool(a: bool, stack: JoyListType): JoyListType =
if a:
return j_true ^^ stack
return j_false ^^ stack
proc as_list(thing: JoyType): JoyListType =
case thing.kind
of joyList:
return thing.listVal
else:
raise newException(ValueError, "Only lists!")
proc push_quote(quote: JoyType, expression: JoyType): JoyType =
#[
Put the quoted program onto the stack-of-stacks.
]#
let ql = as_list(quote)
if ql.isEmpty:
return expression
let el = as_list(expression)
return JoyType(kind: joyList, listVal: (quote ^^ el))
proc push_quote_list(quote: JoyListType, expression: JoyType): JoyType =
#[
Put the quoted program onto the stack-of-stacks.
]#
return push_quote(JoyType(kind: joyList, listVal: quote), expression)
proc next_term(expression: JoyType): (JoyType, JoyType) =
#[
Return the next term from the expression and the new expression.
Raises ValueError if called on an empty expression.
(item, quote), expression = expression
return item, push_quote(quote, expression)
]#
let el = as_list(expression) ## JoyListType
let ehead = el.head ## JoyType
let eheadl = as_list(ehead) ## JoyListType
let item = eheadl.head ## JoyType
let quote = eheadl.tail ## JoyListType
if quote.isEmpty:
let t = JoyType(kind: joyList, listVal: el.tail)
return (item, t)
else:
let q = JoyType(kind: joyList, listVal: quote)
let t = JoyType(kind: joyList, listVal: (q ^^ el.tail))
return (item, t)
#██████╗ █████╗ ██████╗ ███████╗███████╗██████╗
#██╔══██╗██╔══██╗██╔══██╗██╔════╝██╔════╝██╔══██╗
#██████╔╝███████║██████╔╝███████╗█████╗ ██████╔╝
#██╔═══╝ ██╔══██║██╔══██╗╚════██║██╔══╝ ██╔══██╗
#██║ ██║ ██║██║ ██║███████║███████╗██║ ██║
#╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═╝
proc text_to_expression(text: string): JoyType =
#[
Convert a string to a Joy expression.
When supplied with a string this function returns a Python datastructure
that represents the Joy datastructure described by the text expression.
Any unbalanced square brackets will raise a ParseError.
:param str text: Text to convert.
:rtype: stack
:raises ParseError: if the parse fails.
]#
var frame : seq[JoyType] = @[]
var stack : seq[seq[JoyType]] = @[]
var thing : JoyType
for tok in text.replace("[", " [ ").replace("]", " ] ").splitWhitespace():
if tok == "[":
stack.add(frame)
frame = newSeq[JoyType](0)
continue
if tok == "]":
thing = JoyType(kind: joyList, listVal: frame.asList)
try:
frame = stack.pop()
except IndexDefect:
raise newException(ParseError, "Extra closing bracket.")
elif tok == "true":
thing = j_true
elif tok == "false":
thing = j_false
else:
try:
thing = JoyType(kind: joyInt, intVal: tok.initBigInt)
except ValueError:
thing = JoyType(kind: joyAtom, atomVal: tok)
frame.add(thing)
if stack.len() != 0:
raise newException(ParseError, "Unclosed bracket.")
JoyType(kind: joyList, listVal: frame.asList)
#██████╗ ██████╗ ██╗███╗ ██╗████████╗███████╗██████╗
#██╔══██╗██╔══██╗██║████╗ ██║╚══██╔══╝██╔════╝██╔══██╗
#██████╔╝██████╔╝██║██╔██╗ ██║ ██║ █████╗ ██████╔╝
#██╔═══╝ ██╔══██╗██║██║╚██╗██║ ██║ ██╔══╝ ██╔══██╗
#██║ ██║ ██║██║██║ ╚████║ ██║ ███████╗██║ ██║
#╚═╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
proc pr_str(thing: JoyType): string
proc joystr(s: JoyListType): string =
s.map(pr_str).asSeq.join(" ")
proc pr_str(thing: JoyType): string =
case thing.kind
of joyAtom: thing.atomVal
of joyInt: thing.intVal.toString
of joyList: "[" & joystr(thing.listVal) & "]"
of joyTrue: "true"
of joyFalse: "false"
proc print_expression*(stack: JoyListType): string =
joystr(stack)
proc print_stack*(stack: JoyListType): string =
joystr(stack.reverse)
# ██████╗ ██████╗ ███╗ ███╗██████╗ ██╗███╗ ██╗ █████╗ ████████╗ ██████╗ ██████╗ ███████╗
#██╔════╝██╔═══██╗████╗ ████║██╔══██╗██║████╗ ██║██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗██╔════╝
#██║ ██║ ██║██╔████╔██║██████╔╝██║██╔██╗ ██║███████║ ██║ ██║ ██║██████╔╝███████╗
#██║ ██║ ██║██║╚██╔╝██║██╔══██╗██║██║╚██╗██║██╔══██║ ██║ ██║ ██║██╔══██╗╚════██║
#╚██████╗╚██████╔╝██║ ╚═╝ ██║██████╔╝██║██║ ╚████║██║ ██║ ██║ ╚██████╔╝██║ ██║███████║
# ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝
proc branch(stack: JoyListType, expression: JoyType, dictionary: JoyMapType): (JoyListType, JoyType, JoyMapType) =
let (true_body, s0) = pop_list(stack)
let (false_body, s1) = pop_list(s0)
let (flag, s2) = pop_bool(s1)
if flag:
return (s2, push_quote_list(true_body, expression), dictionary)
return (s2, push_quote_list(false_body, expression), dictionary)
# I don't like how this extracts the quotes only to immediately
# re-wrap one of them in a joytype wrapper.
proc clear(stack: JoyListType, expression: JoyType, dictionary: JoyMapType): (JoyListType, JoyType, JoyMapType) =
return (empty_list.listVal, expression, dictionary)
proc concat(stack: JoyListType, expression: JoyType, dictionary: JoyMapType): (JoyListType, JoyType, JoyMapType) =
let (tos, s0) = pop_list(stack)
let (second, s1) = pop_list(s0)
return (push_list((second ++ tos), s1), expression, dictionary)
# ██╗███╗ ██╗████████╗███████╗██████╗ ██████╗ ██████╗ ███████╗████████╗███████╗██████╗
# ██║████╗ ██║╚══██╔══╝██╔════╝██╔══██╗██╔══██╗██╔══██╗██╔════╝╚══██╔══╝██╔════╝██╔══██╗
# ██║██╔██╗ ██║ ██║ █████╗ ██████╔╝██████╔╝██████╔╝█████╗ ██║ █████╗ ██████╔╝
# ██║██║╚██╗██║ ██║ ██╔══╝ ██╔══██╗██╔═══╝ ██╔══██╗██╔══╝ ██║ ██╔══╝ ██╔══██╗
# ██║██║ ╚████║ ██║ ███████╗██║ ██║██║ ██║ ██║███████╗ ██║ ███████╗██║ ██║
# ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
#
# The joy() interpreter function is extrememly simple. It accepts a stack,
# an expression, and a dictionary, and it iterates through the expression
# putting values onto the stack and delegating execution to functions which
# it looks up in the dictionary.
proc joy_eval(sym: string, stack: JoyListType, expression: JoyType, dictionary: JoyMapType): (JoyListType, JoyType, JoyMapType) =
case sym
of "add":
let (a, s0) = pop_int(stack)
let (b, s1) = pop_int(s0)
return (push_int(a + b, s1), expression, dictionary)
of "mul":
let (a, s0) = pop_int(stack)
let (b, s1) = pop_int(s0)
return (push_int(a * b, s1), expression, dictionary)
of "div":
let (a, s0) = pop_int(stack)
let (b, s1) = pop_int(s0)
return (push_int(b div a, s1), expression, dictionary)
of "mod":
let (a, s0) = pop_int(stack)
let (b, s1) = pop_int(s0)
return (push_int(b mod a, s1), expression, dictionary)
of "gt":
let (a, s0) = pop_int(stack)
let (b, s1) = pop_int(s0)
return (push_bool(b > a, s1), expression, dictionary)
of "lt":
let (a, s0) = pop_int(stack)
let (b, s1) = pop_int(s0)
return (push_bool(b < a, s1), expression, dictionary)
of "ge":
let (a, s0) = pop_int(stack)
let (b, s1) = pop_int(s0)
return (push_bool(b >= a, s1), expression, dictionary)
of "le":
let (a, s0) = pop_int(stack)
let (b, s1) = pop_int(s0)
return (push_bool(b <= a, s1), expression, dictionary)
of "ne":
let (a, s0) = pop_int(stack)
let (b, s1) = pop_int(s0)
return (push_bool(b != a, s1), expression, dictionary)
of "eq":
let (a, s0) = pop_int(stack)
let (b, s1) = pop_int(s0)
return (push_bool(b != a, s1), expression, dictionary)
of "branch":
return branch(stack, expression, dictionary)
of "clear":
return clear(stack, expression, dictionary)
of "concat":
return concat(stack, expression, dictionary)
else:
raise newException(UnknownWordError, "Unknown: " & sym)
return (stack, expression, dictionary)
# = ≡ eq
# != ≡ ne
# <> ≡ ne
proc joy(stack: JoyListType, expression: JoyType, dictionary: JoyMapType): (JoyListType, JoyMapType) =
var s = stack
var d = dictionary
var e = push_quote(expression, empty_list)
var term : JoyType
while not e.listVal.isEmpty:
(term, e) = next_term(e)
#echo pr_str(term)
case term.kind
of joyAtom:
(s, e, d) = joy_eval(term.atomVal, s, e, d)
else:
s = term ^^ s
return (s, d)
#
let stack = empty_list.listVal
let dict = newMap[string, JoyListType]()
#let exp = text_to_expression("2 3 add 23 mul 45 gt")
#let exp = text_to_expression("2 3 false [add] [mul] branch")
#let exp = text_to_expression("2 3 true [add] [mul] branch")
#let exp = text_to_expression("[add] [mul] concat")
#let (s,d) = joy(stack, exp, dict)
#echo print_stack(s)
var s = stack
var d = dict
var exp : JoyType
while true:
try:
exp = text_to_expression(readLineFromStdin("joy? "))
except IOError:
break
try:
(s,d) = joy(s, exp, dict)
except:
echo getCurrentExceptionMsg()
echo print_stack(s)
continue
echo print_stack(s)
#echo pr_str(text_to_expression("""
# [ [[abs] ii <=]
# [
# [<>] [pop !-] ||
# ] &&
# ]
# [[ !-] [[++]] [[--]] ifte dip]
# [[pop !-] [--] [++] ifte ]
# ifte
# true false 23
#"""))
# we could start with an empty list and add two expressions
# but instead let's preload a few "commands":
#let e = text_to_expression("[55 true][42]")
#let t = text_to_expression("23")
#
#let f = push_quote(t, e)
#echo pr_str(t)
#echo pr_str(e)
#echo pr_str(f)
#
#var (a, b) = next_term(f)
#echo pr_str(a)
#(a, b) = next_term(b)
#echo pr_str(a)
#(a, b) = next_term(b)
#echo pr_str(a)
#(a, b) = next_term(b)
#echo pr_str(a)
#text_to_expression("""[] [[]]""")