Last active
September 9, 2025 22:32
-
-
Save IFcoltransG/15b680fba9a97149c4eb676dc7f91f40 to your computer and use it in GitHub Desktop.
A parser for very simple maths expressions in Ink
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Ink expression parsing code created by IFcoltransG | |
| // Released into public domain | |
| // May be used under the MIT No Attribution License | |
| CONST string_to_parse = "5 * 10" | |
| Parsing "{string_to_parse}" | |
| ~ temp c = "" // initial cursor | |
| ~ temp result = parse_expression(string_to_parse, c) | |
| final cursor = "{c}" | |
| result = {result} | |
| LIST Operation = Plus, Minus, Multiply, Divide | |
| === function operation_token(op) === | |
| {op: | |
| - Plus: ~ return "+" | |
| - Minus: ~ return "-" | |
| - Multiply: ~ return "*" | |
| - Divide: ~ return "/" | |
| } | |
| // evaluates an expression of the form "x + y" from a string, | |
| // where x,y <= 20, the operation is in "+-*/" and there's | |
| // optionally up to 5 space characters between each token. | |
| // modifies the cursor variable, advancing it past the expression parsed | |
| === function parse_expression(string, ref cursor) === | |
| ~ temp left = parse_integer(string, cursor, 20) | |
| ~ cursor = advance_cursor(cursor, left, -> call, -> string_of) | |
| ~ temp whitespace_left = parse_n(string, cursor, " ", 5) | |
| ~ cursor = advance_cursor(cursor, whitespace_left, -> n_copies, " ") | |
| ~ temp op = parse_list_item(string, cursor, LIST_ALL(Operation), -> call, -> operation_token) | |
| ~ cursor = advance_cursor(cursor, op, -> call, -> operation_token) | |
| ~ temp whitespace_right = parse_n(string, cursor, " ", 5) | |
| ~ cursor = advance_cursor(cursor, whitespace_right, -> n_copies, " ") | |
| ~ temp right = parse_integer(string, cursor, 20) | |
| ~ cursor = advance_cursor(cursor, right, -> call, -> string_of) | |
| {op: | |
| - Plus: | |
| ~ return left + right | |
| - Minus: | |
| ~ return left - right | |
| - Multiply: | |
| ~ return left * right | |
| - Divide: | |
| ~ return left / right | |
| } | |
| // finds the list item at the cursor from the given options, | |
| // possibly transforming the list item into a string first with ->map and map_arg | |
| // map(item, map_arg) is called on each item in options to produce the pattern | |
| // that is checked for at the cursor | |
| === function parse_list_item(string, cursor, options, -> map, map_arg) === | |
| ~ return list_find(-> contains_at, options, string, cursor, map, map_arg) | |
| // parses the biggest integer less than or equal to maximum that begins | |
| // at the cursor position | |
| === function parse_integer(string, cursor, maximum) === | |
| ~ return integer_find(-> contains_at, maximum, string, cursor, -> call, -> string_of) | |
| // finds an n (less than or equal to maximum) | |
| // such that the string has n copies of token at the cursor | |
| === function parse_n(string, cursor, token, maximum) === | |
| ~ return integer_find(-> contains_at, maximum, string, cursor, -> n_copies, token) | |
| // check if a string contains some element (possibly mapped) | |
| // at the position given by the cursor | |
| === function contains_at(element, data, cursor, -> map, map_arg) === | |
| ~ temp next_cursor = advance_cursor(cursor, element, map, map_arg) | |
| ~ return starts_with(data, next_cursor) | |
| // a cursor is just a string that | |
| // prefixes the part of the string you're looking at; | |
| // moving the cursor forward means adding a new string to the prefix; | |
| // ->map and map_arg are for controlling what gets added based on the element | |
| === function advance_cursor(cursor, element, -> map, map_arg) === | |
| ~ return cursor + map(element, map_arg) | |
| // join n copies of a string together | |
| === function n_copies(n, token) === | |
| {n == 0: | |
| ~ return "" | |
| } | |
| ~ return "{token}" + n_copies(n - 1, token) | |
| === function string_of(element) === | |
| ~ return "{element}" | |
| // helper to use up one extra argument if you don't need it | |
| === function call(arg, -> func) === | |
| ~ return func(arg) | |
| // Find the first element of the options list that satisfies the predicate | |
| // (with the given extra args) | |
| === function list_find(-> predicate, options, arg1, arg2, arg3, arg4) === | |
| {options == (): | |
| ~ return () | |
| } | |
| ~ temp element = pop(options) | |
| {predicate(element, arg1, arg2, arg3, arg4): | |
| ~ return element | |
| } | |
| ~ return list_find(predicate, options, arg1, arg2, arg3, arg4) | |
| // Find the largest integer less than or equal to max that satisfies the predicate | |
| // (with the given extra args) | |
| === function integer_find(-> predicate, max, arg1, arg2, arg3, arg4) === | |
| {max < 0: | |
| ~ return () | |
| } | |
| {predicate(max, arg1, arg2, arg3, arg4): | |
| ~ return max | |
| } | |
| ~ return integer_find(predicate, max - 1, arg1, arg2, arg3, arg4) | |
| === function starts_with(word, start) === | |
| ~ return word !? START + start && START + word ? START + start | |
| CONST START = "^^" // please ensure your strings never contain this sentinel | |
| === function pop(ref list) === | |
| ~ temp x = LIST_MIN(list) | |
| ~ list -= x | |
| ~ return x |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment