From 9d3384805b6a0ac782b9853c2505ffb0b5172e2f Mon Sep 17 00:00:00 2001 From: Devine Lu Linvega Date: Sun, 31 Mar 2024 12:18:37 -0700 Subject: [PATCH] Init --- README.md | 22 +++ build.sh | 3 + modal.py | 421 ++++++++++++++++++++++++++++++++++++++++++++++++++ prelude.modal | 144 +++++++++++++++++ 4 files changed, 590 insertions(+) create mode 100644 README.md create mode 100755 build.sh create mode 100644 modal.py create mode 100644 prelude.modal diff --git a/README.md b/README.md new file mode 100644 index 0000000..fa6683a --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# Modal + +Modal is a language based a model of computation that uses pattern matching and replacement to rewrite trees. + +All Modal code is represented as a series of rules applied to a given tree, formatted textually as tokens delimited with parenthesis, which gets continually modified until no rules match any given part of the tree. + +A pattern/replacement can be: + + A token: foo, bar, baz + A variable: ?foo, ?bar, ?baz + A sequence of the above: (foo bar baz), (foo ?bar baz), foo (bar (baz)) + +## Run + +``` +python3 ./modal.py +``` + +## Credits + +Created by [wryl](https://wryl.tech/), Immediate Mode Technologies. + diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..5cd834b --- /dev/null +++ b/build.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env sh + +python3 ./modal.py $1 diff --git a/modal.py b/modal.py new file mode 100644 index 0000000..edc76b8 --- /dev/null +++ b/modal.py @@ -0,0 +1,421 @@ +""" + *------------------------------------------------------------* + |modal.py : An Implementation of Modal in Python | + | | + |This is an implementation of Modal, a programming language | + |based on term rewriting. This particular implementation | + |is based on the idea of cyclic delimited string rewriting | + |using a central queue and a dictionary of variable bindings.| + | | + |© 2019 imode, Immediate Mode Technologies | + *------------------------------------------------------------* +""" + +# `sys` for I/O and arguments. +# `time` for measuring the runtime of the interpreter. +import sys, time; + +# A decorator intended for measuring the runtime of a given function. +def measure(function): + def measurement(*arguments): + start = time.time(); + result = function(*arguments); + end = time.time(); + milliseconds = (end - start) * 1000.0; + print("\nTook {:.3f}ms".format(milliseconds)) + return result; + return measurement; + +# Enqueue an item by appending it to the queue. +def enqueue(queue, item): + return queue + item; + +# Dequeue an item by slicing the queue. The last item is the head. +def dequeue(queue, length=1): + if length > len(queue): + return queue; + return queue[length:]; + +# Get the item(s) at the head of the queue. +def peek(queue, length=1): + if length > len(queue): + return None; + if length == 1: + return queue[0]; + return queue[:length]; + +# Roll/cycle the queue by a certain amount by slicing. +# This dequeues and enqueues a number of items, "cycling" the queue. +def roll(queue, length=1): + if length > len(queue): + return queue; + return queue[length:] + queue[:length]; + +# Seek to a certain position in the queue by repeatedly rolling it. +def seek(queue, pattern): + if pattern not in queue: + return queue; + while peek(queue, len(pattern)) != pattern: + queue = roll(queue); + return queue; + +# Extract a delimited fragment (subtree) from the queue. +def extract(queue): + results = []; + depth = 0; + for element in queue: + if element[0] == "SRT": + return []; + if element[0] == "INC": + depth = depth + 1; + if element[0] == "DEC": + if depth == 0: + return results; + depth = depth - 1; + results.append(element); + if depth == 0: + return results; + return results; + +# Generate a list of variable bindings from the current queue and a pattern. +def match(pattern, queue, context=None): + if context == None: + context = {}; + if peek(queue) == None: + return context; + for element in pattern: + if element[0] == "VAR": + variable = element[1]; + value = extract(queue); + if variable in context: + if context[variable] != value: + return None; + queue = dequeue(queue, len(context[variable])); + else: + if len(value) == 0: + return None; + context[variable] = value; + queue = dequeue(queue, len(context[variable])); + elif element != peek(queue): + return None; + else: + queue = dequeue(queue); + return context; + +# Fill in a pattern with variables in it using a list of variable bindings. +def construct(pattern, context): + results = []; + for element in pattern: + if element[0] == "VAR": + if element[1] in context: + for element in context[element[1]]: + results.append(element); + else: + results.append(element); + else: + results.append(element); + return results; + +# Apply a pattern/replacement rule to the queue. +def apply(queue, rules, pattern, replacement): + context = match(pattern, queue); + if context == None: + return (False, roll(queue)); + pattern = construct(pattern, context); + if not pattern: + return (False, roll(queue)); + replacement = construct(replacement, context); + if len(replacement) == 0: + return (True, dequeue(queue, len(pattern))) + if not replacement: + return (True, roll(queue)); + return (True, enqueue(dequeue(queue, len(pattern)), replacement)); + +def define(queue, rules, pattern): + context = match(pattern, queue); + left = context["left"]; + right = context["right"]; + if right and left: + if len(left) > 1: + left = left[1:][:-1]; + if len(right) > 1: + right = right[1:][:-1]; + rules.append((left, apply, [right])); + return (True, dequeue(queue, len(construct(pattern, context)))); + return (False, roll(queue)); + +def undefine(queue, rules, pattern): + context = match(pattern, queue); + left = context["left"]; + if left: + if len(left) > 1: + left = left[1:][:-1]; + for rule, index in zip(rules, range(0, len(rules))): + candidate , _, _ = rule; + if candidate == left: + del rules[index]; + return (True, dequeue(queue, len(construct(pattern, context)))); + return (False, roll(queue)); + +def add(queue, rules, pattern): + context = match(pattern, queue); + left = context["left"]; + right = context["right"]; + if left and right: + if left[0][0] == "NUM" and right[0][0] == "NUM": + queue = dequeue(queue, len(construct(pattern, context))); + queue = enqueue(queue, parse(str(left[0][1] + right[0][1]))); + return (True, queue); + return (False, roll(queue)); + +def subtract(queue, rules, pattern): + context = match(pattern, queue); + left = context["left"]; + right = context["right"]; + if left and right: + if left[0][0] == "NUM" and right[0][0] == "NUM": + queue = dequeue(queue, len(construct(pattern, context))); + queue = enqueue(queue, parse(str(left[0][1] - right[0][1]))); + return (True, queue); + return (False, roll(queue)); + +def multiply(queue, rules, pattern): + context = match(pattern, queue); + left = context["left"]; + right = context["right"]; + if left and right: + if left[0][0] == "NUM" and right[0][0] == "NUM": + queue = dequeue(queue, len(construct(pattern, context))); + queue = enqueue(queue, parse(str(left[0][1] * right[0][1]))); + return (True, queue); + return (False, roll(queue)); + +def divide(queue, rules, pattern): + context = match(pattern, queue); + left = context["left"]; + right = context["right"]; + if left and right: + if left[0][0] == "NUM" and right[0][0] == "NUM": + queue = dequeue(queue, len(construct(pattern, context))); + queue = enqueue(queue, parse(str(left[0][1] // right[0][1]))); + return (True, queue); + return (False, roll(queue)); + +def modulo(queue, rules, pattern): + context = match(pattern, queue); + left = context["left"]; + right = context["right"]; + if left and right: + if left[0][0] == "NUM" and right[0][0] == "NUM": + queue = dequeue(queue, len(construct(pattern, context))); + queue = enqueue(queue, parse(str(left[0][1] % right[0][1]))); + return (True, queue); + return (False, roll(queue)); + +def display(queue, rules, pattern): + context = match(pattern, queue); + left = context["left"]; + if left: + if left[0][0] == "LIT" or left[0][0] == "NUM": + if left[0][1] == "space": + print(' ', end=""); + elif left[0][1] == "newline": + print('\n', end=""); + elif left[0][1] == "tab": + print('\t', end=""); + else: + print(left[0][1], end=""); + return (True, dequeue(queue, len(construct(pattern, context)))); + return (False, roll(queue)); + +def applicable(rules, queue): + results = []; + for pattern, operation, parameters in rules: + if match(pattern, queue) != None: + results.append((pattern, operation, parameters)); + return results; + +def pick(list): + if len(list) == 0: + return None; + return list[0]; + +def reconstruct(pattern): + for element in pattern: + if element[0] == "INC": + yield '('; + elif element[0] == "DEC": + yield ')'; + elif element[0] == "NUM": + yield str(element[1]); + elif element[0] == "LIT": + yield element[1]; + elif element[0] == "VAR": + yield '?' + element[1]; + yield ' '; + +def number(string): + try: + result = int(string); + return True; + except: + return False; + +def literal(string, index=0): + token = ""; + while index != len(string): + character = string[index]; + if character in ['(', ')', '{', '}', '[', ']', '?', ' ', '\t', '\n', '\r']: + break; + else: + token = token + string[index]; + index = index + 1; + return (token, index); + +def parse(string, index=0): + results = []; + while index != len(string): + character = string[index]; + if character in ['(', ')', '{', '}', '[', ']', '?', ' ', '\t', '\n', '\r']: + index = index + 1; + if character in ['(', '{', '[']: + results.append(["INC"]); + elif character in [')', '}', ']']: + results.append(["DEC"]); + elif character not in [' ', '\t', '\n', '\r']: + token, index = literal(string, index); + if character == '?': + results.append(["VAR", token]); + elif number(token): + results.append(["NUM", int(token)]); + else: + results.append(["LIT", token]); + return results; + +@measure +def run(rules, queue, limit=pow(2, 32)): + steps = 0; + failures = 0; + queue = [["SRT"]] + queue; + while failures != len(queue) and steps != limit: + rule = pick(applicable(rules, queue)); + if rule == None: + queue = roll(queue); + failures = failures + 1; + print("<>: ", inspect(queue)) + else: + pattern, operation, parameters = rule; + result, queue = operation(queue, rules, pattern, *parameters); + if result == True: + failures = 0; + #print("<>: ", inspect(seek(queue, ["SRT"])), ":::", inspect(pattern)) + print("<>: ", inspect(queue), ":::", inspect(pattern)) + #input() + steps = steps + 1; + if steps == limit: + print("Execution limit reached."); + return queue; + +def read(file): + try: + with open(file) as file: + content = file.read(); + return content; + except EnvironmentError: + return None; + +def inspect(pattern): + return "".join(reconstruct(pattern)); + +def usage(name): + print(name + ':', "a programming language based on rewriting."); + print("Usage:"); + print((' ' * 4) + name, ""); + +def prompt(prompt): + try: + return input(prompt); + except: + return None; + +def help(): + prefix = ' ' * 4; + print("Commands:"); + print(prefix + "rules : Display the current ruleset."); + print(prefix + "clear : Clear the current ruleset."); + print(prefix + "help : This message."); + print(prefix + "quit : Quit."); + +def main(): + defaults = [ + (parse("define ?left ?right"), define, []), + (parse("undefine ?left"), undefine, []), + (parse("add (?left) (?right)"), add, []), + (parse("add (?left) ?right"), add, []), + (parse("add ?left (?right)"), add, []), + (parse("add ?left ?right"), add, []), + (parse("subtract (?left) (?right)"), subtract, []), + (parse("subtract (?left) ?right"), subtract, []), + (parse("subtract ?left (?right)"), subtract, []), + (parse("subtract ?left ?right"), subtract, []), + (parse("multiply (?left) (?right)"), multiply, []), + (parse("multiply (?left) ?right"), multiply, []), + (parse("multiply ?left (?right)"), multiply, []), + (parse("multiply ?left ?right"), multiply, []), + (parse("divide (?left) (?right)"), divide, []), + (parse("divide (?left) ?right"), divide, []), + (parse("divide ?left (?right)"), divide, []), + (parse("divide ?left ?right"), divide, []), + (parse("modulo (?left) (?right)"), modulo, []), + (parse("modulo (?left) ?right"), modulo, []), + (parse("modulo ?left (?right)"), modulo, []), + (parse("modulo ?left ?right"), modulo, []), + (parse("(?left) + (?right)"), add, []), + (parse("(?left) + ?right"), add, []), + (parse("?left + (?right)"), add, []), + (parse("?left + ?right"), add, []), + (parse("(?left) - (?right)"), subtract, []), + (parse("(?left) - ?right"), subtract, []), + (parse("?left - (?right)"), subtract, []), + (parse("?left - ?right"), subtract, []), + (parse("(?left) * (?right)"), multiply, []), + (parse("(?left) * ?right"), multiply, []), + (parse("?left * (?right)"), multiply, []), + (parse("?left * ?right"), multiply, []), + (parse("?left / ?right"), divide, []), + (parse("?left % ?right"), modulo, []), + (parse("display ?left"), display, []), + ]; + rules = defaults; + if len(sys.argv) >= 2: + content = read(sys.argv[1]); + if content == None: + print("No such file."); + return; + print("Initializating..."); + run(rules, parse(content)); + print("Modal v0.02"); + help(); + while True: + input = prompt("::> "); + if input == None: + break; + elif parse(input) == parse("rules"): + print("Rules:"); + prefix = ' ' * 4; + for pattern, operation, parameters in rules: + if operation == apply: + print(prefix + inspect(pattern) + "-> " + inspect(parameters[0])); + elif parse(input) == parse("clear"): + rules = defaults; + elif parse(input) == parse("help"): + help(); + elif parse(input) == parse("quit") or parse(input) == parse("exit"): + break; + else: + print("Reducing..."); + print(inspect(seek(run(rules, parse(input)), ["SRT"]))); + return; + +if __name__ == "__main__": + main(); diff --git a/prelude.modal b/prelude.modal new file mode 100644 index 0000000..b2b2f0a --- /dev/null +++ b/prelude.modal @@ -0,0 +1,144 @@ +1 -> (s (0)); + + +neg (neg ?x) -> ?x; +neg (0) -> 0; + + +add (s ?x) (s ?y) -> s (add ?x (s ?y)); +add (0) (s ?x) -> s ?x; +add (s ?x) (0) -> s ?x; +add (0) (0) -> 0; + + +?x + ?y + ?z -> (?x + ?y) + ?z; + + +?x + ?y -> add ?x ?y; + + +sub (s ?x) (s ?y) -> sub ?x ?y; +sub (s ?x) (0) -> s ?x; +sub (0) (s ?x) -> neg (s ?x); +sub (0) (0) -> 0; + + +?x - ?y -> sub ?x ?y; + + +mul (s ?x) (s ?y) -> (s ?x) + (mul (s ?x) ((s ?y) - 1)); +mul (s ?x) (s (0)) -> s ?x; +mul (s ?x) (0) -> 0; +mul (0) (s ?x) -> 0; + + +?x * ?y -> mul ?x ?y; + + +Ensures that a list or a number has been reduced to its normal form. ; + +reduced (0) -> true; +reduced (nil) -> true; +reduced (s ?x) -> reduced ?x; +reduced (?h : ?t) -> reduced ?t; +reduced ?x -> false; + + +Because there may be conflicts with expressions that +are currently being reduced, we need to fold over reduced +lists, i.e ones that have already been fully generated. ; + +fold (?f) ?i ?l -> fold reduced ?l (?f) ?i ?l; +fold true (?f) ?i (nil) -> ?i; +fold true (?f) ?i (?h : (nil)) -> ?f ?i ?h; +fold true (?f) ?i (?h : ?t) -> ?f ?i (fold (?f) ?h ?t); +fold false (?f) ?i ?l -> fold (?f) ?i ?l; + + +factorial (s (0)) -> s (0); +factorial (s ?x) -> (s ?x) * (factorial ((s ?x) - 1)); + + +sum (?h : ?t) -> fold (add) (0) (?h : ?t); + + +range ?x (s (0)) -> ?x : (nil); +range ?x (s ?y) -> ?x : (range (?x + 1) ((s ?y) - 1)); + + +Disgusting (yet valid) hack for currying. +We need lambdas. ; + +unpack (?h : nil) -> ?h; +unpack (?h : ?t) -> ?h unpack ?t; +unpack (?x) -> ?x; +unpack (?x . -> ?x unpack (; + + +:: (?f) ?a -> ?f unpack ?a; + + +mapp (?f) ?a (nil) -> nil; +mapp (?f) ?a (?h : (nil)) -> (?f unpack ?a ?h) : (nil); +mapp (?f) ?a (?h : ?t) -> (?f unpack ?a ?h) : (mapp (?f) ?a ?t); + + +map (?f) (nil) -> nil; +map (?f) (?h : nil) -> (?f ?h) : (nil); +map (?f) (?h : ?t) -> (?f ?h) : (map (?f) ?t); + + +product ?x -> fold (mul) 1 ?x; + + +factorial2 ?x -> product (range 1 ?x); + + +contains ?x (nil) -> false; +contains ?x (?x : ?t) -> true; +contains ?x (?h : ?t) -> contains ?x ?t; + + +unique (nil) -> nil; +unique false (?h : ?t) -> ?h : (unique ?t); +unique true (?h : ?t) -> unique ?t; +unique (?h : ?t) -> unique contains ?h ?t (?h : ?t); + + +length (nil) -> 0; +length (?h : ?t) -> s (length ?t); + + +zipWith (?f) (nil) (nil) -> nil; +zipWith (?f) (?h : ?t) (nil) -> nil; +zipWith (?f) (nil) (?h : ?t) -> nil; +zipWith (?f) (?h1 : ?t1) (?h2 : ?t2) -> (?f ?h1 ?h2) : (zipWith (?f) ?t1 ?t2); + + +evens ?x -> zipWith (add) (range (0) ?x) (range (0) ?x); + + +not (not ?x) -> ?x; +not true -> false; +not false -> true; + + +any ?x -> contains true ?x; + + +all ?x -> not contains false ?x; + + +none ?x -> not any ?x; + + +add1 ?x -> add ?x 1; + + +square ?x -> ?x * ?x; + + +reduce (s ?x) -> s (reduce ?x); +reduce (0) -> (0); +reduce (?h : ?t) -> ?h : (reduce ?t); +reduce (nil) -> nil;