This commit is contained in:
Devine Lu Linvega 2024-03-31 12:18:37 -07:00
commit 9d3384805b
4 changed files with 590 additions and 0 deletions

22
README.md Normal file
View File

@ -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.

3
build.sh Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env sh
python3 ./modal.py $1

421
modal.py Normal file
View File

@ -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, "<file>");
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();

144
prelude.modal Normal file
View File

@ -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;