modal/modal.py

422 lines
15 KiB
Python
Raw Normal View History

2024-03-31 15:18:37 -04:00
"""
*------------------------------------------------------------*
|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.|
| |
2024-04-01 20:17:53 -04:00
|© 2019-2024 wryl, Paradigital |
2024-03-31 15:18:37 -04:00
*------------------------------------------------------------*
"""
# `sys` for I/O and arguments.
# `time` for measuring the runtime of the interpreter.
2024-04-01 20:17:53 -04:00
import sys, time
2024-03-31 15:18:37 -04:00
# A decorator intended for measuring the runtime of a given function.
def measure(function):
def measurement(*arguments):
2024-04-01 20:17:53 -04:00
start = time.time()
result = function(*arguments)
end = time.time()
milliseconds = (end - start) * 1000.0
2024-03-31 15:18:37 -04:00
print("\nTook {:.3f}ms".format(milliseconds))
2024-04-01 20:17:53 -04:00
return result
return measurement
2024-03-31 15:18:37 -04:00
# Enqueue an item by appending it to the queue.
def enqueue(queue, item):
2024-04-01 20:17:53 -04:00
return queue + item
2024-03-31 15:18:37 -04:00
# Dequeue an item by slicing the queue. The last item is the head.
def dequeue(queue, length=1):
if length > len(queue):
2024-04-01 20:17:53 -04:00
return queue
return queue[length:]
2024-03-31 15:18:37 -04:00
# Get the item(s) at the head of the queue.
def peek(queue, length=1):
if length > len(queue):
2024-04-01 20:17:53 -04:00
return None
2024-03-31 15:18:37 -04:00
if length == 1:
2024-04-01 20:17:53 -04:00
return queue[0]
return queue[:length]
2024-03-31 15:18:37 -04:00
# 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):
2024-04-01 20:17:53 -04:00
return queue
return queue[length:] + queue[:length]
2024-03-31 15:18:37 -04:00
# Seek to a certain position in the queue by repeatedly rolling it.
def seek(queue, pattern):
if pattern not in queue:
2024-04-01 20:17:53 -04:00
return queue
2024-03-31 15:18:37 -04:00
while peek(queue, len(pattern)) != pattern:
2024-04-01 20:17:53 -04:00
queue = roll(queue)
return queue
2024-03-31 15:18:37 -04:00
# Extract a delimited fragment (subtree) from the queue.
def extract(queue):
2024-04-01 20:17:53 -04:00
results = []
depth = 0
2024-03-31 15:18:37 -04:00
for element in queue:
if element[0] == "SRT":
2024-04-01 20:17:53 -04:00
return []
2024-03-31 15:18:37 -04:00
if element[0] == "INC":
2024-04-01 20:17:53 -04:00
depth = depth + 1
2024-03-31 15:18:37 -04:00
if element[0] == "DEC":
if depth == 0:
2024-04-01 20:17:53 -04:00
return results
depth = depth - 1
results.append(element)
2024-03-31 15:18:37 -04:00
if depth == 0:
2024-04-01 20:17:53 -04:00
return results
return results
2024-03-31 15:18:37 -04:00
# Generate a list of variable bindings from the current queue and a pattern.
def match(pattern, queue, context=None):
if context == None:
2024-04-01 20:17:53 -04:00
context = {}
2024-03-31 15:18:37 -04:00
if peek(queue) == None:
2024-04-01 20:17:53 -04:00
return context
2024-03-31 15:18:37 -04:00
for element in pattern:
if element[0] == "VAR":
2024-04-01 20:17:53 -04:00
variable = element[1]
value = extract(queue)
2024-03-31 15:18:37 -04:00
if variable in context:
if context[variable] != value:
2024-04-01 20:17:53 -04:00
return None
queue = dequeue(queue, len(context[variable]))
2024-03-31 15:18:37 -04:00
else:
if len(value) == 0:
2024-04-01 20:17:53 -04:00
return None
context[variable] = value
queue = dequeue(queue, len(context[variable]))
2024-03-31 15:18:37 -04:00
elif element != peek(queue):
2024-04-01 20:17:53 -04:00
return None
2024-03-31 15:18:37 -04:00
else:
2024-04-01 20:17:53 -04:00
queue = dequeue(queue)
return context
2024-03-31 15:18:37 -04:00
# Fill in a pattern with variables in it using a list of variable bindings.
def construct(pattern, context):
2024-04-01 20:17:53 -04:00
results = []
2024-03-31 15:18:37 -04:00
for element in pattern:
if element[0] == "VAR":
if element[1] in context:
for element in context[element[1]]:
2024-04-01 20:17:53 -04:00
results.append(element)
2024-03-31 15:18:37 -04:00
else:
2024-04-01 20:17:53 -04:00
results.append(element)
2024-03-31 15:18:37 -04:00
else:
2024-04-01 20:17:53 -04:00
results.append(element)
return results
2024-03-31 15:18:37 -04:00
# Apply a pattern/replacement rule to the queue.
def apply(queue, rules, pattern, replacement):
2024-04-01 20:17:53 -04:00
context = match(pattern, queue)
2024-03-31 15:18:37 -04:00
if context == None:
2024-04-01 20:17:53 -04:00
return (False, roll(queue))
pattern = construct(pattern, context)
2024-03-31 15:18:37 -04:00
if not pattern:
2024-04-01 20:17:53 -04:00
return (False, roll(queue))
replacement = construct(replacement, context)
2024-03-31 15:18:37 -04:00
if not replacement:
2024-04-01 20:17:53 -04:00
return (False, roll(queue))
return (True, enqueue(dequeue(queue, len(pattern)), replacement))
2024-03-31 15:18:37 -04:00
def define(queue, rules, pattern):
2024-04-01 20:17:53 -04:00
context = match(pattern, queue)
left = context["left"]
right = context["right"]
2024-03-31 15:18:37 -04:00
if right and left:
if len(left) > 1:
2024-04-01 20:17:53 -04:00
left = left[1:][:-1]
2024-03-31 15:18:37 -04:00
if len(right) > 1:
2024-04-01 20:17:53 -04:00
right = right[1:][:-1]
rules.append((left, apply, [right]))
return (True, dequeue(queue, len(construct(pattern, context))))
return (False, roll(queue))
2024-03-31 15:18:37 -04:00
def undefine(queue, rules, pattern):
2024-04-01 20:17:53 -04:00
context = match(pattern, queue)
left = context["left"]
2024-03-31 15:18:37 -04:00
if left:
if len(left) > 1:
2024-04-01 20:17:53 -04:00
left = left[1:][:-1]
2024-03-31 15:18:37 -04:00
for rule, index in zip(rules, range(0, len(rules))):
2024-04-01 20:17:53 -04:00
candidate , _, _ = rule
2024-03-31 15:18:37 -04:00
if candidate == left:
2024-04-01 20:17:53 -04:00
del rules[index]
return (True, dequeue(queue, len(construct(pattern, context))))
return (False, roll(queue))
2024-03-31 15:18:37 -04:00
def add(queue, rules, pattern):
2024-04-01 20:17:53 -04:00
context = match(pattern, queue)
left = context["left"]
right = context["right"]
2024-03-31 15:18:37 -04:00
if left and right:
if left[0][0] == "NUM" and right[0][0] == "NUM":
2024-04-01 20:17:53 -04:00
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))
2024-03-31 15:18:37 -04:00
def subtract(queue, rules, pattern):
2024-04-01 20:17:53 -04:00
context = match(pattern, queue)
left = context["left"]
right = context["right"]
2024-03-31 15:18:37 -04:00
if left and right:
if left[0][0] == "NUM" and right[0][0] == "NUM":
2024-04-01 20:17:53 -04:00
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))
2024-03-31 15:18:37 -04:00
def multiply(queue, rules, pattern):
2024-04-01 20:17:53 -04:00
context = match(pattern, queue)
left = context["left"]
right = context["right"]
2024-03-31 15:18:37 -04:00
if left and right:
if left[0][0] == "NUM" and right[0][0] == "NUM":
2024-04-01 20:17:53 -04:00
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))
2024-03-31 15:18:37 -04:00
def divide(queue, rules, pattern):
2024-04-01 20:17:53 -04:00
context = match(pattern, queue)
left = context["left"]
right = context["right"]
2024-03-31 15:18:37 -04:00
if left and right:
if left[0][0] == "NUM" and right[0][0] == "NUM":
2024-04-01 20:17:53 -04:00
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))
2024-03-31 15:18:37 -04:00
def modulo(queue, rules, pattern):
2024-04-01 20:17:53 -04:00
context = match(pattern, queue)
left = context["left"]
right = context["right"]
2024-03-31 15:18:37 -04:00
if left and right:
if left[0][0] == "NUM" and right[0][0] == "NUM":
2024-04-01 20:17:53 -04:00
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))
2024-03-31 15:18:37 -04:00
def display(queue, rules, pattern):
2024-04-01 20:17:53 -04:00
context = match(pattern, queue)
left = context["left"]
2024-03-31 15:18:37 -04:00
if left:
if left[0][0] == "LIT" or left[0][0] == "NUM":
if left[0][1] == "space":
2024-04-01 20:17:53 -04:00
print(' ', end="")
2024-03-31 15:18:37 -04:00
elif left[0][1] == "newline":
2024-04-01 20:17:53 -04:00
print('\n', end="")
2024-03-31 15:18:37 -04:00
elif left[0][1] == "tab":
2024-04-01 20:17:53 -04:00
print('\t', end="")
2024-03-31 15:18:37 -04:00
else:
2024-04-01 20:17:53 -04:00
print(left[0][1], end="")
return (True, dequeue(queue, len(construct(pattern, context))))
return (False, roll(queue))
2024-03-31 15:18:37 -04:00
def applicable(rules, queue):
2024-04-01 20:17:53 -04:00
results = []
2024-03-31 15:18:37 -04:00
for pattern, operation, parameters in rules:
if match(pattern, queue) != None:
2024-04-01 20:17:53 -04:00
results.append((pattern, operation, parameters))
return results
2024-03-31 15:18:37 -04:00
def pick(list):
if len(list) == 0:
2024-04-01 20:17:53 -04:00
return None
return list[0]
2024-03-31 15:18:37 -04:00
def reconstruct(pattern):
for element in pattern:
if element[0] == "INC":
2024-04-01 20:17:53 -04:00
yield '('
2024-03-31 15:18:37 -04:00
elif element[0] == "DEC":
2024-04-01 20:17:53 -04:00
yield ')'
2024-03-31 15:18:37 -04:00
elif element[0] == "NUM":
2024-04-01 20:17:53 -04:00
yield str(element[1])
2024-03-31 15:18:37 -04:00
elif element[0] == "LIT":
2024-04-01 20:17:53 -04:00
yield element[1]
2024-03-31 15:18:37 -04:00
elif element[0] == "VAR":
2024-04-01 20:17:53 -04:00
yield '?' + element[1]
yield ' '
2024-03-31 15:18:37 -04:00
def number(string):
try:
2024-04-01 20:17:53 -04:00
result = int(string)
return True
2024-03-31 15:18:37 -04:00
except:
2024-04-01 20:17:53 -04:00
return False
2024-03-31 15:18:37 -04:00
def literal(string, index=0):
2024-04-01 20:17:53 -04:00
token = ""
2024-03-31 15:18:37 -04:00
while index != len(string):
2024-04-01 20:17:53 -04:00
character = string[index]
2024-03-31 15:18:37 -04:00
if character in ['(', ')', '{', '}', '[', ']', '?', ' ', '\t', '\n', '\r']:
2024-04-01 20:17:53 -04:00
break
2024-03-31 15:18:37 -04:00
else:
2024-04-01 20:17:53 -04:00
token = token + string[index]
index = index + 1
return (token, index)
2024-03-31 15:18:37 -04:00
def parse(string, index=0):
2024-04-01 20:17:53 -04:00
results = []
2024-03-31 15:18:37 -04:00
while index != len(string):
2024-04-01 20:17:53 -04:00
character = string[index]
2024-03-31 15:18:37 -04:00
if character in ['(', ')', '{', '}', '[', ']', '?', ' ', '\t', '\n', '\r']:
2024-04-01 20:17:53 -04:00
index = index + 1
2024-03-31 15:18:37 -04:00
if character in ['(', '{', '[']:
2024-04-01 20:17:53 -04:00
results.append(["INC"])
2024-03-31 15:18:37 -04:00
elif character in [')', '}', ']']:
2024-04-01 20:17:53 -04:00
results.append(["DEC"])
2024-03-31 15:18:37 -04:00
elif character not in [' ', '\t', '\n', '\r']:
2024-04-01 20:17:53 -04:00
token, index = literal(string, index)
2024-03-31 15:18:37 -04:00
if character == '?':
2024-04-01 20:17:53 -04:00
results.append(["VAR", token])
2024-03-31 15:18:37 -04:00
elif number(token):
2024-04-01 20:17:53 -04:00
results.append(["NUM", int(token)])
2024-03-31 15:18:37 -04:00
else:
2024-04-01 20:17:53 -04:00
results.append(["LIT", token])
return results
2024-03-31 15:18:37 -04:00
@measure
def run(rules, queue, limit=pow(2, 32)):
2024-04-01 20:17:53 -04:00
steps = 0
failures = 0
queue = [["SRT"]] + queue
2024-03-31 15:18:37 -04:00
while failures != len(queue) and steps != limit:
2024-04-01 20:17:53 -04:00
rule = pick(applicable(rules, queue))
2024-03-31 15:18:37 -04:00
if rule == None:
2024-04-01 20:17:53 -04:00
queue = roll(queue)
failures = failures + 1
2024-03-31 15:18:37 -04:00
else:
2024-04-01 20:17:53 -04:00
pattern, operation, parameters = rule
result, queue = operation(queue, rules, pattern, *parameters)
2024-03-31 15:18:37 -04:00
if result == True:
2024-04-01 20:17:53 -04:00
failures = 0
#print("<>: ", inspect(seek(queue, ["SRT"])))
#print("<>: ", inspect(queue))
2024-03-31 15:18:37 -04:00
#input()
2024-04-01 20:17:53 -04:00
steps = steps + 1
2024-03-31 15:18:37 -04:00
if steps == limit:
2024-04-01 20:17:53 -04:00
print("Execution limit reached.")
return queue
2024-03-31 15:18:37 -04:00
def read(file):
try:
with open(file) as file:
2024-04-01 20:17:53 -04:00
content = file.read()
return content
2024-03-31 15:18:37 -04:00
except EnvironmentError:
2024-04-01 20:17:53 -04:00
return None
2024-03-31 15:18:37 -04:00
def inspect(pattern):
2024-04-01 20:17:53 -04:00
return "".join(reconstruct(pattern))
2024-03-31 15:18:37 -04:00
def usage(name):
2024-04-01 20:17:53 -04:00
print(name + ':', "a programming language based on rewriting.")
print("Usage:")
print((' ' * 4) + name, "<file>")
2024-03-31 15:18:37 -04:00
def prompt(prompt):
try:
2024-04-01 20:17:53 -04:00
return input(prompt)
2024-03-31 15:18:37 -04:00
except:
2024-04-01 20:17:53 -04:00
return None
2024-03-31 15:18:37 -04:00
def help():
2024-04-01 20:17:53 -04:00
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.")
2024-03-31 15:18:37 -04:00
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, []),
2024-04-01 20:17:53 -04:00
]
rules = defaults.copy()
2024-03-31 15:18:37 -04:00
if len(sys.argv) >= 2:
2024-04-01 20:17:53 -04:00
content = read(sys.argv[1])
2024-03-31 15:18:37 -04:00
if content == None:
2024-04-01 20:17:53 -04:00
print("No such file.")
return
print("Initializating...")
run(rules, parse(content))
else:
usage(sys.argv[0])
return
print("Modal v0.02")
help()
2024-03-31 15:18:37 -04:00
while True:
2024-04-01 20:17:53 -04:00
input = prompt("::> ")
2024-03-31 15:18:37 -04:00
if input == None:
2024-04-01 20:17:53 -04:00
break
2024-03-31 15:18:37 -04:00
elif parse(input) == parse("rules"):
2024-04-01 20:17:53 -04:00
print("Rules:")
prefix = ' ' * 4
2024-03-31 15:18:37 -04:00
for pattern, operation, parameters in rules:
if operation == apply:
2024-04-01 20:17:53 -04:00
print(prefix + inspect(pattern) + "-> " + inspect(parameters[0]))
2024-03-31 15:18:37 -04:00
elif parse(input) == parse("clear"):
2024-04-01 20:17:53 -04:00
rules = defaults
2024-03-31 15:18:37 -04:00
elif parse(input) == parse("help"):
2024-04-01 20:17:53 -04:00
help()
2024-03-31 15:18:37 -04:00
elif parse(input) == parse("quit") or parse(input) == parse("exit"):
2024-04-01 20:17:53 -04:00
break
2024-03-31 15:18:37 -04:00
else:
2024-04-01 20:17:53 -04:00
print("Reducing...")
print(inspect(seek(run(rules, parse(input)), ["SRT"])))
return
2024-03-31 15:18:37 -04:00
if __name__ == "__main__":
2024-04-01 20:17:53 -04:00
main()