diff --git a/combinators.modal b/combinators.modal new file mode 100644 index 0000000..4f3aa96 --- /dev/null +++ b/combinators.modal @@ -0,0 +1,11 @@ + +define (M ?x) (?x ?x) +define (KI ?x ?y) (?y) +define (T ?x ?y) (?y ?y) +define (W ?x ?y) (?x ?y ?y) +define (K ?x ?y) (?x) +define (C ?x ?y ?z) (?x ?z ?y) +define (B ?x ?y ?z) (?x (?y ?z)) +define (I ?x) (?x) +define (S ?x ?y ?z) (?x ?z (?y ?z)) + diff --git a/modal.py b/modal.py index edc76b8..bbd8e0b 100644 --- a/modal.py +++ b/modal.py @@ -7,344 +7,341 @@ |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 | + |© 2019-2024 wryl, Paradigital | *------------------------------------------------------------* """ # `sys` for I/O and arguments. # `time` for measuring the runtime of the interpreter. -import sys, time; +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; + start = time.time() + result = function(*arguments) + end = time.time() + milliseconds = (end - start) * 1000.0 print("\nTook {:.3f}ms".format(milliseconds)) - return result; - return measurement; + return result + return measurement # Enqueue an item by appending it to the queue. def enqueue(queue, item): - return 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:]; + 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; + return None if length == 1: - return queue[0]; - return queue[:length]; + 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]; + 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; + return queue while peek(queue, len(pattern)) != pattern: - queue = roll(queue); - return queue; + queue = roll(queue) + return queue # Extract a delimited fragment (subtree) from the queue. def extract(queue): - results = []; - depth = 0; + results = [] + depth = 0 for element in queue: if element[0] == "SRT": - return []; + return [] if element[0] == "INC": - depth = depth + 1; + depth = depth + 1 if element[0] == "DEC": if depth == 0: - return results; - depth = depth - 1; - results.append(element); + return results + depth = depth - 1 + results.append(element) if depth == 0: - return results; - return results; + 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 = {}; + context = {} if peek(queue) == None: - return context; + return context for element in pattern: if element[0] == "VAR": - variable = element[1]; - value = extract(queue); + variable = element[1] + value = extract(queue) if variable in context: if context[variable] != value: - return None; - queue = dequeue(queue, len(context[variable])); + return None + queue = dequeue(queue, len(context[variable])) else: if len(value) == 0: - return None; - context[variable] = value; - queue = dequeue(queue, len(context[variable])); + return None + context[variable] = value + queue = dequeue(queue, len(context[variable])) elif element != peek(queue): - return None; + return None else: - queue = dequeue(queue); - return context; + queue = dequeue(queue) + return context # Fill in a pattern with variables in it using a list of variable bindings. def construct(pattern, context): - results = []; + results = [] for element in pattern: if element[0] == "VAR": if element[1] in context: for element in context[element[1]]: - results.append(element); + results.append(element) else: - results.append(element); + results.append(element) else: - results.append(element); - return results; + results.append(element) + return results # Apply a pattern/replacement rule to the queue. def apply(queue, rules, pattern, replacement): - context = match(pattern, queue); + context = match(pattern, queue) if context == None: - return (False, roll(queue)); - pattern = construct(pattern, context); + 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))) + return (False, roll(queue)) + replacement = construct(replacement, context) if not replacement: - return (True, roll(queue)); - return (True, enqueue(dequeue(queue, len(pattern)), replacement)); + return (False, 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"]; + context = match(pattern, queue) + left = context["left"] + right = context["right"] if right and left: if len(left) > 1: - left = left[1:][:-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)); + 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"]; + context = match(pattern, queue) + left = context["left"] if left: if len(left) > 1: - left = left[1:][:-1]; + left = left[1:][:-1] for rule, index in zip(rules, range(0, len(rules))): - candidate , _, _ = rule; + candidate , _, _ = rule if candidate == left: - del rules[index]; - return (True, dequeue(queue, len(construct(pattern, context)))); - return (False, roll(queue)); + 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"]; + 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)); + 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"]; + 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)); + 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"]; + 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)); + 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"]; + 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)); + 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"]; + 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)); + 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"]; + 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=""); + print(' ', end="") elif left[0][1] == "newline": - print('\n', end=""); + print('\n', end="") elif left[0][1] == "tab": - print('\t', end=""); + print('\t', end="") else: - print(left[0][1], end=""); - return (True, dequeue(queue, len(construct(pattern, context)))); - return (False, roll(queue)); + print(left[0][1], end="") + return (True, dequeue(queue, len(construct(pattern, context)))) + return (False, roll(queue)) def applicable(rules, queue): - results = []; + results = [] for pattern, operation, parameters in rules: if match(pattern, queue) != None: - results.append((pattern, operation, parameters)); - return results; + results.append((pattern, operation, parameters)) + return results def pick(list): if len(list) == 0: - return None; - return list[0]; + return None + return list[0] def reconstruct(pattern): for element in pattern: if element[0] == "INC": - yield '('; + yield '(' elif element[0] == "DEC": - yield ')'; + yield ')' elif element[0] == "NUM": - yield str(element[1]); + yield str(element[1]) elif element[0] == "LIT": - yield element[1]; + yield element[1] elif element[0] == "VAR": - yield '?' + element[1]; - yield ' '; + yield '?' + element[1] + yield ' ' def number(string): try: - result = int(string); - return True; + result = int(string) + return True except: - return False; + return False def literal(string, index=0): - token = ""; + token = "" while index != len(string): - character = string[index]; + character = string[index] if character in ['(', ')', '{', '}', '[', ']', '?', ' ', '\t', '\n', '\r']: - break; + break else: - token = token + string[index]; - index = index + 1; - return (token, index); + token = token + string[index] + index = index + 1 + return (token, index) def parse(string, index=0): - results = []; + results = [] while index != len(string): - character = string[index]; + character = string[index] if character in ['(', ')', '{', '}', '[', ']', '?', ' ', '\t', '\n', '\r']: - index = index + 1; + index = index + 1 if character in ['(', '{', '[']: - results.append(["INC"]); + results.append(["INC"]) elif character in [')', '}', ']']: - results.append(["DEC"]); + results.append(["DEC"]) elif character not in [' ', '\t', '\n', '\r']: - token, index = literal(string, index); + token, index = literal(string, index) if character == '?': - results.append(["VAR", token]); + results.append(["VAR", token]) elif number(token): - results.append(["NUM", int(token)]); + results.append(["NUM", int(token)]) else: - results.append(["LIT", token]); - return results; + results.append(["LIT", token]) + return results @measure def run(rules, queue, limit=pow(2, 32)): - steps = 0; - failures = 0; - queue = [["SRT"]] + queue; + steps = 0 + failures = 0 + queue = [["SRT"]] + queue while failures != len(queue) and steps != limit: - rule = pick(applicable(rules, queue)); + rule = pick(applicable(rules, queue)) if rule == None: - queue = roll(queue); - failures = failures + 1; - print("<>: ", inspect(queue)) + queue = roll(queue) + failures = failures + 1 else: - pattern, operation, parameters = rule; - result, queue = operation(queue, rules, pattern, *parameters); + 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)) + failures = 0 + #print("<>: ", inspect(seek(queue, ["SRT"]))) + #print("<>: ", inspect(queue)) #input() - steps = steps + 1; + steps = steps + 1 if steps == limit: - print("Execution limit reached."); - return queue; + print("Execution limit reached.") + return queue def read(file): try: with open(file) as file: - content = file.read(); - return content; + content = file.read() + return content except EnvironmentError: - return None; + return None def inspect(pattern): - return "".join(reconstruct(pattern)); + return "".join(reconstruct(pattern)) def usage(name): - print(name + ':', "a programming language based on rewriting."); - print("Usage:"); - print((' ' * 4) + name, ""); + print(name + ':', "a programming language based on rewriting.") + print("Usage:") + print((' ' * 4) + name, "") def prompt(prompt): try: - return input(prompt); + return input(prompt) except: - return None; + 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."); + 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 = [ @@ -385,37 +382,40 @@ def main(): (parse("?left / ?right"), divide, []), (parse("?left % ?right"), modulo, []), (parse("display ?left"), display, []), - ]; - rules = defaults; + ] + rules = defaults.copy() if len(sys.argv) >= 2: - content = read(sys.argv[1]); + content = read(sys.argv[1]) if content == None: - print("No such file."); - return; - print("Initializating..."); - run(rules, parse(content)); - print("Modal v0.02"); - help(); + print("No such file.") + return + print("Initializating...") + run(rules, parse(content)) + else: + usage(sys.argv[0]) + return + print("Modal v0.02") + help() while True: - input = prompt("::> "); + input = prompt("::> ") if input == None: - break; + break elif parse(input) == parse("rules"): - print("Rules:"); - prefix = ' ' * 4; + print("Rules:") + prefix = ' ' * 4 for pattern, operation, parameters in rules: if operation == apply: - print(prefix + inspect(pattern) + "-> " + inspect(parameters[0])); + print(prefix + inspect(pattern) + "-> " + inspect(parameters[0])) elif parse(input) == parse("clear"): - rules = defaults; + rules = defaults elif parse(input) == parse("help"): - help(); + help() elif parse(input) == parse("quit") or parse(input) == parse("exit"): - break; + break else: - print("Reducing..."); - print(inspect(seek(run(rules, parse(input)), ["SRT"]))); - return; + print("Reducing...") + print(inspect(seek(run(rules, parse(input)), ["SRT"]))) + return if __name__ == "__main__": - main(); + main() diff --git a/test.modal b/test.modal new file mode 100644 index 0000000..f6632c4 --- /dev/null +++ b/test.modal @@ -0,0 +1,4 @@ +define nil ((0)) +define (cons ?x (?y)) ((?x ?y)) +define (car (?x)) ?x +