Skip to content

Instantly share code, notes, and snippets.

@IFcoltransG
Last active September 9, 2025 22:32
Show Gist options
  • Save IFcoltransG/15b680fba9a97149c4eb676dc7f91f40 to your computer and use it in GitHub Desktop.
Save IFcoltransG/15b680fba9a97149c4eb676dc7f91f40 to your computer and use it in GitHub Desktop.
A parser for very simple maths expressions in Ink
// 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