├── LICENSE ├── README.md ├── doc.md ├── examples └── 2x2.jf ├── interpreter.py ├── jellyfish.py ├── print_parse.py ├── stdlib.md ├── tutorial.md ├── utils.py └── vocab.py /LICENSE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iatorm/jellyfish/950e738458b9e2d59948432aaa44719299a51da4/LICENSE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jellyfish 2 | 3 | Jellyfish is a two-dimensional esoteric programming language inspired by J and written in Python 3. 4 | It was inspired by a [challenge](http://codegolf.stackexchange.com/questions/65661/parse-a-two-dimensional-syntax) on PPCG. 5 | The name was suggested in PPCG chat as a combination of [Jelly](https://github.com/DennisMitchell/jelly), a golfing language inspired by J, and [Fish](https://esolangs.org/wiki/Fish), a two-dimensional esoteric language. 6 | There's a [syntax documentation file](https://github.com/iatorm/jellyfish/blob/master/doc.md), a [reference file](https://github.com/iatorm/jellyfish/blob/master/stdlib.md), and an [online interpreter](http://jellyfish.tryitonline.net/), courtesy of [Dennis from PPCG](http://codegolf.stackexchange.com/users/12012/dennis). 7 | 8 | Development of Jellyfish is slow but ongoing, so things may freeze for a long time and then break without notice. 9 | At the moment, there's a command-line interpreter and a rudimentary documentation file. 10 | The interpreter can be invoked by the command 11 | 12 | python jellyfish.py 13 | 14 | Input is taken from STDIN, and output goes to STDOUT. 15 | The standard file extension for Jellyfish source files is `jf`, but this is not enforced by the interpreter. -------------------------------------------------------------------------------- /doc.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | For a list of built-in functions and operators, see [here](https://github.com/iatorm/jellyfish/blob/master/stdlib.md). 4 | 5 | ## Source syntax 6 | 7 | Jellyfish is a two-dimensional language, and the source code should be thought of as a 2D grid. 8 | Each position of the grid may contain an _item_, which is one of the following: 9 | 10 | - A _value_, which is further classified as one of the following: 11 | - A _numeric literal_, visually a horizontal run of digits. It is placed at the position of the leftmost digit. 12 | - A _character literal_, visually a single quote `'` followed by said character. It is placed at the position of the quote. 13 | - A _string literal_, visually a horizontal run of characters surrounded by double quotes `"`. A closing quote is inferred at the end of a line. The item is placed at the left quote. 14 | - An _input value_, denoted by the letter `i` for evaluated input and `I` for raw string input. These are parsed from STDIN in the normal English reading order before the program is executed. 15 | - A _function_, which transforms one or two input values into a single output value. 16 | - An _operator_, which transforms one or two input values or functions into a new function. 17 | - A _control character_, which affects the parsing process. 18 | 19 | The following _control characters_ modify the parsing process, mostly the way functions are operators take their arguments. 20 | 21 | - `B` blocks all flow of data. Functions and operators cannot take any arguments through it. 22 | - `V` blocks all values. Functions cannot take their arguments through it. Operators cannot take value arguments, but can take function arguments. 23 | - `F` blocks all functions. An operator can still take an input through this character, but instead of a function, its output value will be considered. 24 | - `A` blocks all arguments. An operator will not be evaluated on the arguments of an input obtained through it. 25 | - `E` turns a southward argument-seeking process to the east. 26 | - `S` turns an eastward argument-seeking process to the south. 27 | - `X` is a combination of `E` and `S`. 28 | 29 | Whitespace is ignored, and all but the leftmost characters of literals are regarded as whitespace. 30 | 31 | Most functions and operators have _unary_ and _binary_ forms. 32 | A function on the grid takes as its inputs the nearest values to its south and east; if both are present, the binary form is used, and if only one is present, the unary form is used. 33 | An operator also takes its inputs from the south and east, but they are functions instead of values. 34 | Only if a value is encountered instead of a function, it is used as the input to an operator. 35 | The operator evaluates its argument(s) and produces a new function, and this function is evaluated on the argument(s) of its east input (or, if the east input has no arguments, its south input). 36 | Every value (a literal or input value) on the grid is counted as having one argument, namely itself. 37 | A program is run by evaluating the top left item, and all other items required for that, in an unspecified order (except that function calls are always evaluated after their arguments). 38 | Unnecessary function calls are not evaluated, even when the function and its arguments are used in an operator call. 39 | 40 | ## Input and output format 41 | 42 | Input and output values have the same format. 43 | A number is a string of digits, possibly prefixed by `-`, and possibly containing the decimal point `.` and/or the exponent marker `e`. 44 | Characters are surrounded in single quotes `'`, and strings in double quotes `"`, from both sides. 45 | Arrays are wrapped in `[]`, and their items are separated by spaces when necessary. 46 | The arrays can be nested in an arbitrary way, and strings are simply arrays of characters. 47 | -------------------------------------------------------------------------------- /examples/2x2.jf: -------------------------------------------------------------------------------- 1 | /+`$i 2 | F,2 3 | 2 4 | ~-2 5 | ,1 -------------------------------------------------------------------------------- /interpreter.py: -------------------------------------------------------------------------------- 1 | from utils import * 2 | from vocab import * 3 | from enum import Enum 4 | import math 5 | 6 | 7 | class Dir(Enum): 8 | east = 0 9 | south = 1 10 | 11 | class ItemType(Enum): 12 | data = 0 13 | function = 1 14 | operator = 2 15 | control = 3 16 | dummy = 4 17 | 18 | class Item: 19 | "An item in the matrix" 20 | 21 | def __init__(self, type, content, filled=False, evald=False): 22 | self.type = type 23 | self.content = content 24 | self.filled = filled 25 | self.evald = evald 26 | self.value = None 27 | self.func = None 28 | self.l_arg = None 29 | self.r_arg = None 30 | 31 | def evaluate(self): 32 | if self.type in [ItemType.function, ItemType.operator] and not self.evald: 33 | self.value = self.func(self.l_arg, self.r_arg) 34 | self.evald = True 35 | return self.value 36 | 37 | class Connection: 38 | "A connection to an item, possibly blocking some fields." 39 | 40 | def __init__(self, pos, has_value, has_func, has_args): 41 | self.pos = pos 42 | self.has_value = has_value 43 | self.has_func = has_func 44 | self.has_args = has_args 45 | 46 | def find_item(items, max_x, max_y, x, y, direction): 47 | "Returns a Connection object." 48 | has_value = has_func = has_args = True 49 | while x <= max_x and y <= max_y: 50 | if (x,y) in items: 51 | item = items[(x,y)] 52 | if item.type == ItemType.data: 53 | return Connection((x,y), has_value, False, has_args) 54 | elif item.type == ItemType.function or item.type == ItemType.operator: 55 | return Connection((x,y), has_value, has_func, has_args) 56 | elif item.type == ItemType.control: 57 | char = item.content 58 | if char == 'B': # Block all 59 | return Connection(None, False, False, False) 60 | elif char == 'V': # Block value 61 | has_value = False 62 | elif char == 'F': # Block function 63 | has_func = False 64 | elif char == 'A': # Block arguments 65 | has_args = False 66 | elif char == 'X': # Switch direction 67 | if direction == Dir.east: 68 | direction = Dir.south 69 | else: 70 | direction = Dir.east 71 | elif char == 'S': # Turn south 72 | direction = Dir.south 73 | elif char == 'E': # Turn east 74 | direction = Dir.east 75 | if direction == Dir.east: 76 | x += 1 77 | else: 78 | y += 1 79 | return Connection(None, False, False, False) 80 | 81 | def parse(matrix): 82 | "Parse a code matrix into a graph of items." 83 | items = {} 84 | digits = "0123456789" 85 | control_chars = "BVFAESX" 86 | for y in range(len(matrix)): 87 | # Parse a row 88 | x = 0 89 | while x < len(matrix[y]): 90 | char = matrix[y][x] 91 | if char == "'": 92 | # Parse a character 93 | if x == len(matrix[y]) - 1: 94 | parsed_char = '\n' 95 | else: 96 | parsed_char = matrix[y][x+1] 97 | item = Item(ItemType.data, to_char_atom(parsed_char)) 98 | items[(x,y)] = item 99 | x += 2 100 | elif char == '"': 101 | # Parse a string 102 | i = x + 1 103 | chars = [] 104 | while i < len(matrix[y]) and matrix[y][i] != '"': 105 | parsed_char = matrix[y][i] 106 | if parsed_char == '\\': 107 | if i == len(matrix[y]) - 1: 108 | parsed_char = '\n' 109 | else: 110 | parsed_char = matrix[y][i+1] 111 | if parsed_char == 'n': 112 | parsed_char = '\n' 113 | i += 2 114 | else: 115 | i += 1 116 | chars.append(parsed_char) 117 | item = Item(ItemType.data, [to_char_atom(c) for c in chars]) 118 | items[(x,y)] = item 119 | x = i + 1 120 | elif char in digits: 121 | # Parse a number 122 | num = "" 123 | i = x 124 | while i < len(matrix[y]) and matrix[y][i] in digits: 125 | num += matrix[y][i] 126 | i += 1 127 | item = Item(ItemType.data, to_num_atom(int(num))) 128 | items[(x,y)] = item 129 | x = i 130 | elif char == 'i': 131 | # Parse input 132 | input_value = parse_value(input())[0] 133 | item = Item(ItemType.data, input_value) 134 | items[(x,y)] = item 135 | x += 1 136 | elif char == 'I': 137 | # Parse raw string input 138 | input_string = input() 139 | input_value = [to_char_atom(char) for char in input_string] 140 | item = Item(ItemType.data, input_value) 141 | items[(x,y)] = item 142 | x += 1 143 | elif char in func_defs: 144 | # Parse a function 145 | item = Item(ItemType.function, func_defs[char]) 146 | items[(x,y)] = item 147 | x += 1 148 | elif char in oper_defs: 149 | # Parse a operator 150 | item = Item(ItemType.operator, oper_defs[char]) 151 | items[(x,y)] = item 152 | x += 1 153 | elif char in control_chars: 154 | # Parse a control character 155 | item = Item(ItemType.control, char) 156 | items[(x,y)] = item 157 | x += 1 158 | else: 159 | x += 1 160 | triples = {} 161 | max_x = max(len(row) for row in matrix) 162 | max_y = len(matrix) 163 | for ((x, y), item) in items.items(): 164 | l_conn = find_item(items, max_x, max_y, x, y+1, Dir.south) 165 | r_conn = find_item(items, max_x, max_y, x+1, y, Dir.east) 166 | triples[(x,y)] = (item, l_conn, r_conn) 167 | triples[None] = (Item(ItemType.dummy, "Dummy"), None, None) 168 | return triples 169 | 170 | def fill(items, pos=(0,0), level=0): 171 | "Fill in the fields of the given items." 172 | item, l_conn, r_conn = items[pos] 173 | if item.filled or item.type == ItemType.dummy: 174 | return item 175 | if item.type == ItemType.data: 176 | item.value = item.content 177 | item.r_arg = item.content 178 | else: 179 | l_nbor = fill(items, l_conn.pos, level+1) 180 | r_nbor = fill(items, r_conn.pos, level+1) 181 | if item.type == ItemType.function: 182 | item.func = item.content 183 | item.l_arg = l_nbor.evaluate() if l_conn.has_value else None 184 | item.r_arg = r_nbor.evaluate() if r_conn.has_value else None 185 | elif item.type == ItemType.operator: 186 | oper = item.content 187 | if l_conn.has_func and l_nbor.func is not None: 188 | l_input = l_nbor.func 189 | else: 190 | l_input = l_nbor.evaluate() if l_conn.has_value else None 191 | if r_conn.has_func and r_nbor.func is not None: 192 | r_input = r_nbor.func 193 | else: 194 | r_input = r_nbor.evaluate() if r_conn.has_value else None 195 | item.func = oper(l_input, r_input) 196 | r_l_arg = r_nbor.l_arg if r_conn.has_args else None 197 | r_r_arg = r_nbor.r_arg if r_conn.has_args else None 198 | if r_l_arg is None and r_r_arg is None: 199 | item.l_arg = l_nbor.l_arg if l_conn.has_args else None 200 | item.r_arg = l_nbor.r_arg if l_conn.has_args else None 201 | else: 202 | item.l_arg, item.r_arg = r_l_arg, r_r_arg 203 | item.filled = True 204 | return item 205 | 206 | def interpret(matrix): 207 | "Interpret a program, returning the top left value." 208 | items = parse(matrix) 209 | corner = fill(items) 210 | return corner.evaluate() 211 | -------------------------------------------------------------------------------- /jellyfish.py: -------------------------------------------------------------------------------- 1 | from interpreter import interpret 2 | import sys 3 | 4 | with open(sys.argv[1], 'r') as source_file: 5 | program = source_file.read().splitlines() 6 | interpret(program) 7 | -------------------------------------------------------------------------------- /print_parse.py: -------------------------------------------------------------------------------- 1 | from utils import * 2 | import re 3 | 4 | def prettyprint(value, quotes=True): 5 | "Convert a value into a human-readable string." 6 | if is_atom(value): 7 | if value.type == AtomType.num: 8 | return str(value.value) 9 | else: 10 | return "'"*quotes + chr(abs(int(value.value))) + "'"*quotes 11 | all_chars = all(atom.type == AtomType.char for atom in flatten(value)) 12 | if all_chars and height(value) == 1: 13 | return '"' + ''.join(prettyprint(item, quotes=False) for item in value) + '"' 14 | else: 15 | return "[" + " ".join(prettyprint(item) for item in value) + "]" 16 | 17 | def matrix_print(value): 18 | "Convert a value into a beautiful grid-style string." 19 | if is_atom(value): 20 | return prettyprint(value) 21 | level = height(value) 22 | flat = flatten(value) 23 | rows = flatten(value, 1) 24 | max_len = max(len(row) for row in rows) 25 | all_chars = all(atom.type == AtomType.char for atom in flat) 26 | pads = [1 + max(print_len(row[i], not all_chars) if i < len(row) else 0 27 | for row in rows) 28 | for i in range(max_len)] 29 | if not all_chars: 30 | pads[0] -= 1 31 | return matrix_print_aux(value, level, pads, not all_chars) 32 | 33 | def print_len(atom, quotes): 34 | if atom.type == AtomType.num: 35 | return len(str(atom.value)) 36 | else: 37 | return 3*quotes 38 | 39 | def matrix_print_aux(value, level, pads, quotes): 40 | if level == 1: 41 | string = "" 42 | for index, item in enumerate(value): 43 | if item.type == AtomType.num: 44 | string += str(item.value).rjust(pads[index]) 45 | else: 46 | char = chr(abs(int(item.value))) 47 | string += ("'"*quotes + char + "'"*quotes).rjust(pads[index]) 48 | return string 49 | else: 50 | item_strings = [matrix_print_aux(item, level-1, pads, quotes) 51 | for item in value] 52 | return ("\n"*(level-1)).join(item_strings) 53 | 54 | def parse_value(string): 55 | digits = "0123456789" 56 | string = string.lstrip() 57 | if string[0] in '-.e' + digits: 58 | neg = string[0] == '-' 59 | j = j0 = 1 if neg else 0 60 | while j < len(string) and string[j] in digits: 61 | j += 1 62 | if j < len(string) and string[j] == '.': 63 | dec = True 64 | j += 1 65 | while j < len(string) and string[j] in digits: 66 | j += 1 67 | else: 68 | dec = False 69 | j1 = j 70 | if j < len(string) and string[j] == 'e': 71 | exp = True 72 | j += 1 73 | neg_exp = j < len(string) and string[j] == '-' 74 | j = j2 = j+1 if neg_exp else j 75 | while j < len(string) and string[j] in digits: 76 | j += 1 77 | if j < len(string) and string[j] == '.': 78 | dec_exp = True 79 | j += 1 80 | while j < len(string) and string[j] in digits: 81 | j += 1 82 | else: 83 | dec_exp = False 84 | else: 85 | exp = False 86 | multiplier = (-1 if neg else 1) * (float if dec else int)(string[j0:j1]) 87 | if exp: 88 | exponent = (-1 if neg_exp else 1) * (float if dec_exp else int)(string[j2:j]) 89 | else: 90 | exponent = 0 91 | return to_num_atom(multiplier * 10**exponent), string[j:] 92 | elif string[0] == "'": 93 | if string[1] == '\\' and string[3] == "'": 94 | char = string[2] 95 | if char == 'n': 96 | char == '\n' 97 | j = 4 98 | elif string[2] == "'": 99 | char = string[1] 100 | j = 3 101 | return to_char_atom(char), string[j:] 102 | elif string[0] == '"': 103 | parsed_string = "" 104 | j = 1 105 | while string[j] != '"': 106 | if string[j] == '\\': 107 | char = string[j+1] 108 | if char == 'n': 109 | char = '\n' 110 | parsed_string += char 111 | j += 2 112 | else: 113 | parsed_string += string[j] 114 | j += 1 115 | return [to_char_atom(char) for char in parsed_string], string[j+1:] 116 | elif string[0] == '[': 117 | array = [] 118 | string = string[1:].lstrip() 119 | while string[0] != ']': 120 | value, string = parse_value(string) 121 | array.append(value) 122 | string = string.lstrip() 123 | return array, string[1:] 124 | -------------------------------------------------------------------------------- /stdlib.md: -------------------------------------------------------------------------------- 1 | # Standard library 2 | 3 | ## Notation 4 | 5 | In this document, the arguments of functions are **a** and **b** (with binary), function arguments of operators are **f** and **g** (with binary), and value arguments of operators are **x** and **y** (with binary). 6 | The first argument is south, the second is east. 7 | Monospace font is reserved for Jellyfish code. 8 | Lists are compared in lexicographical order, and atoms are strictly lower than lists. 9 | Any input combination not listed here is unimplemented, and for 0-threaded functions, list inputs are not listed either. 10 | 11 | ## Functions 12 | 13 | Threading levels are for unary **a**, binary **a** and binary **b**. 14 | Level -1 means no threading. 15 | A **'** after an argument means that atomic arguments are converted to singleton lists. 16 | 17 | | Symbol | Name | Arguments | Threading | Result | Notes | 18 | | :----: | ------------------ | -------------------------- | --------- | ------ | ----- | 19 | | `{` | Left identity | **a** (any) | -1 | **a** | 20 | | | | **a** (any), **b** (any) | -1, -1 | **a** | 21 | | `}` | Right identity | **a** (any) | -1 | **a** | 22 | | | | **a** (any), **b** (any) | -1, -1 | **b** | 23 | | `j` | Read value | **a** (any) | -1 | Read STDIN and eval | 24 | | `j` | Eval/uneval | **a** (atom), **b** (any) | 0, -1 | If **a > 0**, evaluate string **b**, otherwise convert **b** to string | If **a > 0**, a multidimensional **b** is flattened before conversion 25 | | `J` | Read string | **a** (any) | -1 | Read STDIN as string | 26 | | `J` | Read chars | **a** (any), **b** (atom) | -1, 0 | Read **b** characters from STDIN | Stops if EOF is encountered, reads entire STDIN if **b < 0** | 27 | | `p` | Print | **a** (any) | -1 | Print **a** to STDOUT, return **a** | 28 | | `P` | Matrix print | **a** (any) | -1 | Print **a** to STDOUT in matrix format, return **a** | 29 | | `+` | Abs | **a** (atom) | 0 | **abs(a)** | 30 | | | Add | **a** (atom), **b** (atom) | 0, 0 | **a + b** | 31 | | `-` | Negate | **a** (atom) | 0 | **-a** | 32 | | | Subtract | **a** (atom), **b** (atom) | 0, 0 | **b - a** | 33 | | `*` | Signum | **a** (atom) | 0 | **sign(a)** | 34 | | | Multiply | **a** (atom), **b** (atom) | 0, 0 | **a * b** | 35 | | `%` | Reciprocal | **a** (atom) | 0 | **1 / a** | Return **0** for **a = 0** | 36 | | | Divide | **a** (atom), **b** (atom) | 0, 0 | **b / a** | Return **0** for **a = 0** | 37 | | `|` | Round | **a** (atom) | 0 | **round(a)** | 38 | | | Modulus | **a** (atom), **b** (atom) | 0, 0 | **b mod a** | Return **0** for **a = 0** | 39 | | `m` | Floor | **a** (atom) | 0 | **floor(a)** | 40 | | | Minimum | **a** (any), **b** (any) | -1, -1 | **min(a, b)** | 41 | | `M` | Ceiling | **a** (atom) | 0 | **ceiling(a)** | 42 | | | Maximum | **a** (any), **b** (any) | -1, -1 | **max(a, b)** | 43 | | `x` | Prime factors | **a** (atom) | 0 | Prime factors of **a** in ascending order | 44 | | | XOR | **a** (atom), **b** (atom) | 0, 0 | **a xor b** | 45 | | `b` | Base encode | **a** (atom) | 0 | Base-2 digits of **a** | 46 | | | | **a** (any), **b** (atom) | 1, 0 | Base-**a** digits of **b** | 47 | | `d` | Base decode | **a** (any) | 1 | Digit(s) **a** as base-2 number | 48 | | | | **a** (any), **b** (any) | 1, 1 | Digit(s) **b** as base-**a** number | 49 | | `=` | Equality | **a** (any), **b** (any) | -1, -1 | **1** if **a = b**, otherwise **0** | 50 | | `<` | Decrement | **a** (atom) | -1 | **a - 1** | 51 | | | Head | **a** (list) | -1 | First item of **a** | 52 | | | Less than | **a** (any), **b** (any) | -1, -1 | **1** if **a < b**, otherwise **0** | 53 | | `>` | Increment | **a** (atom) | -1 | **a + 1** | 54 | | | Tail | **a** (list) | -1 | **a** with first item removed | 55 | | | Greater than | **a** (any), **b** (any) | -1, -1 | **1** if **a > b**, otherwise **0** | 56 | | `^` | Square | **a** (atom) | -1 | **a2** | 57 | | | Init | **a** (list) | -1 | **a** with last item removed | 58 | | | Power | **a** (atom), **b** (atom) | 0, -1 | **ba** | 59 | | | Take | **a** (atom), **b** (list) | 0, -1 | **a** items from **b** | From beginning if **a > 0**, from end if **a < 0** | 60 | | `v` | Square root | **a** (atom) | -1 | Square root of **a** | 61 | | | Last | **a** (list) | -1 | Last item of **a** | 62 | | | Root | **a** (atom), **b** (atom) | 0, -1 | **b(1/a)** | 63 | | | Drop | **a** (atom), **b** (list) | 0, -1 | **b** with **a** items removed | From beginning if **a > 0**, from end if **a < 0** | 64 | | `!` | Factorial | **a** (atom) | -1 | **a!** | 65 | | | Permutations | **a** (list) | -1 | Permutations of **a** | 66 | | | Falling factorial | **a** (atom), **b** (atom) | 0, -1 | **b! / (b-a)!** | 67 | | | K-permutations | **a** (atom), **b** (list) | 0, -1 | Length-**a** permutations of **b** | 68 | | `c` | Character | **a** (atom) | 0 | **char(a)** | 69 | | | Element | **a** (any), **b'** (list) | -1, -1 | **1** if **a** occurs in **b**, otherwise **0** | 70 | | `C` | Power of two | **a** (atom) | -1 | **2a** | 71 | | | Subsequences | **a** (list) | -1 | Subsequences of **a** | 72 | | | Binomial | **a** (atom), **b** (atom) | 0, -1 | **b! / a!\*(b-a)!** | 73 | | | K-subsequences | **a** (atom), **b** (list) | 0, -1 | Length-**a** subsequences of **b** | 74 | | `n` | Number | **a** (atom) | 0 | **num(a)** | 75 | | | Intersection | **a'** (list), **b'** (list)| -1, -1 | Intersection of **a** and **b** | 76 | | `u` | Uniques | **a'** (list) | -1 | **uniques(a)** | 77 | | | Union | **a'** (list), **b'** (list)| -1, -1 | Union of **a** and **b** | 78 | | `N` | Logical negation | **a** (any) | -1 | **0** if **a** is truthy, **1** otherwise | 79 | | | List difference | **a'** (list), **b'** (list)| -1, -1 | **a** with items of **b** removed | 80 | | `#` | Length | **a** (atom) | -1 | Number of base-10 digits in **a** | 81 | | | | **a** (list) | -1 | Length of **a** | 82 | | | Repeat | **a** (atom), **b'** (list)| -1, -1 | Each item of **b** repeated **a** times | 83 | | | Repeat/filter | **a** (list), **b'** (list)| -1, -1 | Each item of **b** repeated by the corresponding item of **a** | 84 | | `R` | Reverse | **a** (atom) | -1 | **a** | 85 | | | | **a** (list) | -1 | **a** reversed | 86 | | | Rotate | **a** (atom), **b** (atom) | 0, -1 | **b** | 87 | | | | **a** (atom), **b** (list) | 0, -1 | **b** rotated **a** steps to the left | 88 | | `k` | To indices | **a'** (list) | 1 | Convert bitmask **a** to list of indices | 89 | | `K` | To bitmask | **a'** (list) | 1 | Convert list of indices **a** to bitmask | 90 | | `o` | Order | **a'** (list) | -1 | **a** in increasing order | 91 | | | | **a'** (list), **b'** (list)| -1, -1 | **b** ordered using **a** as keys | **a** is repeated or truncated to have **b**'s length | 92 | | `r` | Range | **a** (atom) | -1 | Range from **0** to **a-1** | From **a+1** to **0** if **a < 0** | 93 | | | | **a** (list) | -1 | Cartesian product of ranges for atoms in **a** | 94 | | | | **a** (any), **b** (any) | -1, -1 | Range from **a** to **b-1**, or Cartesian product of ranges like above | 95 | | `,` | Flatten | **a** (any) | -1 | **flatten(a)** | 96 | | | Concatenate | **a** (any), **b** (any) | -1, -1 | **concatenate(a, b)** | 97 | | `;` | Singleton | **a** (any) | -1 | **[a]** | 98 | | | Pair | **a** (any), **b** (any) | -1, -1 | **[a, b]** | 99 | | `$` | Shape | **a** (any) | -1 | Shape vector of **a** | 100 | | | Reshape | **a'** (list), **b** (any) | 1, -1 | **b** reshaped according to shape vector **a** | 101 | | `@` | Indices | **a** (any) | -1 | Indices of all atoms in **a** | 102 | | | Index into | **a'** (list), **b** (any) | -2, -1 | Item of **b** at (multidimensional) index **a** | 103 | | `?` | Random | **a** (atom) | -1 | Random number between **0** and **a** | If **a = 0**, random float between **0** and **1** | 104 | | | | **a** (list) | -1 | Random permutation of **a** | 105 | | | | **a** (atom), **b** (atom) | 1, -1 | **a** random elements between **0** and **b** in order | 106 | | | | **a** (atom), **b** (list) | 1, -1 | **a** random elements of **b** in order | 107 | | | | **a** (list), **b** (atom) | 1, -1 | Disjoint random subsequences of **range(b)**, lengths given by **a** | 108 | | | | **a** (list), **b** (list) | 1, -1 | Disjoint random subsequences of **b**, lengths given by **a** | 109 | 110 | ## Operators 111 | 112 | In this table, threaded arguments are mentioned separately in the description. 113 | 114 | | Symbol | Name | Operator args | Function args | Result | Notes | 115 | | :----: | ------------ | -------------------------- | ------------------------ | ------ | ----- | 116 | | `_` | Call | **f** (func) | **a** (any) | **f(a)** | 117 | | | | | **a** (any), **b** (any) | **f(a, b)** | 118 | | | | **f** (func), **y** (val) | **a (,b)** (any) | **f(y)** | 119 | | | | **x** (val), **g** (func) | **a (,b)** (any) | **g(x)** | 120 | | | | **f** (func), **g** (func) | **a** (any) | **f(a)** | 121 | | | | | **a** (any), **b** (any) | **f(a, b)** | 122 | | `~` | Constant | **x** (val) | **a (, b)** (any) | **x** | 123 | | | Flip | **f** (func) | **a** (any) | **f(a)** | 124 | | | | | **a** (any), **b** (any) | **f(b, a)** | 125 | | | Constant | **x** (val), **y** (val) | **a (,b)** (any) | **[x, y]** | 126 | | | Curry | **f** (func), **y** (val) | **a (,b)** (any) | **f(a, y)** | 127 | | | | **x** (val), **g** (func) | **a** (any) | **g(x, a)** | 128 | | | | | **a** (any), **b** (any) | **g(x, b)** | 129 | | | Flip compose | **f** (func), **g** (func) | **a** (any) | **g(f(a))** | 130 | | | Post-compose | | **a** (any), **b** (any) | **f(g(a), g(b))** | 131 | | `&` | Swap arity | **f** (func) | **a** (any) | **f(a, a)** | 132 | | | | | **a** (any), **b** (any) | **f(b)** | 133 | | | Bi-compose | **f** (func), **y** (val) | **a** (any) | **f(f(y, a), y)** | 134 | | | | | **a** (any), **b** (any) | **b → f(f(y, b), y)** iterated **a** times | 135 | | | | **x** (val), **g** (func) | **a** (any) | **g(x, g(a, x))** | 136 | | | | | **a** (any), **b** (any) | **b → g(x, g(b, x))** iterated **a** times | 137 | | | Compose | **f** (func), **g** (func) | **a** (any) | **f(g(a))** | 138 | | | Pre-compose | | **a** (any), **b** (any) | **f(g(a, b))** | 139 | | `(` | Left hook | **f** (func) | **a** (any) | **[f(a), a]** | 140 | | | | | **a** (any), **b** (any) | **[f(a), b]** | 141 | | | | **f** (func), **g** (func) | **a** (any) | **g(f(a), a)** | 142 | | | | | **a** (any), **b** (any) | **g(f(a), b)** | 143 | | `)` | Right hook | **f** (func) | **a** (any) | **[a, f(a)]** | 144 | | | | | **a** (any), **b** (any) | **[a, f(b)]** | 145 | | | | **f** (func), **g** (func) | **a** (any) | **f(a, g(a))** | 146 | | | | | **a** (any), **b** (any) | **f(a, g(b))** | 147 | | `[` | Left fork | **f** (func) | **a** (any) | **[f(a), a]** | 148 | | | | | **a** (any), **b** (any) | **[f(a, b), b]** | 149 | | | | **f** (func), **g** (func) | **a** (any) | **g(f(a), a)** | 150 | | | | | **a** (any), **b** (any) | **g(f(a, b), b)** | 151 | | `]` | Right fork | **f** (func) | **a** (any) | **[a, f(a)]** | 152 | | | | | **a** (any), **b** (any) | **[a, f(a, b)]** | 153 | | | | **f** (func), **g** (func) | **a** (any) | **f(a, g(a))** | 154 | | | | | **a** (any), **b** (any) | **f(a, g(a, b))** | 155 | | `` ` ``| Thread | **x** (val) | **a (,b)** (any) | **~(x)** threaded to level **0** | 156 | | | | **f** (func) | **a (,b)** (any) | **f** threaded to level **0** | 157 | | | | **x** (val), **y** (val) | **a (,b)** (any) | **~(x)** threaded to level(s) **y** | **y** is reshaped to shape **[3]** | 158 | | | | **f** (func), **y** (val) | **a (,b)** (any) | **f** threaded to level(s) **y** | **y** is reshaped to shape **[3]** | 159 | | | | **x** (val), **g** (func) | **a (,b)** (any) | **g** threaded to level(s) **x** | **x** is reshaped to shape **[3]** | 160 | | | | **f** (func), **g** (func) | **a** (any) | **g(a)**, with **g** threaded to level(s) **f(a)** | **f(a)** is reshaped to shape **[3]** | 161 | | | | | **a** (any), **b** (any) | **g(a, b)**, with **g** threaded to level(s) **f(a, b)** | **f(a, b)** is reshaped to shape **[3]** | 162 | | `L` | Levels | **x** (val) | **a** (any) | Items of **a** of height **x** or greater | 163 | | | | | **a** (any), **b** (any) | Items of **a** and **b** of height **x** or greater, paired together | 164 | | | | **f** (func) | **a** (any) | **f** applied to atoms of **a** | 165 | | | | | **a** (any), **b** (any) | **f** applied to atom-pairs of **a** and **b** | 166 | | | | **x** (val), **y** (val) | **a** (any) | **~(y)** applied to items of **a** of height **x** or greater | 167 | | | | | **a** (any), **b** (any) | **~(y)** applied to item-pairs of **a** and **b** of height **x** or greater | 168 | | | | **f** (func), **y** (val) | **a** (any) | **~(y)** applied to items of **a** of height **f(a)** or greater | 169 | | | | | **a** (any), **b** (any) | **~(y)** applied to item-pairs of **a** and **b** of height **f(a, b)** or greater | 170 | | | | **x** (val), **g** (func) | **a** (any) | **g** applied to items of **a** of height **x** or greater | 171 | | | | | **a** (any), **b** (any) | **g** applied to item-pairs of **a** and **b** of height **x** or greater | 172 | | | | **f** (func), **g** (func) | **a** (any) | **g** applied to items of **a** of height **f(a)** or greater | 173 | | | | | **a** (any), **b** (any) | **g** applied to item-pairs of **a** and **b** of height **f(a, b)** or greater | 174 | | `/` | Join | **x** (val) | **a** (any) | Join **a** **x** times | **x** threaded to level 0 | 175 | | | | | **a** (any), **b** (any) | Insert copies of **a** between items of **b**, and join **x** times | **x** threaded to level 0 | 176 | | | Fold | **f** (func) | **a** (any) | Fold **f** over **a** from the left | 177 | | | | | **a** (any), **b** (any) | Fold **f** over **b** from the left with initial value **a** | 178 | | | If | **x** (val), **y** (val) | **a** (any) | **x** if **a** is truthy, else **y** | 179 | | | | | **a** (any), **b** (any) | **[x, b]** if **a** is truthy, else **[y, b]** | 180 | | | | **f** (func), **y** (val) | **a** (any) | **f(y)** if **a** is truthy, else **y** | 181 | | | | | **a** (any), **b** (any) | **f(b)** if **a** is truthy, else **y** | 182 | | | | **x** (val), **g** (func) | **a** (any) | **g(a)** if **f** is truthy, else **a** | 183 | | | | | **a** (any), **b** (any) | **g(b)** if **f** is truthy, else **g(a)** | 184 | | | | **f** (func), **g** (func) | **a** (any) | **g(a)** if **f(a)** is truthy, else **a** | 185 | | | | | **a** (any), **b** (any) | **f(b)** if **a** is truthy, else **g(b)** | 186 | | `\` | Substrings | **x** (val) | **a** (any) | Substrings of **a** of length **x** | **x < 0** gives non-overapping substrings; **x** threaded to level 0 | 187 | | | Select | | **a** (any), **b** (any) | Select item from **a** if **x** is even, from **b** if odd | Threaded to level -2 for **a** and **b**, level 0 for **x** | 188 | | | Prefixes | **f** (func) | **a** (any) | **f(p)** for every prefix **p** of **a** | 189 | | | Substrings | | **a** (any), **b** (any) | **f(s)** for every length-**a** substring of **b** | **a** threaded to level 0 | 190 | | | Iterate | **f** (func), **y** (val) | **a** (any) | Iterate **f** on **a** **y** times | **y** threaded to level 0 | 191 | | | | | **a** (any), **b** (any) | Iterate **~(a, f)** on **b** **y** times | **y** threaded to level 0 | 192 | | | | **x** (val), **g** (func) | **a** (any) | Iterate **g** on **a** until **x** occurs, return each step | 193 | | | | | **a** (any), **b** (any) | Iterate **~(a, g)** on **b** until **x** occurs, return each step | 194 | | | | **f** (func), **g** (func) | **a** (any) | Iterate **f** on **a** until **g(a, f(a))** is truthy | 195 | | | | | **a** (any), **b** (any) | Iterate **~(a, f)** on **b** until **g(b, f(a, b))** is truthy | 196 | | `O` | Product | **x** (val) | **a** (any) | Cartesian product of **a** on level **x** | **x** threaded to level 0 | 197 | | | | | **a** (any), **b** (any) | Cartesian product of choosing level-**x** elements from **a** or **b** | **x** threaded to level 0 | 198 | | | Each | **f** (func) | **a** (any) | **f** threaded to level -2 | 199 | | | Table | | **a** (any), **b** (any) | **f** threaded to levels -2 and -1 | 200 | | `Z` | Replace at | **x** (val), **y** (val) | **a** (any) | **a** with elements at indices **x** replaced by corresponding items of **y** | 201 | | | | | **a** (any), **b** (any) | **b** with elements at indices **x** replaced by corresponding items of **y** | 202 | | | | **f** (func), **y** (val) | **a** (any) | **a** with elements at indices **f(a)** replaced by corresponding items of **y** | 203 | | | | | **a** (any), **b** (any) | **b** with elements at indices **f(a, b)** replaced by corresponding items of **y** | 204 | | | | **x** (val), **g** (func) | **a** (any) | **a** with elements at indices **x** replaced by corresponding items of **g(a)** | 205 | | | | | **a** (any), **b** (any) | **b** with elements at indices **x** replaced by corresponding items of **g(a, b)** | 206 | | | | **f** (func), **g** (func) | **a** (any) | **a** with elements at indices **f(a)** replaced by corresponding items of **g(a)** | 207 | | | | | **a** (any), **b** (any) | **b** with elements at indices **f(a, b)** replaced by corresponding items of **g(a, b)** | 208 | -------------------------------------------------------------------------------- /tutorial.md: -------------------------------------------------------------------------------- 1 | 2 | # Jellyfish tutorial 3 | 4 | This is a tutorial for the esoteric programming language Jellyfish. 5 | It will cover those parts of the language that are not very likely to change radically in the future. 6 | To try out the examples, you can use either the command line interpreter, or the [online interpreter](http://jellyfish.tryitonline.net/) provided by Dennis. 7 | A function listing can be found [here](https://github.com/iatorm/jellyfish/blob/master/stdlib.md). 8 | 9 | ## First steps 10 | 11 | Let's try our hand with the classic "Hello world" program: 12 | 13 | P "Hello, world!" 14 | 15 | That's it: `P` is the Jellyfish function that prints its argument to STDOUT, and `"Hello, world!"` is a string literal. 16 | 17 | Once you have this program up and running, let's analyze it a little. 18 | In Jellyfish, most functions have a unary (one-argument) form and a binary (two-argument) form. 19 | The print function `P` only has a unary form, but we'll encounter binary functions soon enough. 20 | Jellyfish programs are parsed as grids. 21 | In the above program, the function `P` lies at the coordinate **(0,0)**, while the string literal lies at the position of its left quote, or **(2,0)**. 22 | A function will look for two arguments: one to the south, and one to the east, ignoring all empty coordinates in between. 23 | If two arguments are found, the binary form of the function is used; if just one, the unary form. 24 | In this case, the function `P` doesn't find an argument to the south, but finds the string literal in the east, and thus applies its unary form to that argument. 25 | 26 | Now, let's try something a bit more complex: a program that adds 1 and 2, and prints the result: 27 | 28 | P + 1 29 | 2 30 | 31 | The function `+` in its binary form performs addition, in this case on the numeric literals `1` and `2`. 32 | The function `P` finds the function `+`, and uses its result as its own argument. 33 | The binary arithmetic functions `+-*%` all behave as expected (`%` is division), although the argument order may be surprising. 34 | For example, `-` subtracts its south argument from its east argument. 35 | 36 | ## Input 37 | 38 | The easiest way of getting user input in Jellyfish are the input literals `i` and `I`. 39 | At the beginning of execution, every `i` in the source code is replaced by an evaluated value taken from STDIN, and every `I` is replaced by an unevaluated string taken from STDIN. 40 | For example, here's a program for incrementing a given number: 41 | 42 | P + i 43 | 1 44 | 45 | Here's a program for adding two numbers, given on different lines: 46 | 47 | P + i 48 | i 49 | 50 | The replacement is done in the normal English reading order, so the `i` on the first line is the first input, and the `i` on the second line is the second input. 51 | 52 | The binary function `,` performs list and string concatenation, so the following program takes a name from STDIN and prints a greeting: 53 | 54 | P , , "!" 55 | "Hello, " I 56 | 57 | There are two `,`s here, one for appending a `!` to the name, and one for prepending the `Hello`. 58 | 59 | ## Lists 60 | 61 | There are only three datatypes in Jellyfish: numbers, characters (which are really a type of number), and lists. 62 | Lists can be nested arbitrarily, and they can hold any type of data. 63 | The syntax for lists, as they are taken from STDIN, is `[0 1 2 "hello" [2 4 'x']]`: a list begins with `[`, contains items that are separated by spaces, and ends with `]`. 64 | The single-quoted `'x'` is a character, and the string syntax `"hello"` is really just a shorthand for a list of characters `['h' 'e' 'l' 'l' 'o']`. 65 | Note that there is no syntax for list literals in the source code, so you'll have to build lists manually using functions like `,` (flatten/concatenate) and `;` (singleton/pair). 66 | For example, the list `[0 3 6 4]` would be represented by 67 | 68 | , , , 4 69 | 0 3 6 70 | 71 | The function `P` prints lists in a "matrix format". 72 | This means that a nested list of numbers is printed as a beautiful grid: the program 73 | 74 | P i 75 | 76 | with input 77 | 78 | [[10 2 7] [2 45 -4] [0 256 0]] 79 | 80 | results in 81 | 82 | 10 2 7 83 | 2 45 -4 84 | 0 256 0 85 | 86 | However, if the lists are not as nicely nested, `P` will throw an error. 87 | In that case, you should use the `p` function, which prints its argument in the input format. 88 | 89 | There are a number of functions in Jellyfish that deal with lists. 90 | Let's look at `#` (length/repeat) first. 91 | The unary form simply computes the length of a given list. 92 | For example, the program 93 | 94 | P # "Hello" 95 | 96 | will print `5`. 97 | The binary form takes two lists, and repeats each element of the east argument a number of times given by its south argument. 98 | For example, the program 99 | 100 | P # "Hello" 101 | , , , , 1 102 | 2 1 0 0 103 | 104 | will print `HHeo`. 105 | The last two rows construct the list `[2 1 0 0 1]`, which is paired with `"Hello"`, and the `H` is repeated twice, `e` and `o` once, and both `l`s zero times. 106 | The binary form of `#` is mostly used as a "filter", where the south argument is a 0-1 list representing some property of the elements, and all elements without the property are removed. 107 | 108 | Some other list-manipulating functions are `R` (reverse/rotate), `o` (order) and `r` (range). 109 | You can find their descriptions in the reference file. 110 | 111 | ## Threading 112 | 113 | *TODO* 114 | 115 | ## Redirection commands 116 | 117 | *TODO* 118 | 119 | ## Operators 120 | 121 | *TODO* -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | from itertools import cycle 2 | from enum import Enum 3 | 4 | class AtomType(Enum): 5 | num = 0 6 | char = 1 7 | 8 | class Atom: 9 | def __init__(self, type, value): 10 | self.type = type 11 | self.value = value 12 | 13 | def __int__(self): 14 | return int(self.value) 15 | 16 | def __eq__(self, other): 17 | if isinstance(other, Atom): 18 | return self.value == other.value 19 | else: 20 | return False 21 | 22 | def __ne__(self, other): 23 | if isinstance(other, Atom): 24 | return self.value != other.value 25 | else: 26 | return True 27 | 28 | def __lt__(self, other): 29 | if isinstance(other, Atom): 30 | return self.value < other.value 31 | else: 32 | return True 33 | 34 | def __le__(self, other): 35 | if isinstance(other, Atom): 36 | return self.value <= other.value 37 | else: 38 | return True 39 | 40 | def __gt__(self, other): 41 | if isinstance(other, Atom): 42 | return self.value > other.value 43 | else: 44 | return False 45 | 46 | def __ge__(self, other): 47 | if isinstance(other, Atom): 48 | return self.value >= other.value 49 | else: 50 | return False 51 | 52 | def __repr__(self): 53 | if self.type == AtomType.num: 54 | return "<{}>".format(self.value) 55 | else: 56 | return "<{}>".format(repr(chr(abs(int(self.value))))) 57 | 58 | def to_num_atom(d): 59 | return Atom(AtomType.num, d) 60 | 61 | def to_char_atom(c): 62 | return Atom(AtomType.char, abs(int(ord(c)))) 63 | 64 | def is_value(item): 65 | return not callable(item) 66 | 67 | def is_atom(value): 68 | return isinstance(value, Atom) 69 | 70 | def is_truthy(value): 71 | if is_atom(value): 72 | return value.value != 0 73 | else: 74 | return value != [] 75 | 76 | def full_copy(value): 77 | if is_atom(value): 78 | return value 79 | else: 80 | return [full_copy(item) for item in value] 81 | 82 | def uniques(array): 83 | out = [] 84 | for item in array: 85 | if item not in out: 86 | out.append(item) 87 | return out 88 | 89 | def prefixes(a): 90 | if is_atom(a): 91 | a = un_range(a) 92 | return [a[:i] for i in range(1, len(a)+1)] 93 | 94 | def infixes(a, n): 95 | if is_atom(a): 96 | a = un_range(a) 97 | if n > 0: 98 | return [a[i:i+n] for i in range(len(a)-n+1)] 99 | elif n < 0: 100 | n = -n 101 | return [a[i*n:(i+1)*n] for i in range((len(a)+n-1)//n)] 102 | else: 103 | return [a[i:i+k] for k in reversed(range(1,len(a)+1)) for i in range(len(a)-k+1)] 104 | 105 | def shape(value): 106 | if is_atom(value): 107 | return [] 108 | else: 109 | shapes = zip(*[shape(item) for item in value]) 110 | return [len(value)] + [min(x) for x in shapes] 111 | 112 | def flatten(value, max_height=0): 113 | if height(value) <= max_height: 114 | return [value] 115 | return [subitem for item in value for subitem in flatten(item, max_height)] 116 | 117 | def join_times(value, times=1): 118 | if is_atom(value) or times == 0: 119 | return value 120 | if times > 0: 121 | joined = [] 122 | for item in value: 123 | if is_atom(item): 124 | joined += [item] 125 | else: 126 | joined += item 127 | return join_times(joined, times-1) 128 | raise Exception("Can't join a negative number of times.") 129 | 130 | def intersperse(value, array): 131 | if is_atom(array): 132 | return array 133 | res = array[:1] 134 | for item in array[1:]: 135 | res += [value, item] 136 | return res 137 | 138 | def cartesian_product(array, level=-2): 139 | if is_atom(array) or level >= 0 and height(array) < level: 140 | yield array 141 | elif level == -1 or array and level == height(array): 142 | for item in array: 143 | yield item 144 | elif array: 145 | head, *rest = array 146 | for item in cartesian_product(head, level+1 if level < 0 else level): 147 | for word in cartesian_product(rest, level): 148 | yield [item] + word 149 | else: 150 | yield [] 151 | 152 | def grid(iterator, shape): 153 | if shape: 154 | return [grid(iterator, shape[1:]) for i in range(int(shape[0]))] 155 | return next(iterator) 156 | 157 | def reshape(value, shape): 158 | iterator = cycle(flatten(value)) 159 | return grid(iterator, shape) 160 | 161 | def rank(value): 162 | return len(shape(value)) 163 | 164 | def height(value): 165 | if is_atom(value): 166 | return 0 167 | if value: 168 | return 1 + max(height(item) for item in value) 169 | return 1 170 | 171 | def incneg(n): 172 | return n + (n<0) 173 | 174 | def thread_binary(f, height1, height2): 175 | def threaded_f(a, b, lev1=height1, lev2=height2): 176 | height_a, height_b = height(a), height(b) 177 | if height_a <= max(0, lev1) or lev1 == -1: 178 | if height_b <= max(0, lev2) or lev2 == -1: 179 | return f(a, b) 180 | else: 181 | return [threaded_f(a, y, -1, incneg(lev2)) 182 | for y in b] 183 | elif height_b <= max(0, lev2) or lev2 == -1: 184 | return [threaded_f(x, b, incneg(lev1), -1) 185 | for x in a] 186 | else: 187 | return [threaded_f(x, y, incneg(lev1), incneg(lev2)) 188 | for (x, y) in zip(a,b)] 189 | return threaded_f 190 | 191 | def thread_unary(f, height): 192 | return lambda a: thread_binary(lambda x, y: f(x), height, -1)(a, None) 193 | 194 | def un_range(x): 195 | if is_atom(x): 196 | end = int(x.value) 197 | if end >= 0: 198 | return [Atom(x.type, n) for n in range(end)] 199 | else: 200 | return [Atom(x.type, -n) for n in reversed(range(-end))] 201 | elif x: 202 | return [[n] + w 203 | for n in un_range(x[0]) 204 | for w in un_range(x[1:])] 205 | else: 206 | return [[]] 207 | 208 | def bin_range(x): 209 | if not x: 210 | return [[]] 211 | elif height(x) == 1: 212 | (lo_type, lo), (hi_type, hi) = map(lambda y: (y.type, int(y.value)), x) 213 | if lo <= hi: 214 | return [Atom(lo_type, y) for y in range(lo, hi)] 215 | else: 216 | return [Atom(lo_type, y) for y in reversed(range(hi, lo))] 217 | else: 218 | return [[y] + w 219 | for y in bin_range(x[0]) 220 | for w in bin_range(x[1:])] 221 | 222 | def iterate(f, a, n): 223 | for i in range(n): 224 | a = f(a) 225 | return a 226 | 227 | def iterate_until(f, a, g): 228 | while True: 229 | b = f(a) 230 | if is_truthy(g(a, b)): 231 | return b 232 | a = b 233 | 234 | def acc_iterate_until(f, a, g): 235 | out = [a] 236 | while True: 237 | b = f(a) 238 | out.append(b) 239 | if is_truthy(g(a, b)): 240 | return out 241 | a = b 242 | -------------------------------------------------------------------------------- /vocab.py: -------------------------------------------------------------------------------- 1 | from utils import * 2 | from print_parse import * 3 | import sys 4 | import math 5 | import random 6 | import itertools 7 | from enum import Enum 8 | 9 | func_defs = {} 10 | oper_defs = {} 11 | 12 | class Roles(Enum): 13 | function = 0 14 | operator = 1 15 | 16 | def get_defs(role): 17 | if role == Roles.function: 18 | return func_defs 19 | else: 20 | return oper_defs 21 | 22 | def defun(char, arity, role): 23 | def define_cmd(func): 24 | defs = get_defs(role) 25 | if char in defs: 26 | impl = list(defs[char]) 27 | else: 28 | impl = [None, None] 29 | impl[arity-1] = func 30 | defs[char] = tuple(impl) 31 | return func 32 | return define_cmd 33 | 34 | defun_unary = lambda char: defun(char, 1, Roles.function) 35 | defun_binary = lambda char: defun(char, 2, Roles.function) 36 | defop_unary = lambda char: defun(char, 1, Roles.operator) 37 | defop_binary = lambda char: defun(char, 2, Roles.operator) 38 | 39 | def mathy_unary(f): 40 | return lambda x: Atom(x.type, f(x.value)) 41 | 42 | def mathy_binary(f): 43 | return lambda x, y: Atom(x.type, f(x.value, y.value)) 44 | 45 | threaded_unary = lambda rank: lambda f: thread_unary(f, rank) 46 | threaded_binary = lambda rank1, rank2: lambda f: thread_binary(f, rank1, rank2) 47 | 48 | @defun_unary('{') 49 | def func_left_id(a): 50 | return a 51 | 52 | @defun_binary('{') 53 | def func_left(a, b): 54 | return a 55 | 56 | @defun_unary('}') 57 | def func_right_id(a): 58 | return a 59 | 60 | @defun_binary('}') 61 | def func_right(a, b): 62 | return b 63 | 64 | @defun_unary('j') 65 | def func_input(a): 66 | return parse_value(input())[0] 67 | 68 | @defun_binary('j') 69 | @threaded_binary(0, -1) 70 | def func_binary_input(a, b): 71 | if a.value > 0: 72 | string = "".join(chr(abs(int(item.value))) for item in flatten(b)) 73 | return parse_value(string)[0] 74 | else: 75 | return [to_char_atom(c) for c in prettyprint(b)] 76 | 77 | @defun_unary('J') 78 | def func_raw_input(a): 79 | return [to_char_atom(c) for c in input()] 80 | 81 | @defun_binary('J') 82 | @threaded_binary(-1, 0) 83 | def func_binary_raw_input(a, b): 84 | return [to_char_atom(c) for c in sys.stdin.read(int(b))] 85 | 86 | @defun_unary('p') 87 | def func_print(a): 88 | print(prettyprint(a)) 89 | return a 90 | 91 | @defun_binary('p') 92 | def func_binary_print(a, b): 93 | raise Exception("Binary 'p' not implemented.") 94 | 95 | @defun_unary('P') 96 | def func_matrix_print(a): 97 | print(matrix_print(a)) 98 | return a 99 | 100 | @defun_binary('P') 101 | def func_binary_matrix_print(a, b): 102 | raise Exception("Binary 'P' not implemented.") 103 | 104 | @defun_unary('+') 105 | @threaded_unary(0) 106 | @mathy_unary 107 | def func_abs(a): return abs(a) 108 | 109 | @defun_binary('+') 110 | @threaded_binary(0, 0) 111 | @mathy_binary 112 | def func_add(a, b): return a + b 113 | 114 | @defun_unary('-') 115 | @threaded_unary(0) 116 | @mathy_unary 117 | def func_negate(a): return -a 118 | 119 | @defun_binary('-') 120 | @threaded_binary(0, 0) 121 | @mathy_binary 122 | def func_subtract(a, b): return b - a 123 | 124 | @defun_unary('*') 125 | @threaded_unary(0) 126 | @mathy_unary 127 | def func_signum(a): return (a>0) - (a<0) 128 | 129 | @defun_binary('*') 130 | @threaded_binary(0, 0) 131 | @mathy_binary 132 | def func_multiply(a, b): return a * b 133 | 134 | @defun_unary('%') 135 | @threaded_unary(0) 136 | @mathy_unary 137 | def func_reciprocal(a): 138 | if a == 0: 139 | return 0 # TODO: give error? 140 | return 1/a 141 | 142 | @defun_binary('%') 143 | @threaded_binary(0, 0) 144 | @mathy_binary 145 | def func_divide(a, b): 146 | if a != 0 != b: 147 | if type(a) == int == type(b) and b % a == 0: 148 | return b // a 149 | return b / a 150 | return 0 # TODO: give errors? 151 | 152 | @defun_unary('|') 153 | @threaded_unary(0) 154 | @mathy_unary 155 | def func_round(a): return math.floor(a + 0.5) 156 | 157 | @defun_binary('|') 158 | @threaded_binary(0, 0) 159 | @mathy_binary 160 | def func_modulus(a, b): 161 | if a != 0 != b: 162 | return b % a 163 | return 0 # TODO: give errors? 164 | 165 | @defun_unary('m') 166 | @threaded_unary(0) 167 | @mathy_unary 168 | def func_floor(a): return math.floor(a) 169 | 170 | @defun_binary('m') 171 | def func_min(a, b): return min(a, b) 172 | 173 | @defun_unary('M') 174 | @threaded_unary(0) 175 | @mathy_unary 176 | def func_ceil(a): return math.ceil(a) 177 | 178 | @defun_binary('M') 179 | def func_max(a, b): return max(a, b) 180 | 181 | @defun_unary('x') 182 | @threaded_unary(0) 183 | def func_factorize(a): 184 | n = int(a) 185 | if n == 0: 186 | return [to_num_atom(0)] 187 | if n > 0: 188 | fact = [] 189 | else: 190 | n = -n 191 | fact = [to_num_atom(-1)] 192 | div = 2 193 | while n > 1: 194 | if n % div == 0: 195 | n //= div 196 | fact.append(to_num_atom(div)) 197 | else: 198 | div += 1 199 | return fact 200 | 201 | 202 | @defun_binary('x') 203 | @threaded_binary(0, 0) 204 | @mathy_binary 205 | def func_xor(a, b): return a ^ b 206 | 207 | @defun_unary('b') 208 | def func_base2(a): 209 | return func_base(to_num_atom(2), a) 210 | 211 | @defun_binary('b') 212 | @threaded_binary(1, 0) 213 | def func_base(a, b): 214 | if is_atom(a): 215 | base = a.value 216 | num = b.value 217 | digits = [] 218 | while abs(num) >= abs(base): 219 | digits = [to_num_atom(num % base)] + digits 220 | if type(num) is int and type(base) is int: 221 | num = num // base 222 | else: 223 | num /= base 224 | return [to_num_atom(num)] + digits 225 | else: 226 | num = b.value 227 | digits = [] 228 | for item in reversed(a): 229 | base = item.value 230 | digits = [to_num_atom(num % base)] + digits 231 | if type(num) is int and type(base) is int: 232 | num = num // base 233 | else: 234 | num /= base 235 | return digits 236 | 237 | @defun_unary('d') 238 | def func_antibase2(a): 239 | return func_antibase(to_num_atom(2), a) 240 | 241 | @defun_binary('d') 242 | @threaded_binary(1, 1) 243 | def func_antibase(a, b): 244 | if is_atom(b): 245 | return b 246 | if is_atom(a): 247 | a = [a]*len(b) 248 | total = 0 249 | old_base = 1 250 | for base, n in reversed(list(zip(a, b))): 251 | total += n.value * old_base 252 | old_base *= base.value 253 | return to_num_atom(total) 254 | 255 | 256 | @defun_unary('=') 257 | def func_unary_eq(a): 258 | raise Exception("Unary '=' not implemented.") 259 | 260 | @defun_binary('=') 261 | def func_equals(a, b): 262 | if is_atom(a) != is_atom(b): 263 | return to_num_atom(0) 264 | else: 265 | return to_num_atom(int(a == b)) 266 | 267 | @defun_unary('<') 268 | def func_head_dec(a): 269 | if is_atom(a): 270 | return Atom(a.type, a.value-1) 271 | else: 272 | return a[0] 273 | 274 | @defun_binary('<') 275 | def func_less_than(a, b): 276 | if is_atom(a): 277 | if is_atom(b): 278 | return to_num_atom(int(a < b)) 279 | else: 280 | return to_num_atom(1) 281 | elif is_atom(b): 282 | return to_num_atom(0) 283 | else: 284 | return to_num_atom(int(a < b)) 285 | 286 | @defun_unary('>') 287 | def func_tail_inc(a): 288 | if is_atom(a): 289 | return Atom(a.type, a.value+1) 290 | else: 291 | return a[1:] 292 | 293 | @defun_binary('>') 294 | def func_greater_than(a, b): 295 | if is_atom(a): 296 | if is_atom(b): 297 | return to_num_atom(int(a > b)) 298 | else: 299 | return to_num_atom(0) 300 | elif is_atom(b): 301 | return to_num_atom(1) 302 | else: 303 | return to_num_atom(int(a > b)) 304 | 305 | @defun_unary('^') 306 | def func_init_sqr(a): 307 | if is_atom(a): 308 | return Atom(a.type, a.value * a.value) 309 | else: 310 | return a[:-1] 311 | 312 | @defun_binary('^') 313 | @threaded_binary(0, -1) 314 | def func_take_pow(a, b): 315 | n = a.value 316 | if is_atom(b): 317 | return Atom(a.type, b.value ** n) 318 | elif n >= 0: 319 | return b[:int(n)] 320 | else: 321 | return b[int(n):] 322 | 323 | @defun_unary('v') 324 | def func_last_sqrt(a): 325 | if is_atom(a): 326 | return Atom(a.type, math.sqrt(a.value)) 327 | else: 328 | return a[-1] 329 | 330 | @defun_binary('v') 331 | @threaded_binary(0, -1) 332 | def func_drop_root(a, b): 333 | n = a.value 334 | if is_atom(b): 335 | return Atom(a.type, b.value ** (1/n)) 336 | elif n >= 0: 337 | return b[int(n):] 338 | else: 339 | return b[:int(n)] 340 | 341 | @defun_unary('!') 342 | def func_permutations(a): 343 | if is_atom(a): 344 | return Atom(a.type, math.factorial(int(a.value))) 345 | else: 346 | return [list(p) for p in itertools.permutations(a)] 347 | 348 | @defun_binary('!') 349 | @threaded_binary(0, -1) 350 | def func_binary_permutations(a, b): 351 | if is_atom(b): 352 | return Atom(a.type, math.factorial(int(b.value)) // math.factorial(int(b.value - a.value))) 353 | else: 354 | return [list(p) for p in itertools.permutations(b, int(a))] 355 | 356 | @defun_unary('c') 357 | @threaded_unary(0) 358 | def func_to_char(a): return Atom(AtomType.char, a.value) 359 | 360 | @defun_binary('c') 361 | def func_elem(a, b): 362 | if is_atom(b): 363 | b = [b] 364 | return to_num_atom(int(a in b)) 365 | 366 | @defun_unary('C') 367 | def func_subsequences(a): 368 | if is_atom(a): 369 | return Atom(a.type, 2**a.value) 370 | else: 371 | return [list(s) 372 | for i in range(len(a)+1) 373 | for s in itertools.combinations(a, i)] 374 | 375 | @defun_binary('C') 376 | @threaded_binary(0, -1) 377 | def func_combinations(a, b): 378 | x = a.value 379 | if is_atom(b): 380 | y = b.value 381 | if y < x: 382 | return Atom(a.type, 0) 383 | z = math.factorial(int(y)) // math.factorial(int(x)) // math.factorial(int(y - x)) 384 | return Atom(a.type, z) 385 | elif b: 386 | x = x % len(b) 387 | return [list(c) for c in itertools.combinations(b, x)] 388 | else: 389 | return [] 390 | 391 | @defun_unary('n') 392 | @threaded_unary(0) 393 | def func_to_num(a): return Atom(AtomType.num, a.value) 394 | 395 | @defun_binary('n') 396 | def func_intersection(a, b): 397 | if is_atom(a): 398 | a = [a] 399 | if is_atom(b): 400 | b = [b] 401 | return [x for x in a if x in b] 402 | 403 | @defun_unary('u') 404 | def func_uniques(a): 405 | if is_atom(a): 406 | return [a] 407 | else: 408 | return uniques(a) 409 | 410 | @defun_binary('u') 411 | def func_union(a, b): 412 | if is_atom(a): 413 | a = [a] 414 | if is_atom(b): 415 | b = [b] 416 | return a + [x for x in uniques(b) if x not in a] 417 | 418 | @defun_unary('N') 419 | def func_not(a): 420 | if is_truthy(a): 421 | return to_num_atom(0) 422 | else: 423 | return to_num_atom(1) 424 | 425 | @defun_binary('N') 426 | def func_without(a, b): 427 | if is_atom(a): 428 | a = [a] 429 | if is_atom(b): 430 | b = [b] 431 | return [x for x in a if x not in b] 432 | 433 | @defun_unary('#') 434 | def func_len(a): 435 | if is_atom(a): 436 | return to_num_atom(len(func_base(to_num_atom(10), a))) 437 | else: 438 | return to_num_atom(len(a)) 439 | 440 | @defun_binary('#') 441 | @threaded_binary(1, -1) 442 | def func_repeat(a, b): 443 | if is_atom(b): 444 | b = [b] 445 | if is_atom(a): 446 | a = [a]*len(b) 447 | else: 448 | return [y for (x,y) in zip(a,b) for _ in range(int(x))] 449 | 450 | @defun_unary('R') 451 | def func_reverse(a): 452 | if is_atom(a): 453 | return a 454 | else: 455 | return list(reversed(a)) 456 | 457 | @defun_binary('R') 458 | @threaded_binary(0, -1) 459 | def func_rotate(a, b): 460 | if is_atom(b) or not b: 461 | return b 462 | elif b: 463 | a = int(a) % len(b) 464 | return b[a:] + b[:a] 465 | 466 | @defun_unary('k') 467 | @threaded_unary(1) 468 | def func_mask_to_indices(a): 469 | if is_atom(a): 470 | return [to_num_atom(0)]*int(a.value) 471 | else: 472 | return [to_num_atom(i) for (i, x) in enumerate(a) for _ in range(int(x.value))] 473 | 474 | @defun_binary('k') 475 | def func_binary_k(a, b): 476 | raise Error("Binary 'k' not implemented.") 477 | 478 | @defun_unary('K') 479 | @threaded_unary(1) 480 | def func_indices_to_mask(a): 481 | if is_atom(a): 482 | a = [a] 483 | a = [int(n.value) for n in a] 484 | return [to_num_atom(a.count(i)) for i in range(max(a) + 1)] 485 | 486 | @defun_binary('K') 487 | def func_binary_K(a, b): 488 | raise Error("Binary 'K' not implemented.") 489 | 490 | @defun_unary('o') 491 | def func_sort(a): 492 | if is_atom(a): 493 | a = [a] 494 | return list(sorted(a)) 495 | 496 | @defun_binary('o') 497 | def func_binary_sort(a, b): 498 | if is_atom(a) or not(a): 499 | a = [a] 500 | if is_atom(b): 501 | b = [b] 502 | a = (a*len(b))[:len(b)] 503 | return [x for (y, x) in sorted(zip(a, b))] 504 | 505 | @defun_unary('r') 506 | def func_unary_range(a): 507 | return un_range(a) 508 | 509 | @defun_binary('r') 510 | def func_binary_range(a, b): 511 | pairs = thread_binary(lambda x, y: [x, y], 0, 0)(a, b) 512 | return bin_range(pairs) 513 | 514 | @defun_unary(',') 515 | def func_flatten(a): 516 | return flatten(a) 517 | 518 | @defun_binary(',') 519 | def func_append(a, b): 520 | if is_atom(a): 521 | if is_atom(b): 522 | return [a, b] 523 | else: 524 | return [a] + b 525 | elif is_atom(b): 526 | return a + [b] 527 | else: 528 | return a + b 529 | 530 | @defun_unary(';') 531 | def func_singleton(a): 532 | return [a] 533 | 534 | @defun_binary(';') 535 | def func_pair(a, b): 536 | return [a, b] 537 | 538 | @defun_unary('$') 539 | def func_shape(a): 540 | return [to_num_atom(dim) for dim in shape(a)] 541 | 542 | @defun_binary('$') 543 | @threaded_binary(1, -1) 544 | def func_reshape(a, b): 545 | if is_atom(a): 546 | a = [a] 547 | return reshape(b, a) 548 | 549 | @defun_unary('@') 550 | def func_indices(a): 551 | if is_atom(a): 552 | return to_num_atom(0) 553 | res = [] 554 | for ind, item in enumerate(a): 555 | if is_atom(item): 556 | res.append([to_num_atom(ind)]) 557 | else: 558 | for subind in func_indices(item): 559 | res.append([to_num_atom(ind)] + subind) 560 | return res 561 | 562 | @defun_binary('@') 563 | @threaded_binary(-2, -1) 564 | def func_index(a, b): 565 | if is_atom(b): 566 | return b 567 | if is_atom(a): 568 | return b[int(a) % len(b)] 569 | for ind in flatten(a): 570 | b = b[int(ind) % len(b)] 571 | if is_atom(b): 572 | return b 573 | return b 574 | 575 | @defun_unary('?') 576 | def func_random_gen(a): 577 | if is_atom(a): 578 | x = a.value 579 | if type(x) == float: 580 | if x >= 0: 581 | res = random.uniform(0, x) 582 | else: 583 | res = random.uniform(x, 0) 584 | elif x == 0: 585 | res = random.random() 586 | elif x > 0: 587 | res = random.randrange(0, x) 588 | else: 589 | res = random.rangrange(x+1, 1) 590 | return Atom(a.type, res) 591 | else: 592 | res = a[:] 593 | random.shuffle(res) 594 | return res 595 | 596 | @defun_binary('?') 597 | @threaded_binary(1, -1) 598 | def func_random_choice(a, b): 599 | if is_atom(b): 600 | b = func_unary_range(b) 601 | if is_atom(a): 602 | chosen = sorted(random.sample(range(len(b)), min(len(b), int(a)))) 603 | return [b[i] for i in chosen] 604 | else: 605 | res = [] 606 | for item in a: 607 | chosen = sorted(random.sample(range(len(b)), min(len(b), int(item)))) 608 | res.append([b[i] for i in chosen]) 609 | b = [val for (i, val) in enumerate(b) if i not in chosen] 610 | return res 611 | 612 | def variadize(func, binary=None): 613 | if binary is None: 614 | unary, binary = func 615 | else: 616 | unary = func 617 | def variadic(a, b=None): 618 | if a is None: 619 | if b is None: 620 | return None 621 | else: 622 | return unary(b) 623 | elif b is None: 624 | return unary(a) 625 | else: 626 | return binary(a, b) 627 | return variadic 628 | 629 | @defop_unary('_') 630 | def oper_id(f): 631 | if is_value(f): 632 | raise Exception("Unary '_' on values not implemented.") 633 | return f 634 | 635 | @defop_binary('_') 636 | def oper_left(f, g): 637 | if is_value(f): 638 | if is_value(g): 639 | raise Exception("Binary '_' on values not implemented.") 640 | return oper_const_or_flip(g(f)) 641 | if is_value(g): 642 | return oper_const_or_flip(f(g)) 643 | return f 644 | 645 | @defop_unary('~') 646 | def oper_const_or_flip(f): 647 | if is_value(f): 648 | return variadize(lambda a: f, 649 | lambda a, b: f) 650 | return variadize(lambda a: f(a), 651 | lambda a, b: f(b, a)) 652 | 653 | @defop_binary('~') 654 | def oper_curry_or_precompose(f, g): 655 | if is_value(f): 656 | if is_value(g): 657 | return variadize(lambda a: [f, g], 658 | lambda a, b: [f, g]) 659 | return variadize(lambda a: g(f, a), 660 | lambda a, b: g(f, b)) 661 | elif is_value(g): 662 | return variadize(lambda a: f(a, g), 663 | lambda a, b: f(a, g)) 664 | return variadize(lambda a: g(f(a)), 665 | lambda a, b: f(g(a), g(b))) 666 | 667 | @defop_unary('&') 668 | def oper_swap_arity(f): 669 | return variadize(lambda a: f(a, a), 670 | lambda a, b: f(b)) 671 | 672 | @defop_binary('&') 673 | def oper_twosided_curry_or_postcompose(f, g): 674 | if is_value(f): 675 | if is_value(g): 676 | raise Exception("Binary '&' on values not implemented.") 677 | def two_sided(a): 678 | return g(f, g(a, f)) 679 | return variadize(two_sided, 680 | lambda a, b: iterate(two_sided, b, int(a))) 681 | elif is_value(g): 682 | def two_sided(a): 683 | return f(f(g, a), g) 684 | return variadize(two_sided, 685 | lambda a, b: iterate(two_sided, b, int(a))) 686 | return variadize(lambda a: f(g(a)), 687 | lambda a, b: f(g(a, b))) 688 | 689 | @defop_unary('(') 690 | def oper_left_unary_hook(f): 691 | return variadize(lambda a: [f(a), a], 692 | lambda a, b: [f(a), b]) 693 | 694 | @defop_binary('(') 695 | def oper_left_hook(f, g): 696 | return variadize(lambda a: g(f(a), a), 697 | lambda a, b: g(f(a), b)) 698 | 699 | @defop_unary(')') 700 | def oper_right_unary_hook(f): 701 | return variadize(lambda a: [a, f(a)], 702 | lambda a, b: [a, f(b)]) 703 | 704 | @defop_binary(')') 705 | def oper_right_hook(f, g): 706 | return variadize(lambda a: f(a, g(a)), 707 | lambda a, b: f(a, g(b))) 708 | 709 | @defop_unary('[') 710 | def oper_left_unary_fork(f): 711 | return variadize(lambda a: [f(a), a], 712 | lambda a, b: [f(a, b), b]) 713 | 714 | @defop_binary('[') 715 | def oper_left_fork(f, g): 716 | return variadize(lambda a: g(f(a), a), 717 | lambda a, b: g(f(a, b), b)) 718 | 719 | @defop_unary(']') 720 | def oper_right_unary_fork(f): 721 | return variadize(lambda a: [a, f(a)], 722 | lambda a, b: [a, f(a, b)]) 723 | 724 | @defop_binary(']') 725 | def oper_right_fork(f, g): 726 | return variadize(lambda a: f(a, g(a)), 727 | lambda a, b: f(a, g(a, b))) 728 | 729 | @defop_unary('`') 730 | def oper_unary_thread(f): 731 | if is_value(f): 732 | return variadize(thread_unary(lambda a: f, 0), 733 | thread_binary(lambda a, b: f, 0, 0)) 734 | return variadize(thread_unary(f, 0), 735 | thread_binary(f, 0, 0)) 736 | 737 | @defop_binary('`') 738 | def oper_binary_thread(f, g): 739 | if is_value(f): 740 | if is_value(g): 741 | sole, right, left = reshape(g, [3]) 742 | return variadize(thread_unary(lambda a: f, int(sole)), 743 | thread_binary(lambda a, b: f, int(left), int(right))) 744 | sole, right, left = reshape(f, [3]) 745 | return variadize(thread_unary(g, int(sole)), 746 | thread_binary(g, int(left), int(right))) 747 | elif is_value(g): 748 | sole, right, left = reshape(g, [3]) 749 | return variadize(thread_unary(f, int(sole)), 750 | thread_binary(f, int(left), int(right))) 751 | def dynamic_thread_unary(a): 752 | level = reshape(f(a), []) 753 | return thread_unary(g, int(level))(a) 754 | def dynamic_thread_binary(a, b): 755 | _, right, left = reshape(f(a, b), [3]) 756 | return thread_binary(g, int(left), int(right))(a, b) 757 | return variadize(dynamic_thread_unary, dynamic_thread_binary) 758 | 759 | @defop_unary('L') 760 | def oper_unary_levels(f): 761 | if is_value(f): 762 | return oper_binary_levels(f, variadize(lambda a: a, 763 | lambda a, b: [a, b])) 764 | return oper_binary_levels(0, f) 765 | 766 | @defop_binary('L') 767 | def oper_binary_levels(f, g): 768 | if is_value(g): 769 | return oper_binary_levels(f, oper_const_or_flip(g)) 770 | if is_value(f): 771 | def level_map(a): 772 | def map_at(level): 773 | return [g(x) for x in flatten(a, int(level))] 774 | return thread_unary(map_at, 0)(f) 775 | def level_zip(a, b): 776 | def zip_at(level): 777 | _, right, left = reshape(level, [3]) 778 | return [g(x, y) for (x, y) in zip(flatten(a, int(left)), flatten(b, int(left)))] 779 | return thread_unary(zip_at, 1)(f) 780 | return variadize(level_map, level_zip) 781 | return variadize(lambda a: oper_binary_levels(f(a), g)(a), 782 | lambda a, b: oper_binary_levels(f(a, b), g)(a, b)) 783 | 784 | @defop_unary('/') 785 | def oper_join_or_fold(f): 786 | if is_value(f): 787 | return variadize(lambda a: 788 | thread_unary(lambda times: 789 | join_times(a, int(times)), 790 | 0)(f), 791 | lambda a, b: 792 | thread_unary(lambda times: 793 | join_times(intersperse(a, b), 794 | int(times)), 795 | 0)(f)) 796 | def folded(a): 797 | if is_atom(a): 798 | return a 799 | if not a: 800 | return to_num_atom(0) 801 | x = a[0] 802 | for y in a[1:]: 803 | x = f(x, y) 804 | return x 805 | def folded_init(a, b): 806 | if is_atom(b): 807 | return f(a, b) 808 | for y in b: 809 | a = f(a, y) 810 | return a 811 | return variadize(folded, folded_init) 812 | 813 | @defop_binary('/') 814 | def oper_choice(f, g): 815 | if is_value(f): 816 | if is_value(g): 817 | return variadize(lambda a: f if is_truthy(a) else g, 818 | lambda a, b: [f if is_truthy(a) else g, b]) 819 | return variadize(lambda a: g(a) if is_truthy(f) else a, 820 | lambda a, b: g(b) if is_truthy(f) else g(a)) 821 | elif is_value(g): 822 | return variadize(lambda a: f(g) if is_truthy(a) else g, 823 | lambda a, b: f(b) if is_truthy(a) else g) 824 | return variadize(lambda a: g(a) if is_truthy(f(a)) else a, 825 | lambda a, b: f(b) if is_truthy(a) else g(b)) 826 | 827 | @defop_unary('\\') 828 | def oper_substrings(f): 829 | if is_value(f): 830 | return variadize(lambda a: thread_unary(lambda b: infixes(a, int(b)), 0)(f), 831 | lambda a, b: thread_binary(lambda u, v: u if is_atom(u) else u[int(v.value) % 2], 1, 0) 832 | (thread_binary(lambda x, y: [x, y], -2, -2)(a, b), f)) 833 | return variadize(lambda a: [f(p) for p in prefixes(a)], 834 | thread_binary(lambda a, b: [f(p) for p in infixes(b, int(a))], 0, -1)) 835 | 836 | @defop_binary('\\') 837 | def oper_iterate(f, g): 838 | if is_value(f): 839 | if is_value(g): 840 | raise Exception("Binary '\\' on values not implemented.") 841 | return variadize(lambda a: acc_iterate_until(g, a, lambda x, y: func_equals(y, f)), 842 | lambda a, b: acc_iterate_until(lambda x: g(a, x), b, lambda x, y: func_equals(y, f))) 843 | if is_value(g): 844 | return variadize(lambda a: thread_unary(lambda n: iterate(f, a, int(n)), 0)(g), 845 | lambda a, b: thread_unary(lambda n: iterate(lambda x: f(a, x), b, int(n)), 0)(g)) 846 | return variadize(lambda a: iterate_until(f, a, g), 847 | lambda a, b: iterate_until(lambda x: f(a, x), b, g)) 848 | 849 | @defop_unary('O') 850 | def oper_prod_table(f): 851 | if is_value(f): 852 | return variadize(lambda a: thread_unary(lambda n: list(cartesian_product(a, int(n))), 0)(f), 853 | lambda a, b: thread_unary(lambda n: list(cartesian_product(thread_binary(lambda x, y: [x, y], int(n), int(n))(a, b), 854 | int(n)+1 if int(n) >= 0 else int(n))), 855 | 0)(f)) 856 | return oper_binary_thread(f, [to_num_atom(-2), to_num_atom(-2), to_num_atom(-1)]) 857 | 858 | @defop_binary('O') 859 | def oper_binary_O(f, g): 860 | raise Exception("Binary 'O' not implemented.") 861 | 862 | @defop_unary('Z') 863 | def oper_unary_Z(f): 864 | raise Exception("Unary 'Z' not implemented.") 865 | 866 | @defop_binary('Z') 867 | def oper_modify_indices(f, g): 868 | if is_value(f): 869 | return oper_modify_indices(oper_const_or_flip(f), g) 870 | if is_value(g): 871 | return oper_modify_indices(f, oper_const_or_flip(g)) 872 | def modify_indices(ind, mod, a): 873 | indices = thread_unary(lambda x: x if is_value(x) else flatten(x), -2)(ind(a)) 874 | new_items = mod(func_index(indices, a)) 875 | if is_atom(indices): 876 | indices = [indices] 877 | new_items = [new_items] 878 | if is_atom(new_items): 879 | new_items = [new_items]*len(indices) 880 | a = full_copy(a) 881 | for index, item in zip(indices, new_items): 882 | if is_atom(a): 883 | a = item 884 | continue 885 | focus = old_focus = a 886 | old_coord = 0 887 | if is_atom(index): 888 | index = [index] 889 | for coord in index: 890 | new_focus = focus[int(coord) % len(focus)] 891 | old_coord = coord 892 | old_focus = focus 893 | if is_atom(new_focus): 894 | break 895 | focus = new_focus 896 | old_focus[int(old_coord) % len(old_focus)] = item 897 | return a 898 | return variadize(lambda a: modify_indices(f, g, a), 899 | lambda a, b: modify_indices(lambda x: f(a, x), lambda x: g(a, x), b)) 900 | 901 | func_defs = {c:variadize(f) for (c,f) in func_defs.items()} 902 | oper_defs = {c:variadize(f) for (c,f) in oper_defs.items()} 903 | --------------------------------------------------------------------------------