import rdstdin, strutils import bigints, fp #[ ███████╗████████╗ █████╗ ██████╗██╗ ██╗ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝ ███████╗ ██║ ███████║██║ █████╔╝ ╚════██║ ██║ ██╔══██║██║ ██╔═██╗ ███████║ ██║ ██║ ██║╚██████╗██║ ██╗ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ When talking about Joy we use the terms "stack", "quote", "sequence", "list", and others to mean the same thing: a simple linear datatype that permits certain operations such as iterating and pushing and popping values from (at least) one end. In describing Joy I have used the term quotation to describe all of the above, because I needed a word to describe the arguments to combinators which fulfill the same role in Joy as lambda abstractions (with variables) fulfill in the more familiar functional languages. I use the term list for those quotations whose members are what I call literals: numbers, characters, truth values, sets, strings and other quotations. All these I call literals because their occurrence in code results in them being pushed onto the stack. But I also call [London Paris] a list. So, [dup *] is a quotation but not a list. `"A Conversation with Manfred von Thun" w/ Stevan Apter `_ In Nim we use the cons list provided by nimfp: https://github.com/vegansk/nimfp Cons list: https://en.wikipedia.org/wiki/Cons#Lists The nodes in the list must be of one type (JoyType) of four kinds (JoyTypeType) corresponding to the four kinds of values in Joy: symbols, Booleans, integers, and lists. Note that true and false each are given their own JoyTypeType and singleton constant values (so technically there are five kinds.) It will be important to keep clear the distinction between a list (instance of JoyListType) vs. a list node (instance of JoyType) containing a list. ]# type JoyListType* = List[JoyType] JoyMapType* = Map[string, JoyListType] JoyTypeType* = enum joySymbol, joyTrue, joyFalse, joyInt, joyList JoyType* = ref object case kind*: JoyTypeType of joySymbol: symVal*: string of joyFalse, joyTrue: nil of joyInt: intVal*: BigInt of joyList: listVal*: JoyListType ParseError* = object of ValueError UnknownWordError* = object of ValueError let zero = 0.initBigInt # 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: joySymbol, symVal: "loop") # Singleton value for the empty list node. The # singleton value for the empty list itself is empty_list.listVal let empty_list = JoyType(kind: joyList, listVal: Nil[JoyType]()) # Global dictionary. var dictionary = newMap[string, JoyListType]() #[ ██╗ ██╗████████╗██╗██╗ ███████╗ ██║ ██║╚══██╔══╝██║██║ ██╔════╝ ██║ ██║ ██║ ██║██║ ███████╗ ██║ ██║ ██║ ██║██║ ╚════██║ ╚██████╔╝ ██║ ██║███████╗███████║ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚══════╝ Convert from nodes to values or raise errors. ]# proc as_list(thing: JoyType): JoyListType = # You've got a node, you want the list or it's an error. case thing.kind of joyList: return thing.listVal else: raise newException(ValueError, "Not a list.") proc as_int(i: JoyType): BigInt = # You've got a node, you want the int or it's an error. case i.kind of joyInt: return i.intVal else: raise newException(ValueError, "Not an integer.") #[ Push values onto stacks wrapping them in the correct node kinds. ]# 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 push_bool(a: bool, stack: JoyListType): JoyListType = if a: return j_true ^^ stack return j_false ^^ stack # Whence push_symbol()? We do not push symbols onto stacks. #[ Pop values from stacks. ]# proc pop_any(stack: JoyListType): (JoyType, JoyListType) = if stack.isEmpty: raise newException(ValueError, "Not enough values on stack.") return (stack.head, stack.tail) proc pop_int(stack: JoyListType): (BigInt, JoyListType) = if stack.isEmpty: raise newException(ValueError, "Not enough values on stack.") return (as_int(stack.head), stack.tail) proc pop_list_node(stack: JoyListType): (JoyType, JoyListType) = # For quotes that will be added to the expression we want to # keep the node wrapper. if stack.isEmpty: raise newException(ValueError, "Not enough values on stack.") case stack.head.kind: of joyList: return (stack.head, stack.tail) else: raise newException(ValueError, "Not a list.") proc pop_list(stack: JoyListType): (JoyListType, JoyListType) = # For stacks that will be used (e.g. concat'd or something) # we want the List. if stack.isEmpty: raise newException(ValueError, "Not enough values on stack.") return (as_list(stack.head), stack.tail) proc pop_bool(stack: JoyListType): (bool, JoyListType) = if stack.isEmpty: raise newException(ValueError, "Not enough values on stack.") case stack.head.kind: of joyTrue: return (true, stack.tail) of joyFalse: return (false, stack.tail) else: raise newException(ValueError, "Not a Boolean value.") # We might not need to reify to bool? Just "if foo == j_true" everywhere? #[ ███████╗██╗ ██╗██████╗ ██████╗ ███████╗███████╗███████╗██╗ ██████╗ ███╗ ██╗ ██╔════╝╚██╗██╔╝██╔══██╗██╔══██╗██╔════╝██╔════╝██╔════╝██║██╔═══██╗████╗ ██║ █████╗ ╚███╔╝ ██████╔╝██████╔╝█████╗ ███████╗███████╗██║██║ ██║██╔██╗ ██║ ██╔══╝ ██╔██╗ ██╔═══╝ ██╔══██╗██╔══╝ ╚════██║╚════██║██║██║ ██║██║╚██╗██║ ███████╗██╔╝ ██╗██║ ██║ ██║███████╗███████║███████║██║╚██████╔╝██║ ╚████║ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝ 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_quote(quote: JoyType, expression: JoyListType): JoyListType = #[ Put the quoted program NODE onto the stack-of-stacks. ]# if as_list(quote).isEmpty: return expression return quote ^^ expression proc push_quote_list(quote: JoyListType, expression: JoyListType): JoyListType = #[ Put the quoted program LIST onto the stack-of-stacks. ]# return push_quote(JoyType(kind: joyList, listVal: quote), expression) proc next_term(expression: JoyListType): (JoyType, JoyListType) = #[ 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 (eheadl, etail) = pop_list(expression) let item = eheadl.head ## JoyType let quote = eheadl.tail ## JoyListType if quote.isEmpty: return (item, etail) else: return (item, push_list(quote, etail)) #[ ██████╗ █████╗ ██████╗ ███████╗███████╗██████╗ ██╔══██╗██╔══██╗██╔══██╗██╔════╝██╔════╝██╔══██╗ ██████╔╝███████║██████╔╝███████╗█████╗ ██████╔╝ ██╔═══╝ ██╔══██║██╔══██╗╚════██║██╔══╝ ██╔══██╗ ██║ ██║ ██║██║ ██║███████║███████╗██║ ██║ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═╝ ]# proc text_to_expression(text: string): JoyListType = #[ 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 elif tok == "-": # see https://github.com/nim-lang/bigints/issues/116 thing = JoyType(kind: joySymbol, symVal: tok) else: try: thing = JoyType(kind: joyInt, intVal: tok.initBigInt) except ValueError: thing = JoyType(kind: joySymbol, symVal: tok) frame.add(thing) if stack.len() != 0: raise newException(ParseError, "Unclosed bracket.") frame.asList #██████╗ ██████╗ ██╗███╗ ██╗████████╗███████╗██████╗ #██╔══██╗██╔══██╗██║████╗ ██║╚══██╔══╝██╔════╝██╔══██╗ #██████╔╝██████╔╝██║██╔██╗ ██║ ██║ █████╗ ██████╔╝ #██╔═══╝ ██╔══██╗██║██║╚██╗██║ ██║ ██╔══╝ ██╔══██╗ #██║ ██║ ██║██║██║ ╚████║ ██║ ███████╗██║ ██║ #╚═╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ proc pr_str(thing: JoyType): string proc print_expression(s: JoyListType): string = s.map(pr_str).asSeq.join(" ") proc pr_str(thing: JoyType): string = case thing.kind of joySymbol: thing.symVal of joyInt: thing.intVal.toString of joyList: "[" & print_expression(thing.listVal) & "]" of joyTrue: "true" of joyFalse: "false" proc print_stack*(stack: JoyListType): string = print_expression(stack.reverse) # ██████╗ ██████╗ ███╗ ███╗██████╗ ██╗███╗ ██╗ █████╗ ████████╗ ██████╗ ██████╗ ███████╗ #██╔════╝██╔═══██╗████╗ ████║██╔══██╗██║████╗ ██║██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗██╔════╝ #██║ ██║ ██║██╔████╔██║██████╔╝██║██╔██╗ ██║███████║ ██║ ██║ ██║██████╔╝███████╗ #██║ ██║ ██║██║╚██╔╝██║██╔══██╗██║██║╚██╗██║██╔══██║ ██║ ██║ ██║██╔══██╗╚════██║ #╚██████╗╚██████╔╝██║ ╚═╝ ██║██████╔╝██║██║ ╚████║██║ ██║ ██║ ╚██████╔╝██║ ██║███████║ # ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ proc branch(stack: JoyListType, expression: JoyListType, dictionary: JoyMapType): (JoyListType, JoyListType, JoyMapType) = let (true_body_node, s0) = pop_list_node(stack) let (false_body_node, s1) = pop_list_node(s0) let (flag, s2) = pop_bool(s1) if flag: return (s2, push_quote(true_body_node, expression), dictionary) return (s2, push_quote(false_body_node, expression), dictionary) proc dip(stack: JoyListType, expression: JoyListType, dictionary: JoyMapType): (JoyListType, JoyListType, JoyMapType) = let (body_node, s0) = pop_list_node(stack) if s0.isEmpty: raise newException(ValueError, "Not enough values on stack.") let tos_as_list_of_one = s0.head ^^ empty_list.listVal return ( s0.tail, push_quote(body_node, push_quote_list(tos_as_list_of_one, expression)), dictionary ) proc i(stack: JoyListType, expression: JoyListType, dictionary: JoyMapType): (JoyListType, JoyListType, JoyMapType) = let (body_node, s0) = pop_list_node(stack) return ( s0, push_quote(body_node, expression), dictionary ) proc loop(stack: JoyListType, expression: JoyListType, dictionary: JoyMapType): (JoyListType, JoyListType, JoyMapType) = let (body_node, s0) = pop_list_node(stack) let (flag, s1) = pop_bool(s0) if flag: let l1 = body_node ^^ j_loop ^^ empty_list.listVal let e0 = push_quote_list(l1, expression) let e1 = push_quote(body_node, e0) return (s1, e1, dictionary) return (s1, expression, dictionary) #[ ██████╗ ██████╗ ██████╗ ███████╗ ██╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗ ██╔════╝██╔═══██╗██╔══██╗██╔════╝ ██║ ██║██╔═══██╗██╔══██╗██╔══██╗██╔════╝ ██║ ██║ ██║██████╔╝█████╗ ██║ █╗ ██║██║ ██║██████╔╝██║ ██║███████╗ ██║ ██║ ██║██╔══██╗██╔══╝ ██║███╗██║██║ ██║██╔══██╗██║ ██║╚════██║ ╚██████╗╚██████╔╝██║ ██║███████╗ ╚███╔███╔╝╚██████╔╝██║ ██║██████╔╝███████║ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚══╝╚══╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚══════╝ ]# proc clear(stack: JoyListType, expression: JoyListType, dictionary: JoyMapType): (JoyListType, JoyListType, JoyMapType) = return (empty_list.listVal, expression, dictionary) proc concat(stack: JoyListType, expression: JoyListType, dictionary: JoyMapType): (JoyListType, JoyListType, JoyMapType) = let (tos, s0) = pop_list(stack) let (second, s1) = pop_list(s0) return (push_list((second ++ tos), s1), expression, dictionary) proc cons(stack: JoyListType, expression: JoyListType, dictionary: JoyMapType): (JoyListType, JoyListType, JoyMapType) = let (tos, s0) = pop_list(stack) if s0.isEmpty: raise newException(ValueError, "Not enough values on stack.") return (push_list((s0.head ^^ tos), s0.tail), expression, dictionary) proc dup(stack: JoyListType, expression: JoyListType, dictionary: JoyMapType): (JoyListType, JoyListType, JoyMapType) = if stack.isEmpty: raise newException(ValueError, "Cannot dup empty stack.") return (stack.head ^^ stack, expression, dictionary) proc first(stack: JoyListType, expression: JoyListType, dictionary: JoyMapType): (JoyListType, JoyListType, JoyMapType) = let (tos, s0) = pop_list(stack) if tos.isEmpty: raise newException(ValueError, "Cannot take first of empty list.") return (tos.head ^^ s0, expression, dictionary) proc pop(stack: JoyListType, expression: JoyListType, dictionary: JoyMapType): (JoyListType, JoyListType, JoyMapType) = if stack.isEmpty: raise newException(ValueError, "Cannot pop empty stack.") return (stack.tail, expression, dictionary) proc rest(stack: JoyListType, expression: JoyListType, dictionary: JoyMapType): (JoyListType, JoyListType, JoyMapType) = let (tos, s0) = pop_list(stack) if tos.isEmpty: raise newException(ValueError, "Cannot take rest of empty list.") return (push_list(tos.tail, s0), expression, dictionary) proc stack(stack: JoyListType, expression: JoyListType, dictionary: JoyMapType): (JoyListType, JoyListType, JoyMapType) = return (push_list(stack, stack), expression, dictionary) proc swaack(stack: JoyListType, expression: JoyListType, dictionary: JoyMapType): (JoyListType, JoyListType, JoyMapType) = let (tos, s0) = pop_list(stack) return (push_list(s0, tos), expression, dictionary) proc swap(stack: JoyListType, expression: JoyListType, dictionary: JoyMapType): (JoyListType, JoyListType, JoyMapType) = let (tos, s0) = pop_any(stack) let (second, s1) = pop_any(s0) return ((second ^^ tos ^^ s1), expression, dictionary) proc truthy(stack: JoyListType, expression: JoyListType, dictionary: JoyMapType): (JoyListType, JoyListType, JoyMapType) = let (tos, s0) = pop_any(stack) case tos.kind: of joyTrue, joyFalse: return (stack, expression, dictionary) of joyInt: return (push_bool(tos.intVal != zero, s0), expression, dictionary) of joyList: return (push_bool(not tos.listVal.isEmpty, s0), expression, dictionary) else: raise newException(ValueError, "Cannot Boolify.") proc inscribe(stack: JoyListType, expression: JoyListType, d: JoyMapType): (JoyListType, JoyListType, JoyMapType) = let (def, s0) = pop_list(stack) if def.isEmpty: raise newException(ValueError, "No symbol in definition.") let sym = def.head case sym.kind: of joySymbol: # Check the global default dictionary. let hmm = dictionary.get(sym.symVal) if hmm.isEmpty: let dd = d + (sym.symVal, def.tail) return (s0, expression, dd) else: return (s0, expression, d) else: raise newException(ValueError, "Non-symbol as head of definition.") #[ ██╗███╗ ██╗████████╗███████╗██████╗ ██████╗ ██████╗ ███████╗████████╗███████╗██████╗ ██║████╗ ██║╚══██╔══╝██╔════╝██╔══██╗██╔══██╗██╔══██╗██╔════╝╚══██╔══╝██╔════╝██╔══██╗ ██║██╔██╗ ██║ ██║ █████╗ ██████╔╝██████╔╝██████╔╝█████╗ ██║ █████╗ ██████╔╝ ██║██║╚██╗██║ ██║ ██╔══╝ ██╔══██╗██╔═══╝ ██╔══██╗██╔══╝ ██║ ██╔══╝ ██╔══██╗ ██║██║ ╚████║ ██║ ███████╗██║ ██║██║ ██║ ██║███████╗ ██║ ███████╗██║ ██║ ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ 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: JoyListType, dictionary: JoyMapType): (JoyListType, JoyListType, 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 "-", "sub": let (a, s0) = pop_int(stack) let (b, s1) = pop_int(s0) return (push_int(b - a, 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 ">": let (a, s0) = pop_int(stack) let (b, s1) = pop_int(s0) return (push_bool(b > a, s1), expression, dictionary) of "<": let (a, s0) = pop_int(stack) let (b, s1) = pop_int(s0) return (push_bool(b < a, s1), expression, dictionary) of ">=": let (a, s0) = pop_int(stack) let (b, s1) = pop_int(s0) return (push_bool(b >= a, s1), expression, dictionary) of "<=": let (a, s0) = pop_int(stack) let (b, s1) = pop_int(s0) return (push_bool(b <= a, s1), expression, dictionary) of "!=", "<>": let (a, s0) = pop_int(stack) let (b, s1) = pop_int(s0) return (push_bool(b != a, s1), expression, dictionary) of "=": let (a, s0) = pop_int(stack) let (b, s1) = pop_int(s0) return (push_bool(b == a, s1), expression, dictionary) of "lshift": let (a, s0) = pop_int(stack) let (b, s1) = pop_int(s0) # I couldn't get toInt[int](a) to compile. # > Nim Error: cannot instantiate: 'toInt[int]'; got 1 typeof(s) but expected 0 # So just convert to string and back to int, and hope for the best... let n = parseInt($a) if n < 0: raise newException(ValueError, "Negative shift count.") let i = b shl n return (push_int(i, s1), expression, dictionary) of "rshift": let (a, s0) = pop_int(stack) let (b, s1) = pop_int(s0) let n = parseInt($a) if n < 0: raise newException(ValueError, "Negative shift count.") let i = b shr n return (push_int(i, s1), expression, dictionary) of "branch": return branch(stack, expression, dictionary) of "clear": return clear(stack, expression, dictionary) of "concat": return concat(stack, expression, dictionary) of "cons": return cons(stack, expression, dictionary) of "dip": return dip(stack, expression, dictionary) of "dup": return dup(stack, expression, dictionary) of "first": return first(stack, expression, dictionary) of "i": return i(stack, expression, dictionary) of "loop": return loop(stack, expression, dictionary) of "pop": return pop(stack, expression, dictionary) of "rest": return rest(stack, expression, dictionary) of "stack": return stack(stack, expression, dictionary) of "swaack": return swaack(stack, expression, dictionary) of "swap": return swap(stack, expression, dictionary) of "bool": # bool is a reserved word in Nim. return truthy(stack, expression, dictionary) of "inscribe": return inscribe(stack, expression, dictionary) else: let def = dictionary.get(sym) if def.isEmpty: raise newException(UnknownWordError, "Unknown: " & sym) return (stack, push_quote_list(def.get(), expression), dictionary) proc joy(stack: JoyListType, expression: JoyListType, dictionary: JoyMapType): (JoyListType, JoyMapType) = var s = stack var d = dictionary var e = push_quote_list(expression, empty_list.listVal) var term: JoyType while not e.isEmpty: #echo print_stack(s) & " . " & print_expression(e) (term, e) = next_term(e) #echo pr_str(term) case term.kind of joySymbol: (s, e, d) = joy_eval(term.symVal, s, e, d) else: s = term ^^ s return (s, d) proc add_def(def: string, dictionary: var JoyMapType) = let d = text_to_expression(def) let sym = d.head case sym.kind: of joySymbol: let def = dictionary.get(sym.symVal) if def.isEmpty: dictionary = dictionary + (sym.symVal, d.tail) else: raise newException(ValueError, def) #proc defs_file2dict(defs_filename: string = "defs.txt"): JoyMapType = # var strm = newFileStream(defs_filename, fmRead) # var dictionary = newMap[string, JoyListType]() # var line = "" # if not isNil(strm): # while strm.readLine(line): # if line.isEmptyOrWhitespace: # continue # add_def(line, dictionary) # strm.close() # return dictionary #let dictionary = defs_file2dict() #var d = dictionary const defs_text = staticRead"defs.txt" for line in defs_text.splitLines: if line.isEmptyOrWhitespace: continue add_def(line, dictionary) var d = dictionary var s = empty_list.listVal var exp: JoyListType while true: try: exp = text_to_expression(readLineFromStdin("joy? ")) except IOError: break try: (s, d) = joy(s, exp, d) except: writeLine(stderr, getCurrentExceptionMsg()) echo print_stack(s)