""" *------------------------------------------------------------* |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();