""" *------------------------------------------------------------* |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-2024 wryl, Paradigital | *------------------------------------------------------------* """ # `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) 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 else: pattern, operation, parameters = rule result, queue = operation(queue, rules, pattern, *parameters) if result == True: failures = 0 #print("<>: ", inspect(seek(queue, ["SRT"]))) #print("<>: ", inspect(queue)) #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.copy() if len(sys.argv) >= 2: content = read(sys.argv[1]) if content == None: 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("::> ") 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()