diff --git a/implementations/Nim/simplejoy.nim b/implementations/Nim/simplejoy.nim new file mode 100644 index 0000000..5028b1f --- /dev/null +++ b/implementations/Nim/simplejoy.nim @@ -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("""[] [[]]""") \ No newline at end of file