novaweb-compiler.nv
· 3.3 KiB · Text
Raw
Playground
|| :: load entry point into source
:: process novaweb
|:: load entry point into source? :entry point: $char|
:processed: $char
|:: load entry point into source? :processed: $char|
:source: $char
|:: load entry point into source|
|:: process novaweb? :second pass needed:? :processed: $char|
:source: $char
|:: process novaweb? :second pass needed:|
|:: process novaweb? :source: "@@@"|
:: signal a second pass is needed
:: load link :strip left: :strip right:
:: search dictionary
|:: process novaweb? :source: $char|
:processed: $char
|::process novaweb|
:: finished compiling
|:: signal a second pass is needed :second pass needed:?|
|:: signal a second pass is needed|
:second pass needed:
|:: load link? :source: "@@@"? :incoming link: 9 :strip right:?|
|:: load link? :source: "@@@"? :incoming link: 10 :strip right:?|
|:: load link? :source: "@@@"? :incoming link: 13 :strip right:?|
|:: load link? :source: "@@@"? :incoming link: 32 :strip right:?|
|:: load link? :source: "@@@"? :strip right:|
|:: load link? :source: "@@@"? :incoming link: $char|
:link: $char
|:: load link :source: "@@@"|
|:: load link? :source: 9 :strip left:?|
|:: load link? :source: 10 :strip left:?|
|:: load link? :source: 13 :strip left:?|
|:: load link? :source: 32 :strip left:?|
|:: load link? :strip left:|
|:: load link? :source: $char|
:incoming link: $char
|:: search dictionary? :link: $char? :name: $char?|
:: match made
|:: search dictionary? :link: $char? :name:?|
:: search failed
|:: search dictionary? :link: $char? :name: $other-char?|
:: search failed
|:: search dictionary :name:?|
:: search succeeded
|:: search dictionary? :link: $char?|
:: search exhausted
|:: search succeeded|
:: restore name dictionary
:: flush link
:: push code to processed
:: restore code
|:: restore name dictionary? :consumed name:|
:name:
|:: restore name dictionary? :consumed name: $char|
:name: $char
|:: restore name dictionary|
|:: restore code? :consumed code:|
:code:
|:: restore code? :consumed code: $char|
:code: $char
|:: restore code|
|:: flush link? :consumed link: $char|
|:: flush link|
|:: push code to processed? :code: $char|
:processed: $char :consumed code: $char
|:: push code to processed :code:|
:consumed code:
|:: search failed|
:: next name in dictionary
:: next page of code
:: reset link
|:: next name in dictionary :name:|
:consumed name:
|:: next name in dictionary? :name: $char|
:consumed name: $char
|:: next page of code :code:|
:consumed code:
|:: next page of code? :code: $char|
:consumed code: $char
|:: reset link? :consumed link: $char|
:link: $char
|:: reset link|
|:: search exhausted|
:: show error :error message: "No reference found for"
:error message: (58 32)
:: exit
|:: show error? :error message: $char|
:@stdio: write $char
|:: show error? :link: $char|
:@stdio: write $char
|:: show error|
:@stdio: write 10
|:: match made|
:: advance link
:: advance name dictionary
|:: advance link :link: $char|
:consumed link: $char
|:: advance name dictionary :name: $char|
:consumed name: $char
|:: finished compiling? :processed: $char|
:source: $char
|:: finished compiling|
:: print source
|:: print source? :source: $char|
:@stdio: write $char
|:: print source|
| 1 | || :: load entry point into source |
| 2 | :: process novaweb |
| 3 | |
| 4 | |:: load entry point into source? :entry point: $char| |
| 5 | :processed: $char |
| 6 | |:: load entry point into source? :processed: $char| |
| 7 | :source: $char |
| 8 | |:: load entry point into source| |
| 9 | |
| 10 | |:: process novaweb? :second pass needed:? :processed: $char| |
| 11 | :source: $char |
| 12 | |:: process novaweb? :second pass needed:| |
| 13 | |:: process novaweb? :source: "@@@"| |
| 14 | :: signal a second pass is needed |
| 15 | :: load link :strip left: :strip right: |
| 16 | :: search dictionary |
| 17 | |:: process novaweb? :source: $char| |
| 18 | :processed: $char |
| 19 | |::process novaweb| |
| 20 | :: finished compiling |
| 21 | |
| 22 | |
| 23 | |:: signal a second pass is needed :second pass needed:?| |
| 24 | |:: signal a second pass is needed| |
| 25 | :second pass needed: |
| 26 | |
| 27 | |
| 28 | |:: load link? :source: "@@@"? :incoming link: 9 :strip right:?| |
| 29 | |:: load link? :source: "@@@"? :incoming link: 10 :strip right:?| |
| 30 | |:: load link? :source: "@@@"? :incoming link: 13 :strip right:?| |
| 31 | |:: load link? :source: "@@@"? :incoming link: 32 :strip right:?| |
| 32 | |:: load link? :source: "@@@"? :strip right:| |
| 33 | |:: load link? :source: "@@@"? :incoming link: $char| |
| 34 | :link: $char |
| 35 | |:: load link :source: "@@@"| |
| 36 | |
| 37 | |:: load link? :source: 9 :strip left:?| |
| 38 | |:: load link? :source: 10 :strip left:?| |
| 39 | |:: load link? :source: 13 :strip left:?| |
| 40 | |:: load link? :source: 32 :strip left:?| |
| 41 | |:: load link? :strip left:| |
| 42 | |:: load link? :source: $char| |
| 43 | :incoming link: $char |
| 44 | |
| 45 | |
| 46 | |:: search dictionary? :link: $char? :name: $char?| |
| 47 | :: match made |
| 48 | |:: search dictionary? :link: $char? :name:?| |
| 49 | :: search failed |
| 50 | |:: search dictionary? :link: $char? :name: $other-char?| |
| 51 | :: search failed |
| 52 | |:: search dictionary :name:?| |
| 53 | :: search succeeded |
| 54 | |:: search dictionary? :link: $char?| |
| 55 | :: search exhausted |
| 56 | |
| 57 | |:: search succeeded| |
| 58 | :: restore name dictionary |
| 59 | :: flush link |
| 60 | :: push code to processed |
| 61 | :: restore code |
| 62 | |
| 63 | |:: restore name dictionary? :consumed name:| |
| 64 | :name: |
| 65 | |:: restore name dictionary? :consumed name: $char| |
| 66 | :name: $char |
| 67 | |:: restore name dictionary| |
| 68 | |
| 69 | |
| 70 | |:: restore code? :consumed code:| |
| 71 | :code: |
| 72 | |:: restore code? :consumed code: $char| |
| 73 | :code: $char |
| 74 | |:: restore code| |
| 75 | |
| 76 | |
| 77 | |:: flush link? :consumed link: $char| |
| 78 | |:: flush link| |
| 79 | |
| 80 | |
| 81 | |:: push code to processed? :code: $char| |
| 82 | :processed: $char :consumed code: $char |
| 83 | |:: push code to processed :code:| |
| 84 | :consumed code: |
| 85 | |
| 86 | |
| 87 | |:: search failed| |
| 88 | :: next name in dictionary |
| 89 | :: next page of code |
| 90 | :: reset link |
| 91 | |
| 92 | |:: next name in dictionary :name:| |
| 93 | :consumed name: |
| 94 | |:: next name in dictionary? :name: $char| |
| 95 | :consumed name: $char |
| 96 | |
| 97 | |:: next page of code :code:| |
| 98 | :consumed code: |
| 99 | |:: next page of code? :code: $char| |
| 100 | :consumed code: $char |
| 101 | |
| 102 | |:: reset link? :consumed link: $char| |
| 103 | :link: $char |
| 104 | |:: reset link| |
| 105 | |
| 106 | |:: search exhausted| |
| 107 | :: show error :error message: "No reference found for" |
| 108 | :error message: (58 32) |
| 109 | :: exit |
| 110 | |
| 111 | |:: show error? :error message: $char| |
| 112 | :@stdio: write $char |
| 113 | |:: show error? :link: $char| |
| 114 | :@stdio: write $char |
| 115 | |:: show error| |
| 116 | :@stdio: write 10 |
| 117 | |
| 118 | |:: match made| |
| 119 | :: advance link |
| 120 | :: advance name dictionary |
| 121 | |
| 122 | |:: advance link :link: $char| |
| 123 | :consumed link: $char |
| 124 | |:: advance name dictionary :name: $char| |
| 125 | :consumed name: $char |
| 126 | |
| 127 | |:: finished compiling? :processed: $char| |
| 128 | :source: $char |
| 129 | |:: finished compiling| |
| 130 | :: print source |
| 131 | |
| 132 | |:: print source? :source: $char| |
| 133 | :@stdio: write $char |
| 134 | |:: print source| |
novaweb-nova.nv
· 36 KiB · Text
Raw
Playground
^^
~entry point~ "
@@@ Import Python core libraries @@@
@@@ Set up PyGame @@@
@@@ Set up NFC @@@
@@@ The implementation of a Nova parser @@@
@@@ The implementation of a Nova interpreter @@@
@@@ The built-in modules @@@
@@@ Let's include a REPL @@@
@@@ Finally, the main program @@@
"
~name~
"The implementation of a Nova parser"
~name~
~code~
"
@@@ Parse the container format @@@
@@@ Parse the label format @@@
@@@ Parse the pattern format @@@
@@@ Chain these steps together for the parser @@@
"
~code~
~name~
"The built-in modules"
~name~
~code~
"
@@@ The Math Module @@@
@@@ The Arrays Module @@@
@@@ The Graphics Module @@@
@@@ The Midi Module @@@
@@@ The Time Module @@@
@@@ The Stdio Module @@@
@@@ The NFC Module @@@
"
~code~
~name~
"Parse the container format"
~name~
~code~
"
def parse_container(string):
left = []
right = []
target = left
characters = (character for character in list(string) + [None])
delimiter = None
for character in characters:
if character in {' ', '\n', '\r', '\t'}:
continue
delimiter = character
break
if delimiter is None:
return None
for character in characters:
if character in {delimiter, None}:
if target is left:
target = right
else:
target = left
yield (left.copy(), right.copy())
left.clear()
right.clear()
else:
target.append(character)
"
~code~
~name~
"Parse the label format"
~name~
~code~
"
def parse_labels(rules):
left = []
right = []
pair = ([], [])
target = left
delimiter = None
for condition, result in rules:
for side in (condition, result):
characters = (character for character in side + [None])
state = "find delimiter"
for character in characters:
if character == None:
label = "".join(pair[0]).strip()
pattern = "".join(pair[1]).strip()
if not(len(label) == 0 and len(pattern) == 0):
target.append((label, pattern))
pair[0].clear()
pair[1].clear()
break
elif state == "find delimiter":
if character in {' ', '\n', '\r', '\t'}:
continue
delimiter = character
state = "parse label"
elif state == "parse label":
if character == delimiter:
state = "parse pattern"
continue
pair[0].append(character)
elif state == "parse pattern":
if character in {delimiter, None}:
state = "parse label"
label = "".join(pair[0]).strip()
pattern = "".join(pair[1]).strip()
if not(len(label) == 0 and len(pattern) == 0):
target.append((label, pattern))
pair[0].clear()
pair[1].clear()
continue
pair[1].append(character)
if target is left:
target = right
else:
target = left
yield (left.copy(), right.copy())
left.clear()
right.clear()
"
~code~
~name~
"Parse the pattern format"
~name~
~code~
"
def parse_patterns(rules):
for condition, result in rules:
left = []
right = []
preserves = []
for label, pattern in condition:
preserve = False
if pattern.endswith('?'):
pattern = pattern[:-1]
preserve = True
if (pattern.startswith('"') or pattern.startswith("'")) and (pattern.endswith('"') or pattern.endswith("'")):
for character in pattern[1:-1]:
left.append((label, [str(ord(character))]))
if preserve:
right.append((label, [str(ord(character))]))
elif pattern.startswith('(') and pattern.endswith(')'):
for token in pattern[1:-1].split():
left.append((label, [token]))
if preserve:
right.append((label, [token]))
elif pattern.startswith('.'):
for tuple in pattern.split('.'):
tuple = tuple.strip()
if len(tuple) != 0:
left.append((label, tuple.split()))
if preserve:
right.append((label, tuple.split()))
else:
left.append((label, pattern.split()))
if preserve:
preserves.append((label, pattern.split()))
for label, pattern in result:
if (pattern.startswith('"') or pattern.startswith("'")) and (pattern.endswith('"') or pattern.endswith("'")):
for character in pattern[1:-1]:
right.append((label, [str(ord(character))]))
elif pattern.startswith('(') and pattern.endswith(')'):
for token in pattern[1:-1].split():
right.append((label, [token]))
elif pattern.startswith('.'):
for tuple in pattern.split('.'):
tuple = tuple.strip()
if len(tuple) != 0:
right.append((label, tuple.split()))
else:
right.append((label, pattern.split()))
yield (left.copy(), right.copy() + preserves.copy())
left.clear()
right.clear()
preserves.clear()
"
~code~
~name~
"Chain these steps together for the parser"
~name~
~code~
"
def parse(string):
return parse_patterns(parse_labels(parse_container(string)))
"
~code~
~name~
"The implementation of a Nova interpreter"
~name~
~code~
"
def facts(ruleset):
for left, right in ruleset:
if not left:
for element in right:
yield element
def rules(ruleset):
for left, right in ruleset:
if left:
yield (left, right)
def match(pattern, knowledge, variables=None):
if variables is None:
variables = {}
label, elements = pattern
if label not in knowledge or not knowledge[label][0]:
return None
if knowledge[label][1] >= len(knowledge[label][0]):
knowledge[label][1] = 0
return None
top = knowledge[label][0][(-1 - knowledge[label][1])]
len_top = len(top)
if len_top != len(elements):
knowledge[label][1] = 0
return None
for index in range(len_top):
left = elements[index]
right = top[index]
if left[0] == '$':
variable = left[1:]
if variable not in variables:
variables[variable] = right
elif variables[variable] != right:
knowledge[label][1] = 0
return None
elif left != right:
knowledge[label][1] = 0
return None
knowledge[label][1] += 1
return variables
def consume(pattern, knowledge):
if type(pattern) == str:
knowledge[pattern][0].pop()
knowledge[pattern][1] = 0
else:
label, _ = pattern
knowledge[label][0].pop()
knowledge[label][1] = 0
def produce(pattern, knowledge, variables=None):
if variables is None:
variables = {}
label, elements = pattern
len_elements = len(elements)
fact = [None] * len_elements
for index in range(len_elements):
element = elements[index]
if element[0] == '$':
variable = element[1:]
if variable in variables:
element = variables[variable]
fact[index] = element
if label not in knowledge:
knowledge[label] = [[], 0]
knowledge[label][0].append(fact)
knowledge[label][1] = 0
def reset(pattern, knowledge):
label, _ = pattern
if label in knowledge:
knowledge[label][1] = 0
def prepare(ruleset, knowledge=None):
if knowledge == None:
knowledge = {}
for fact in reversed(list(facts(ruleset))):
label, elements = fact
if label not in knowledge:
knowledge[label] = [[], 0]
knowledge[label][0].append(elements)
return knowledge
def print_knowledge(knowledge, debug=0, maximum=10):
for label, facts in knowledge.items():
facts = facts[0]
if not facts:
continue
if not label:
label = ":"
print(label + ': ', end="")
if len(facts) >= maximum and debug % 2:
print("...", len(facts), "...")
continue
if facts:
if len(facts[-1]) == 0:
print("<blank>")
else:
print(" ".join(facts[-1]))
for fact in reversed(facts[:-1]):
if len(fact) == 0:
fact = ["<blank>"]
print(' ' * (len(label) + 1), " ".join(fact))
def step(ruleset, primitives=[], knowledge=None, snapshots=[], debug=0, trace=False):
variables = {}
steps = 0
if debug >= 3:
print_knowledge(knowledge, debug)
print("")
matched = False
for primitive in primitives:
steps += 1
if primitive(ruleset, knowledge, snapshots):
matched = True
break
if matched:
return (True, steps, knowledge)
for left, right in ruleset:
steps += 1
matched = True
variables.clear()
for pattern in left:
if match(pattern, knowledge, variables) is None:
matched = False
for pattern in left:
reset(pattern, knowledge)
break
if matched:
for pattern in left:
consume(pattern, knowledge)
for pattern in reversed(right):
produce(pattern, knowledge, variables)
break
return (matched, steps, knowledge)
def run(ruleset, primitives=[], knowledge=None, debug=0, trace=False):
knowledge = prepare(ruleset, knowledge)
ruleset = list(rules(ruleset))
matched = True
steps = 0
snapshots = []
while matched:
matched, completed, knowledge = step(ruleset, primitives, knowledge, snapshots, debug, trace)
steps += completed
if trace:
snapshots.append(copy.deepcopy(knowledge))
if debug:
print("Took", steps, "steps.")
return (knowledge, snapshots)
def usage(name):
print(name, ":: a nova interpreter")
print("usage:")
print('\t' + name, "<file> [-d] [-r]")
"
~code~
~name~
"The Math Module"
~name~
~code~
"
def math(ruleset, knowledge, snapshots=[], trace=False):
if (variables := match(("@math", ["$operation", "$x", "$y"]), knowledge)) is not None:
operation = variables["operation"]
x = int(variables["x"])
y = int(variables["y"])
if operation == "add":
variables["z"] = str(x + y)
elif operation == "subtract":
variables["z"] = str(x - y)
elif operation == "multiply":
variables["z"] = str(x * y)
elif operation == "divide":
variables["z"] = str(x // y)
elif operation == "modulo":
variables["z"] = str(x % y)
elif operation == "compare":
if x < y:
variables["z"] = "less"
elif x == y:
variables["z"] = "equal"
else:
variables["z"] = "greater"
elif operation == "random":
if y < x:
x, y = y, x
variables["z"] = str(random.randrange(x, y))
consume("@math", knowledge)
produce(("@math", ["$z"]), knowledge, variables)
return True
return False
"
~code~
~name~
"The Arrays Module"
~name~
~code~
"
def arrays():
arrays = {}
def create(ruleset, knowledge, snapshots=[], trace=False):
nonlocal arrays
if (variables := match(("@array", ["create", "$array", "$size"]), knowledge)) is not None:
array = variables["array"]
size = int(variables["size"])
consume("@array", knowledge)
if array in arrays:
produce(("@array", ["$array", "exists"]), knowledge, variables)
else:
arrays[array] = [None] * size
return True
return False
def set(ruleset, knowledge, snapshots=[], trace=False):
nonlocal arrays
if (variables := match(("@array", ["set", "$array", "$index", "$value"]), knowledge)) is not None:
array = variables["array"]
index = int(variables["index"])
value = variables["value"]
consume("@array", knowledge)
if array not in arrays:
produce(("@array", ["$array", "not", "found"]), knowledge, variables)
elif index >= len(arrays[array]):
produce(("@array", ["$index", "out", "of", "bounds", "for", "$array"]), knowledge, variables)
else:
arrays[array][index] = value
return True
return False
def get(ruleset, knowledge, snapshots=[], trace=False):
nonlocal arrays
if (variables := match(("@array", ["get", "$array", "$index"]), knowledge)) is not None:
array = variables["array"]
index = int(variables["index"])
consume("@array", knowledge)
if array not in arrays:
produce(("@array", ["$array", "not", "found"]), knowledge, variables)
elif index >= len(arrays[array]):
produce(("@array", ["$index", "out", "of", "bounds", "for", "$array"]), knowledge, variables)
else:
if arrays[array][index] == None:
produce(("@array", ["$array", "$index"]), knowledge, variables)
else:
variables["value"] = str(arrays[array][index])
produce(("@array", ["$array", "$index", "$value"]), knowledge, variables)
return True
return False
return [
create,
set,
get
]
"
~code~
~name~
"The Graphics Module"
~name~
~code~
"
def graphics():
keys = {}
mouse_buttons = {}
screen = None
pixels = None
clock = None
font = None
resolution = (0, 0)
def poll_input(ruleset, knowledge, snapshots=[]):
nonlocal keys
nonlocal mouse_buttons
if match(("@input", ["poll-input"]), knowledge) is not None:
consume("@input", knowledge)
for event in pygame.event.get():
if event.type == pygame.QUIT:
produce(("@signal", ["quit"]), knowledge)
keys = pygame.key.get_pressed()
mouse_buttons = pygame.mouse.get_pressed()
return True
return False
def check_key(ruleset, knowledge, snapshots=[]):
nonlocal keys
if (variables := match(("@input", ["check-key", "$key"]), knowledge)) is not None:
consume("@input", knowledge)
keycode = pygame.key.key_code(variables["key"])
if keys[keycode]:
produce(("@input", ["key-pressed", "$key"]), knowledge, variables)
else:
produce(("@input", ["key-released", "$key"]), knowledge, variables)
return True
return False
def check_mouse_button(ruleset, knowledge, snapshots=[]):
nonlocal mouse_buttons
if (variables := match(("@input", ["check-mouse-button", "$button"]), knowledge)) is not None:
consume("@input", knowledge)
button = int(variables["button"])
if mouse_buttons[button]:
produce(("@input", ["mouse-button-pressed", "$button"]), knowledge, variables)
else:
produce(("@input", ["mouse-button-released", "$button"]), knowledge, variables)
return True
return False
def get_mouse_position(ruleset, knowledge, snapshots=[]):
if (variables := match(("@input", ["get-mouse-position"]), knowledge)) is not None:
consume("@input", knowledge)
position = pygame.mouse.get_pos()
variables["x"], variables["y"] = (str(position[0]), str(position[1]))
produce(("@input", ["mouse-position", "$x", "$y"]), knowledge, variables)
return True
return False
def set_pixel(ruleset, knowledge, snapshots=[]):
nonlocal pixels
if (variables := match(("@graphics", ["set-pixel", "$x", "$y", "$r", "$g", "$b"]), knowledge)) is not None:
x = int(variables["x"])
y = int(variables["y"])
r = int(variables["r"])
g = int(variables["g"])
b = int(variables["b"])
pixels[x % resolution[0], y % resolution[1]] = (r % 256, g % 256, b % 256)
consume("@graphics", knowledge)
return True
return False
def draw_line(ruleset, knowledge, snapshots=[]):
nonlocal screen
if (variables := match(("@graphics", ["draw-line", "$x1", "$y1", "$x2", "$y2", "$r", "$g", "$b", "$w"]), knowledge)) is not None:
x1 = int(variables["x1"])
y1 = int(variables["y1"])
x2 = int(variables["x2"])
y2 = int(variables["y2"])
r = int(variables["r"])
g = int(variables["g"])
b = int(variables["b"])
w = int(variables["w"])
pygame.draw.line(screen, (r % 256, g % 256, b % 256), (x1, y1), (x2, y2), w)
consume("@graphics", knowledge)
return True
return False
def draw_rect(ruleset, knowledge, snapshots=[]):
nonlocal screen
if (variables := match(("@graphics", ["draw-rect", "$x1", "$y1", "$x2", "$y2", "$r", "$g", "$b", "$w"]), knowledge)) is not None:
x1 = int(variables["x1"])
y1 = int(variables["y1"])
x2 = int(variables["x2"])
y2 = int(variables["y2"])
r = int(variables["r"])
g = int(variables["g"])
b = int(variables["b"])
w = int(variables["w"])
pygame.draw.rect(screen, (r % 256, g % 256, b % 256), (x1, y1, x2, y2), w)
consume("@graphics", knowledge)
return True
return False
def draw_circle(ruleset, knowledge, snapshots=[]):
nonlocal screen
if (variables := match(("@graphics", ["draw-circle", "$x", "$y", "$d", "$r", "$g", "$b", "$w"]), knowledge)) is not None:
x = int(variables["x"])
y = int(variables["y"])
d = int(variables["d"])
r = int(variables["r"])
g = int(variables["g"])
b = int(variables["b"])
w = int(variables["w"])
pygame.draw.circle(screen, (r % 256, g % 256, b % 256), (x, y), d, w)
consume("@graphics", knowledge)
return True
return False
def clear_screen(ruleset, knowledge, snapshots=[]):
nonlocal screen
if (variables := match(("@graphics", ["clear-screen", "$r", "$g", "$b"]), knowledge)) is not None:
r = int(variables["r"])
g = int(variables["g"])
b = int(variables["b"])
screen.fill((r % 256, g % 256, b % 256))
consume("@graphics", knowledge)
return True
return False
def draw_fps(ruleset, knowledge, snapshots=[]):
nonlocal pixels
nonlocal screen
nonlocal clock
nonlocal font
if match(("@graphics", ["draw-fps"]), knowledge) is not None:
if clock is None:
clock = pygame.time.Clock()
clock.tick()
if font is None:
font = pygame.font.SysFont("m3x6", 16)
framerate = clock.get_fps()
if framerate <= 1_000_000:
fps = font.render(str(int(clock.get_fps())) , 1, pygame.Color("RED"))
pixels.close()
screen.blit(fps, (0, 0))
pixels = pygame.PixelArray(screen)
consume("@graphics", knowledge)
return True
return False
def display(ruleset, knowledge, snapshots=[]):
nonlocal pixels
if match(("@graphics", ["display"]), knowledge) is not None:
pixels.close()
pygame.display.flip()
pixels = pygame.PixelArray(screen)
consume("@graphics", knowledge)
return True
return False
def draw_text(ruleset, knowledge, snapshots=[]):
nonlocal font
nonlocal screen
nonlocal pixels
if (variables := match(("@graphics", ["draw-text", "$x", "$y"]), knowledge)) is not None:
x = int(variables["x"])
y = int(variables["y"])
if font is None:
font = pygame.font.SysFont("m3x6", 16)
text = ''.join(chr(int(n[0])) for n in reversed(knowledge["@text to draw"][0]))
knowledge["@text to draw"] = [[], 0]
drawn_text = font.render(text, False, pygame.Color("WHITE"))
pixels.close()
pygame.draw.rect(screen, (0, 0, 0), (0, 48, 64, 64))
screen.blit(drawn_text, (x, y))
pixels = pygame.PixelArray(screen)
consume("@graphics", knowledge)
return True
return False
def set_resolution(ruleset, knowledge, snapshots=[]):
nonlocal screen
nonlocal pixels
nonlocal resolution
if (variables := match(("@graphics", ["set-resolution", "$x", "$y"]), knowledge)) is not None:
if screen is None:
pygame.init()
x = int(variables["x"])
y = int(variables["y"])
screen = pygame.display.set_mode((x, y))
pygame.display.set_caption("Nova")
icon = pygame.Surface((32, 32))
icon.fill("black")
icon.set_colorkey((0, 0, 0))
pygame.display.set_icon(icon)
pixels = pygame.PixelArray(screen)
resolution = (x, y)
consume("@graphics", knowledge)
return True
elif (variables := match(("@graphics", ["set-resolution", "fullscreen", "$x", "$y"]), knowledge)) is not None:
if screen is None:
pygame.init()
x = int(variables["x"])
y = int(variables["y"])
screen = pygame.display.set_mode((x, y), flags=pygame.FULLSCREEN | pygame.SCALED)
pygame.display.set_caption("Nova")
icon = pygame.Surface((32, 32))
icon.fill("black")
icon.set_colorkey((0, 0, 0))
pygame.display.set_icon(icon)
pixels = pygame.PixelArray(screen)
resolution = (x, y)
consume("@graphics", knowledge)
return True
elif (variables := match(("@graphics", ["set-resolution", "fullscreen"]), knowledge)) is not None:
if screen is None:
pygame.init()
screen = pygame.display.set_mode((0, 0), flags=pygame.FULLSCREEN)
pygame.display.set_caption("Nova")
icon = pygame.Surface((32, 32))
icon.fill("black")
icon.set_colorkey((0, 0, 0))
pygame.display.set_icon(icon)
pixels = pygame.PixelArray(screen)
resolution = pygame.display.get_surface().get_size()
consume("@graphics", knowledge)
return True
return False
return [
set_pixel,
draw_line,
draw_rect,
draw_circle,
draw_text,
display,
poll_input,
draw_fps,
clear_screen,
check_key,
check_mouse_button,
get_mouse_position,
set_resolution
]
"
~code~
~name~
"The Midi Module"
~name~
~code~
"
def midi():
output = None
def set_output(ruleset, knowledge, snapshots=[]):
nonlocal output
if (variables := match(("@midi", ["set-output", "$x"]), knowledge)) is not None:
if output is None:
pygame.midi.init()
else:
del output
output = pygame.midi.Output(int(variables["x"]))
consume("@midi", knowledge)
return True
return False
def abort(ruleset, knowledge, snapshots=[]):
nonlocal output
if (variables := match(("@midi", ["abort"]), knowledge)) is not None:
output.abort()
consume("@midi", knowledge)
return True
return False
def set_instrument(ruleset, knowledge, snapshots=[]):
nonlocal output
if (variables := match(("@midi", ["set-instrument", "$x"]), knowledge)) is not None:
output.set_instrument(int(variables["x"]))
consume("@midi", knowledge)
return True
return False
def note(ruleset, knowledge, snapshots=[]):
nonlocal output
if (variables := match(("@midi", ["note", "$x", "$y", "$z", "$w"]), knowledge)) is not None:
state = variables["x"]
note = int(variables["y"])
velocity = int(variables["z"])
channel = int(variables["w"])
if state == "on":
output.note_on(note, velocity, channel)
elif state == "off":
output.note_off(note, velocity, channel)
else:
return False
consume("@midi", knowledge)
return True
elif (variables := match(("@midi", ["note", "$x", "$y", "$z"]), knowledge)) is not None:
state = variables["x"]
note = int(variables["y"])
velocity = int(variables["z"])
if state == "on":
output.note_on(note, velocity)
elif state == "off":
output.note_off(note, velocity)
else:
return False
consume("@midi", knowledge)
return True
return False
return [
set_output,
abort,
set_instrument,
note
]
"
~code~
~name~
"The Time Module"
~name~
~code~
"
def _time():
def sleep(ruleset, knowledge, snapshots=[]):
if (variables := match(("@time", ["sleep", "$x", "$y"]), knowledge)) is not None:
duration = int(variables["x"])
unit = variables["y"]
if unit == "seconds":
time.sleep(duration)
elif unit == "milliseconds":
time.sleep(0.001 * duration)
else:
return False
consume("@time", knowledge)
return True
return False
return [
sleep
]
"
~code~
~name~
"The Stdio Module"
~name~
~code~
"
def stdio():
def read(ruleset, knowledge, snapshots=[]):
if (variables := match(("@stdio", ["read", "$x"]), knowledge)) is not None:
try:
amount = int(variables["x"])
bytes = sys.stdin.read(amount)
consume("@stdio", knowledge)
for byte in reversed(bytes.encode()):
variables["x"] = str(byte)
produce(("@stdio", ["$x"]), knowledge, variables)
return True
except KeyboardInterrupt:
consume("@stdio", knowledge)
return True
except:
pass
elif (variables := match(("@stdio", ["read"]), knowledge)) is not None:
try:
variables["x"] = str(ord(sys.stdin.read(1)))
consume("@stdio", knowledge)
produce(("@stdio", ["$x"]), knowledge, variables)
return True
except KeyboardInterrupt:
consume("@stdio", knowledge)
return True
except:
pass
return False
def write(ruleset, knowledge, snapshots=[]):
if (variables := match(("@stdio", ["write", "$x", "$y"]), knowledge)) is not None:
try:
byte = int(variables["x"])
length = int(variables["y"])
if byte < 0:
sys.stdout.buffer.write(byte.to_bytes(length, signed=True))
else:
sys.stdout.buffer.write(byte.to_bytes(length))
sys.stdout.buffer.flush()
consume("@stdio", knowledge)
variables["x"] = str(length)
produce(("@stdio", ["wrote", "$x"]), knowledge, variables)
return True
except OverflowError:
length = (byte.bit_length() + 7) // 8
if length == 0:
length = 1
if byte < 0:
sys.stdout.buffer.write(byte.to_bytes(length, signed=True))
else:
sys.stdout.buffer.write(byte.to_bytes(length))
sys.stdout.buffer.flush()
consume("@stdio", knowledge)
variables["x"] = str(length)
produce(("@stdio", ["wrote", "$x"]), knowledge, variables)
return True
except:
pass
elif (variables := match(("@stdio", ["write", "$x"]), knowledge)) is not None:
try:
byte = int(variables["x"])
length = (byte.bit_length() + 7) // 8
if length == 0:
length = 1
if byte < 0:
sys.stdout.buffer.write(byte.to_bytes(length, signed=True))
else:
sys.stdout.buffer.write(byte.to_bytes(length))
sys.stdout.buffer.flush()
consume("@stdio", knowledge)
variables["x"] = str(length)
produce(("@stdio", ["wrote", "$x"]), knowledge, variables)
return True
except:
pass
return False
return [
read, write
]
"
~code~
~name~
"The NFC Module"
~name~
~code~
"
def _nfc():
clf = None
last_card = None
def nfc_read_card():
nonlocal clf
payload = []
identifier = None
card_types = [RemoteTarget('106A'), RemoteTarget('106B'), RemoteTarget('212F')]
target = clf.sense(*card_types)
if target is not None:
tag = nfc.tag.activate(clf, target)
if tag is not None and tag.ndef is not None:
for record in tag.ndef.records:
text = zlib.decompress(base64.b64decode(record.text)).decode()
payload += list(parse(text))
identifier = tag.identifier
return (payload, identifier)
def open(ruleset, knowledge, snapshots=[]):
nonlocal clf
if (variables := match(("@nfc", ["open"]), knowledge)) is not None:
try:
consume("@nfc", knowledge)
clf = nfc.ContactlessFrontend('usb')
return True
except:
produce(("@nfc", ["no-device"]), knowledge)
return True
return False
def close(ruleset, knowledge, snapshots=[]):
nonlocal clf
if (variables := match(("@nfc", ["close"]), knowledge)) is not None:
try:
consume("@nfc", knowledge)
clf.close()
return True
except:
produce(("@nfc", ["no-device"]), knowledge)
return True
return False
def read_card(ruleset, knowledge, snapshots=[]):
nonlocal last_card
if (variables := match(("@nfc", ["read-card"]), knowledge)) is not None:
try:
consume("@nfc", knowledge)
(payload, identifier) = nfc_read_card()
variables["x"] = "".join([hex(byte)[2:] for byte in identifier])
last_card = payload
produce(("@nfc", ["card", "$x"]), knowledge, variables)
return True
except:
produce(("@nfc", ["read-failed"]), knowledge)
return True
return False
def load_card_rules(ruleset, knowledge, snapshots=[]):
nonlocal last_card
if (variables := match(("@nfc", ["load-rules"]), knowledge)) is not None:
try:
for rule in rules(reversed(last_card)):
ruleset.insert(0, rule)
consume("@nfc", knowledge)
return True
except:
pass
return False
def load_card_facts(ruleset, knowledge, snapshots=[]):
if (variables := match(("@nfc", ["load-facts"]), knowledge)) is not None:
try:
prepare(last_card, knowledge)
consume("@nfc", knowledge)
return True
except:
pass
return False
return [
open,
close,
read_card,
load_card_rules,
load_card_facts
]
"
~code~
~name~
"Let's include a REPL"
~name~
~code~
"
def repl(rulesets, primitives, debug):
try:
knowledge, _ = run(rulesets)
print_knowledge(knowledge, debug)
current_stack = ""
command = input("::> ")
while command != "@quit":
if command in {"@run", '!'}:
knowledge, _ = run(rulesets, primitives, knowledge)
if debug:
print_knowledge(knowledge, debug)
if match(("@signal", ["quit"]), knowledge) != None:
break
elif command.startswith('@'):
current_stack = command[1:]
elif command.startswith('.'):
command = command[1:]
knowledge, _ = run(list(parse(command)) + list(rules(rulesets)), primitives, knowledge, debug)
if debug:
print_knowledge(knowledge, debug)
if match(("@signal", ["quit"]), knowledge) != None:
break
elif command.startswith(':'):
command = command[1:]
rulesets = list(parse(command)) + rulesets
knowledge, _ = run(rulesets, primitives, knowledge, debug)
if debug:
print_knowledge(knowledge, debug)
if match(("@signal", ["quit"]), knowledge) != None:
break
elif len(command) != 0:
command = "||:" + current_stack + ':' + command
knowledge, _ = run(list(parse(command)) + list(rules(rulesets)), primitives, knowledge, debug)
if debug:
print_knowledge(knowledge, debug)
if match(("@signal", ["quit"]), knowledge) != None:
break
else:
if debug:
print_knowledge(knowledge, debug)
command = input(":" + current_stack + ":> ")
except EOFError:
pass
except KeyboardInterrupt:
pass
"
~code~
~name~
"Import Python core libraries"
~name~
~code~
"import sys, time, random, copy"
~code~
~name~
"Set up PyGame"
~name~
~code~
"
try:
import warnings
warnings.filterwarnings("ignore")
from os import environ
environ['PYGAME_HIDE_SUPPORT_PROMPT'] = '1'
import pygame
import pygame.midi
pygame_supported = True
except:
pygame_supported = False
pass
"
~code~
~name~
"Set up NFC"
~name~
~code~
"
try:
import nfc, ndef, zlib, base64
from nfc.clf import RemoteTarget
nfc_supported = True
except:
nfc_supported = False
pass
"
~code~
~name~
"Finally, the main program"
~name~
~code~
"
def main(name, arguments):
primitives = _time() + [math] + arrays() + stdio()
if pygame_supported:
primitives += graphics() + midi()
if nfc_supported:
primitives += _nfc()
rulesets = []
debug = 0
prompt = False
trace = False
if not arguments:
usage(name)
return
for argument in arguments:
if argument == '-d':
debug += 1
elif argument == '-r':
prompt = True
elif argument == '-t':
trace = True
else:
with open(argument) as file:
rulesets += list(parse(file.read()))
if prompt:
repl(rulesets, primitives, debug)
else:
if len(rulesets) == 0:
usage(name)
return
if debug:
knowledge, snapshots = run(rulesets, primitives, None, debug, trace)
if trace:
for index in range(len(snapshots)):
print("Snapshot", str(index) + ':')
print("----------------")
print_knowledge(snapshots[index], debug)
print("")
else:
print_knowledge(knowledge, debug)
else:
run(rulesets, primitives, None, debug, trace)
if __name__ == "__main__":
main(sys.argv[0], sys.argv[1:])
"
~code~
| 1 | ^^ |
| 2 | ~entry point~ " |
| 3 | @@@ Import Python core libraries @@@ |
| 4 | @@@ Set up PyGame @@@ |
| 5 | @@@ Set up NFC @@@ |
| 6 | @@@ The implementation of a Nova parser @@@ |
| 7 | @@@ The implementation of a Nova interpreter @@@ |
| 8 | @@@ The built-in modules @@@ |
| 9 | @@@ Let's include a REPL @@@ |
| 10 | @@@ Finally, the main program @@@ |
| 11 | " |
| 12 | |
| 13 | ~name~ |
| 14 | "The implementation of a Nova parser" |
| 15 | ~name~ |
| 16 | ~code~ |
| 17 | " |
| 18 | @@@ Parse the container format @@@ |
| 19 | @@@ Parse the label format @@@ |
| 20 | @@@ Parse the pattern format @@@ |
| 21 | @@@ Chain these steps together for the parser @@@ |
| 22 | " |
| 23 | ~code~ |
| 24 | |
| 25 | ~name~ |
| 26 | "The built-in modules" |
| 27 | ~name~ |
| 28 | ~code~ |
| 29 | " |
| 30 | @@@ The Math Module @@@ |
| 31 | @@@ The Arrays Module @@@ |
| 32 | @@@ The Graphics Module @@@ |
| 33 | @@@ The Midi Module @@@ |
| 34 | @@@ The Time Module @@@ |
| 35 | @@@ The Stdio Module @@@ |
| 36 | @@@ The NFC Module @@@ |
| 37 | " |
| 38 | ~code~ |
| 39 | |
| 40 | ~name~ |
| 41 | "Parse the container format" |
| 42 | ~name~ |
| 43 | ~code~ |
| 44 | " |
| 45 | def parse_container(string): |
| 46 | left = [] |
| 47 | right = [] |
| 48 | target = left |
| 49 | characters = (character for character in list(string) + [None]) |
| 50 | delimiter = None |
| 51 | for character in characters: |
| 52 | if character in {' ', '\n', '\r', '\t'}: |
| 53 | continue |
| 54 | delimiter = character |
| 55 | break |
| 56 | if delimiter is None: |
| 57 | return None |
| 58 | for character in characters: |
| 59 | if character in {delimiter, None}: |
| 60 | if target is left: |
| 61 | target = right |
| 62 | else: |
| 63 | target = left |
| 64 | yield (left.copy(), right.copy()) |
| 65 | left.clear() |
| 66 | right.clear() |
| 67 | else: |
| 68 | target.append(character) |
| 69 | " |
| 70 | ~code~ |
| 71 | |
| 72 | ~name~ |
| 73 | "Parse the label format" |
| 74 | ~name~ |
| 75 | ~code~ |
| 76 | " |
| 77 | def parse_labels(rules): |
| 78 | left = [] |
| 79 | right = [] |
| 80 | pair = ([], []) |
| 81 | target = left |
| 82 | delimiter = None |
| 83 | for condition, result in rules: |
| 84 | for side in (condition, result): |
| 85 | characters = (character for character in side + [None]) |
| 86 | state = "find delimiter" |
| 87 | for character in characters: |
| 88 | if character == None: |
| 89 | label = "".join(pair[0]).strip() |
| 90 | pattern = "".join(pair[1]).strip() |
| 91 | if not(len(label) == 0 and len(pattern) == 0): |
| 92 | target.append((label, pattern)) |
| 93 | pair[0].clear() |
| 94 | pair[1].clear() |
| 95 | break |
| 96 | elif state == "find delimiter": |
| 97 | if character in {' ', '\n', '\r', '\t'}: |
| 98 | continue |
| 99 | delimiter = character |
| 100 | state = "parse label" |
| 101 | elif state == "parse label": |
| 102 | if character == delimiter: |
| 103 | state = "parse pattern" |
| 104 | continue |
| 105 | pair[0].append(character) |
| 106 | elif state == "parse pattern": |
| 107 | if character in {delimiter, None}: |
| 108 | state = "parse label" |
| 109 | label = "".join(pair[0]).strip() |
| 110 | pattern = "".join(pair[1]).strip() |
| 111 | if not(len(label) == 0 and len(pattern) == 0): |
| 112 | target.append((label, pattern)) |
| 113 | pair[0].clear() |
| 114 | pair[1].clear() |
| 115 | continue |
| 116 | pair[1].append(character) |
| 117 | if target is left: |
| 118 | target = right |
| 119 | else: |
| 120 | target = left |
| 121 | yield (left.copy(), right.copy()) |
| 122 | left.clear() |
| 123 | right.clear() |
| 124 | " |
| 125 | ~code~ |
| 126 | |
| 127 | ~name~ |
| 128 | "Parse the pattern format" |
| 129 | ~name~ |
| 130 | ~code~ |
| 131 | " |
| 132 | def parse_patterns(rules): |
| 133 | for condition, result in rules: |
| 134 | left = [] |
| 135 | right = [] |
| 136 | preserves = [] |
| 137 | for label, pattern in condition: |
| 138 | preserve = False |
| 139 | if pattern.endswith('?'): |
| 140 | pattern = pattern[:-1] |
| 141 | preserve = True |
| 142 | if (pattern.startswith('"') or pattern.startswith("'")) and (pattern.endswith('"') or pattern.endswith("'")): |
| 143 | for character in pattern[1:-1]: |
| 144 | left.append((label, [str(ord(character))])) |
| 145 | if preserve: |
| 146 | right.append((label, [str(ord(character))])) |
| 147 | elif pattern.startswith('(') and pattern.endswith(')'): |
| 148 | for token in pattern[1:-1].split(): |
| 149 | left.append((label, [token])) |
| 150 | if preserve: |
| 151 | right.append((label, [token])) |
| 152 | elif pattern.startswith('.'): |
| 153 | for tuple in pattern.split('.'): |
| 154 | tuple = tuple.strip() |
| 155 | if len(tuple) != 0: |
| 156 | left.append((label, tuple.split())) |
| 157 | if preserve: |
| 158 | right.append((label, tuple.split())) |
| 159 | else: |
| 160 | left.append((label, pattern.split())) |
| 161 | if preserve: |
| 162 | preserves.append((label, pattern.split())) |
| 163 | for label, pattern in result: |
| 164 | if (pattern.startswith('"') or pattern.startswith("'")) and (pattern.endswith('"') or pattern.endswith("'")): |
| 165 | for character in pattern[1:-1]: |
| 166 | right.append((label, [str(ord(character))])) |
| 167 | elif pattern.startswith('(') and pattern.endswith(')'): |
| 168 | for token in pattern[1:-1].split(): |
| 169 | right.append((label, [token])) |
| 170 | elif pattern.startswith('.'): |
| 171 | for tuple in pattern.split('.'): |
| 172 | tuple = tuple.strip() |
| 173 | if len(tuple) != 0: |
| 174 | right.append((label, tuple.split())) |
| 175 | else: |
| 176 | right.append((label, pattern.split())) |
| 177 | yield (left.copy(), right.copy() + preserves.copy()) |
| 178 | left.clear() |
| 179 | right.clear() |
| 180 | preserves.clear() |
| 181 | " |
| 182 | ~code~ |
| 183 | |
| 184 | ~name~ |
| 185 | "Chain these steps together for the parser" |
| 186 | ~name~ |
| 187 | ~code~ |
| 188 | " |
| 189 | def parse(string): |
| 190 | return parse_patterns(parse_labels(parse_container(string))) |
| 191 | " |
| 192 | ~code~ |
| 193 | |
| 194 | ~name~ |
| 195 | "The implementation of a Nova interpreter" |
| 196 | ~name~ |
| 197 | ~code~ |
| 198 | " |
| 199 | def facts(ruleset): |
| 200 | for left, right in ruleset: |
| 201 | if not left: |
| 202 | for element in right: |
| 203 | yield element |
| 204 | |
| 205 | def rules(ruleset): |
| 206 | for left, right in ruleset: |
| 207 | if left: |
| 208 | yield (left, right) |
| 209 | |
| 210 | def match(pattern, knowledge, variables=None): |
| 211 | if variables is None: |
| 212 | variables = {} |
| 213 | label, elements = pattern |
| 214 | if label not in knowledge or not knowledge[label][0]: |
| 215 | return None |
| 216 | if knowledge[label][1] >= len(knowledge[label][0]): |
| 217 | knowledge[label][1] = 0 |
| 218 | return None |
| 219 | top = knowledge[label][0][(-1 - knowledge[label][1])] |
| 220 | len_top = len(top) |
| 221 | if len_top != len(elements): |
| 222 | knowledge[label][1] = 0 |
| 223 | return None |
| 224 | for index in range(len_top): |
| 225 | left = elements[index] |
| 226 | right = top[index] |
| 227 | if left[0] == '$': |
| 228 | variable = left[1:] |
| 229 | if variable not in variables: |
| 230 | variables[variable] = right |
| 231 | elif variables[variable] != right: |
| 232 | knowledge[label][1] = 0 |
| 233 | return None |
| 234 | elif left != right: |
| 235 | knowledge[label][1] = 0 |
| 236 | return None |
| 237 | knowledge[label][1] += 1 |
| 238 | return variables |
| 239 | |
| 240 | def consume(pattern, knowledge): |
| 241 | if type(pattern) == str: |
| 242 | knowledge[pattern][0].pop() |
| 243 | knowledge[pattern][1] = 0 |
| 244 | else: |
| 245 | label, _ = pattern |
| 246 | knowledge[label][0].pop() |
| 247 | knowledge[label][1] = 0 |
| 248 | |
| 249 | def produce(pattern, knowledge, variables=None): |
| 250 | if variables is None: |
| 251 | variables = {} |
| 252 | label, elements = pattern |
| 253 | len_elements = len(elements) |
| 254 | fact = [None] * len_elements |
| 255 | for index in range(len_elements): |
| 256 | element = elements[index] |
| 257 | if element[0] == '$': |
| 258 | variable = element[1:] |
| 259 | if variable in variables: |
| 260 | element = variables[variable] |
| 261 | fact[index] = element |
| 262 | if label not in knowledge: |
| 263 | knowledge[label] = [[], 0] |
| 264 | knowledge[label][0].append(fact) |
| 265 | knowledge[label][1] = 0 |
| 266 | |
| 267 | def reset(pattern, knowledge): |
| 268 | label, _ = pattern |
| 269 | if label in knowledge: |
| 270 | knowledge[label][1] = 0 |
| 271 | |
| 272 | def prepare(ruleset, knowledge=None): |
| 273 | if knowledge == None: |
| 274 | knowledge = {} |
| 275 | for fact in reversed(list(facts(ruleset))): |
| 276 | label, elements = fact |
| 277 | if label not in knowledge: |
| 278 | knowledge[label] = [[], 0] |
| 279 | knowledge[label][0].append(elements) |
| 280 | return knowledge |
| 281 | |
| 282 | def print_knowledge(knowledge, debug=0, maximum=10): |
| 283 | for label, facts in knowledge.items(): |
| 284 | facts = facts[0] |
| 285 | if not facts: |
| 286 | continue |
| 287 | if not label: |
| 288 | label = ":" |
| 289 | print(label + ': ', end="") |
| 290 | if len(facts) >= maximum and debug % 2: |
| 291 | print("...", len(facts), "...") |
| 292 | continue |
| 293 | if facts: |
| 294 | if len(facts[-1]) == 0: |
| 295 | print("<blank>") |
| 296 | else: |
| 297 | print(" ".join(facts[-1])) |
| 298 | for fact in reversed(facts[:-1]): |
| 299 | if len(fact) == 0: |
| 300 | fact = ["<blank>"] |
| 301 | print(' ' * (len(label) + 1), " ".join(fact)) |
| 302 | |
| 303 | def step(ruleset, primitives=[], knowledge=None, snapshots=[], debug=0, trace=False): |
| 304 | variables = {} |
| 305 | steps = 0 |
| 306 | if debug >= 3: |
| 307 | print_knowledge(knowledge, debug) |
| 308 | print("") |
| 309 | matched = False |
| 310 | for primitive in primitives: |
| 311 | steps += 1 |
| 312 | if primitive(ruleset, knowledge, snapshots): |
| 313 | matched = True |
| 314 | break |
| 315 | if matched: |
| 316 | return (True, steps, knowledge) |
| 317 | for left, right in ruleset: |
| 318 | steps += 1 |
| 319 | matched = True |
| 320 | variables.clear() |
| 321 | for pattern in left: |
| 322 | if match(pattern, knowledge, variables) is None: |
| 323 | matched = False |
| 324 | for pattern in left: |
| 325 | reset(pattern, knowledge) |
| 326 | break |
| 327 | if matched: |
| 328 | for pattern in left: |
| 329 | consume(pattern, knowledge) |
| 330 | for pattern in reversed(right): |
| 331 | produce(pattern, knowledge, variables) |
| 332 | break |
| 333 | return (matched, steps, knowledge) |
| 334 | |
| 335 | def run(ruleset, primitives=[], knowledge=None, debug=0, trace=False): |
| 336 | knowledge = prepare(ruleset, knowledge) |
| 337 | ruleset = list(rules(ruleset)) |
| 338 | matched = True |
| 339 | steps = 0 |
| 340 | snapshots = [] |
| 341 | while matched: |
| 342 | matched, completed, knowledge = step(ruleset, primitives, knowledge, snapshots, debug, trace) |
| 343 | steps += completed |
| 344 | if trace: |
| 345 | snapshots.append(copy.deepcopy(knowledge)) |
| 346 | if debug: |
| 347 | print("Took", steps, "steps.") |
| 348 | return (knowledge, snapshots) |
| 349 | |
| 350 | def usage(name): |
| 351 | print(name, ":: a nova interpreter") |
| 352 | print("usage:") |
| 353 | print('\t' + name, "<file> [-d] [-r]") |
| 354 | " |
| 355 | ~code~ |
| 356 | |
| 357 | ~name~ |
| 358 | "The Math Module" |
| 359 | ~name~ |
| 360 | ~code~ |
| 361 | " |
| 362 | def math(ruleset, knowledge, snapshots=[], trace=False): |
| 363 | if (variables := match(("@math", ["$operation", "$x", "$y"]), knowledge)) is not None: |
| 364 | operation = variables["operation"] |
| 365 | x = int(variables["x"]) |
| 366 | y = int(variables["y"]) |
| 367 | if operation == "add": |
| 368 | variables["z"] = str(x + y) |
| 369 | elif operation == "subtract": |
| 370 | variables["z"] = str(x - y) |
| 371 | elif operation == "multiply": |
| 372 | variables["z"] = str(x * y) |
| 373 | elif operation == "divide": |
| 374 | variables["z"] = str(x // y) |
| 375 | elif operation == "modulo": |
| 376 | variables["z"] = str(x % y) |
| 377 | elif operation == "compare": |
| 378 | if x < y: |
| 379 | variables["z"] = "less" |
| 380 | elif x == y: |
| 381 | variables["z"] = "equal" |
| 382 | else: |
| 383 | variables["z"] = "greater" |
| 384 | elif operation == "random": |
| 385 | if y < x: |
| 386 | x, y = y, x |
| 387 | variables["z"] = str(random.randrange(x, y)) |
| 388 | consume("@math", knowledge) |
| 389 | produce(("@math", ["$z"]), knowledge, variables) |
| 390 | return True |
| 391 | return False |
| 392 | " |
| 393 | ~code~ |
| 394 | |
| 395 | ~name~ |
| 396 | "The Arrays Module" |
| 397 | ~name~ |
| 398 | ~code~ |
| 399 | " |
| 400 | def arrays(): |
| 401 | arrays = {} |
| 402 | def create(ruleset, knowledge, snapshots=[], trace=False): |
| 403 | nonlocal arrays |
| 404 | if (variables := match(("@array", ["create", "$array", "$size"]), knowledge)) is not None: |
| 405 | array = variables["array"] |
| 406 | size = int(variables["size"]) |
| 407 | consume("@array", knowledge) |
| 408 | if array in arrays: |
| 409 | produce(("@array", ["$array", "exists"]), knowledge, variables) |
| 410 | else: |
| 411 | arrays[array] = [None] * size |
| 412 | return True |
| 413 | return False |
| 414 | def set(ruleset, knowledge, snapshots=[], trace=False): |
| 415 | nonlocal arrays |
| 416 | if (variables := match(("@array", ["set", "$array", "$index", "$value"]), knowledge)) is not None: |
| 417 | array = variables["array"] |
| 418 | index = int(variables["index"]) |
| 419 | value = variables["value"] |
| 420 | consume("@array", knowledge) |
| 421 | if array not in arrays: |
| 422 | produce(("@array", ["$array", "not", "found"]), knowledge, variables) |
| 423 | elif index >= len(arrays[array]): |
| 424 | produce(("@array", ["$index", "out", "of", "bounds", "for", "$array"]), knowledge, variables) |
| 425 | else: |
| 426 | arrays[array][index] = value |
| 427 | return True |
| 428 | return False |
| 429 | def get(ruleset, knowledge, snapshots=[], trace=False): |
| 430 | nonlocal arrays |
| 431 | if (variables := match(("@array", ["get", "$array", "$index"]), knowledge)) is not None: |
| 432 | array = variables["array"] |
| 433 | index = int(variables["index"]) |
| 434 | consume("@array", knowledge) |
| 435 | if array not in arrays: |
| 436 | produce(("@array", ["$array", "not", "found"]), knowledge, variables) |
| 437 | elif index >= len(arrays[array]): |
| 438 | produce(("@array", ["$index", "out", "of", "bounds", "for", "$array"]), knowledge, variables) |
| 439 | else: |
| 440 | if arrays[array][index] == None: |
| 441 | produce(("@array", ["$array", "$index"]), knowledge, variables) |
| 442 | else: |
| 443 | variables["value"] = str(arrays[array][index]) |
| 444 | produce(("@array", ["$array", "$index", "$value"]), knowledge, variables) |
| 445 | return True |
| 446 | return False |
| 447 | return [ |
| 448 | create, |
| 449 | set, |
| 450 | get |
| 451 | ] |
| 452 | " |
| 453 | ~code~ |
| 454 | |
| 455 | ~name~ |
| 456 | "The Graphics Module" |
| 457 | ~name~ |
| 458 | ~code~ |
| 459 | " |
| 460 | def graphics(): |
| 461 | keys = {} |
| 462 | mouse_buttons = {} |
| 463 | screen = None |
| 464 | pixels = None |
| 465 | clock = None |
| 466 | font = None |
| 467 | resolution = (0, 0) |
| 468 | def poll_input(ruleset, knowledge, snapshots=[]): |
| 469 | nonlocal keys |
| 470 | nonlocal mouse_buttons |
| 471 | if match(("@input", ["poll-input"]), knowledge) is not None: |
| 472 | consume("@input", knowledge) |
| 473 | for event in pygame.event.get(): |
| 474 | if event.type == pygame.QUIT: |
| 475 | produce(("@signal", ["quit"]), knowledge) |
| 476 | keys = pygame.key.get_pressed() |
| 477 | mouse_buttons = pygame.mouse.get_pressed() |
| 478 | return True |
| 479 | return False |
| 480 | def check_key(ruleset, knowledge, snapshots=[]): |
| 481 | nonlocal keys |
| 482 | if (variables := match(("@input", ["check-key", "$key"]), knowledge)) is not None: |
| 483 | consume("@input", knowledge) |
| 484 | keycode = pygame.key.key_code(variables["key"]) |
| 485 | if keys[keycode]: |
| 486 | produce(("@input", ["key-pressed", "$key"]), knowledge, variables) |
| 487 | else: |
| 488 | produce(("@input", ["key-released", "$key"]), knowledge, variables) |
| 489 | return True |
| 490 | return False |
| 491 | def check_mouse_button(ruleset, knowledge, snapshots=[]): |
| 492 | nonlocal mouse_buttons |
| 493 | if (variables := match(("@input", ["check-mouse-button", "$button"]), knowledge)) is not None: |
| 494 | consume("@input", knowledge) |
| 495 | button = int(variables["button"]) |
| 496 | if mouse_buttons[button]: |
| 497 | produce(("@input", ["mouse-button-pressed", "$button"]), knowledge, variables) |
| 498 | else: |
| 499 | produce(("@input", ["mouse-button-released", "$button"]), knowledge, variables) |
| 500 | return True |
| 501 | return False |
| 502 | def get_mouse_position(ruleset, knowledge, snapshots=[]): |
| 503 | if (variables := match(("@input", ["get-mouse-position"]), knowledge)) is not None: |
| 504 | consume("@input", knowledge) |
| 505 | position = pygame.mouse.get_pos() |
| 506 | variables["x"], variables["y"] = (str(position[0]), str(position[1])) |
| 507 | produce(("@input", ["mouse-position", "$x", "$y"]), knowledge, variables) |
| 508 | return True |
| 509 | return False |
| 510 | def set_pixel(ruleset, knowledge, snapshots=[]): |
| 511 | nonlocal pixels |
| 512 | if (variables := match(("@graphics", ["set-pixel", "$x", "$y", "$r", "$g", "$b"]), knowledge)) is not None: |
| 513 | x = int(variables["x"]) |
| 514 | y = int(variables["y"]) |
| 515 | r = int(variables["r"]) |
| 516 | g = int(variables["g"]) |
| 517 | b = int(variables["b"]) |
| 518 | pixels[x % resolution[0], y % resolution[1]] = (r % 256, g % 256, b % 256) |
| 519 | consume("@graphics", knowledge) |
| 520 | return True |
| 521 | return False |
| 522 | def draw_line(ruleset, knowledge, snapshots=[]): |
| 523 | nonlocal screen |
| 524 | if (variables := match(("@graphics", ["draw-line", "$x1", "$y1", "$x2", "$y2", "$r", "$g", "$b", "$w"]), knowledge)) is not None: |
| 525 | x1 = int(variables["x1"]) |
| 526 | y1 = int(variables["y1"]) |
| 527 | x2 = int(variables["x2"]) |
| 528 | y2 = int(variables["y2"]) |
| 529 | r = int(variables["r"]) |
| 530 | g = int(variables["g"]) |
| 531 | b = int(variables["b"]) |
| 532 | w = int(variables["w"]) |
| 533 | pygame.draw.line(screen, (r % 256, g % 256, b % 256), (x1, y1), (x2, y2), w) |
| 534 | consume("@graphics", knowledge) |
| 535 | return True |
| 536 | return False |
| 537 | def draw_rect(ruleset, knowledge, snapshots=[]): |
| 538 | nonlocal screen |
| 539 | if (variables := match(("@graphics", ["draw-rect", "$x1", "$y1", "$x2", "$y2", "$r", "$g", "$b", "$w"]), knowledge)) is not None: |
| 540 | x1 = int(variables["x1"]) |
| 541 | y1 = int(variables["y1"]) |
| 542 | x2 = int(variables["x2"]) |
| 543 | y2 = int(variables["y2"]) |
| 544 | r = int(variables["r"]) |
| 545 | g = int(variables["g"]) |
| 546 | b = int(variables["b"]) |
| 547 | w = int(variables["w"]) |
| 548 | pygame.draw.rect(screen, (r % 256, g % 256, b % 256), (x1, y1, x2, y2), w) |
| 549 | consume("@graphics", knowledge) |
| 550 | return True |
| 551 | return False |
| 552 | def draw_circle(ruleset, knowledge, snapshots=[]): |
| 553 | nonlocal screen |
| 554 | if (variables := match(("@graphics", ["draw-circle", "$x", "$y", "$d", "$r", "$g", "$b", "$w"]), knowledge)) is not None: |
| 555 | x = int(variables["x"]) |
| 556 | y = int(variables["y"]) |
| 557 | d = int(variables["d"]) |
| 558 | r = int(variables["r"]) |
| 559 | g = int(variables["g"]) |
| 560 | b = int(variables["b"]) |
| 561 | w = int(variables["w"]) |
| 562 | pygame.draw.circle(screen, (r % 256, g % 256, b % 256), (x, y), d, w) |
| 563 | consume("@graphics", knowledge) |
| 564 | return True |
| 565 | return False |
| 566 | def clear_screen(ruleset, knowledge, snapshots=[]): |
| 567 | nonlocal screen |
| 568 | if (variables := match(("@graphics", ["clear-screen", "$r", "$g", "$b"]), knowledge)) is not None: |
| 569 | r = int(variables["r"]) |
| 570 | g = int(variables["g"]) |
| 571 | b = int(variables["b"]) |
| 572 | screen.fill((r % 256, g % 256, b % 256)) |
| 573 | consume("@graphics", knowledge) |
| 574 | return True |
| 575 | return False |
| 576 | def draw_fps(ruleset, knowledge, snapshots=[]): |
| 577 | nonlocal pixels |
| 578 | nonlocal screen |
| 579 | nonlocal clock |
| 580 | nonlocal font |
| 581 | if match(("@graphics", ["draw-fps"]), knowledge) is not None: |
| 582 | if clock is None: |
| 583 | clock = pygame.time.Clock() |
| 584 | clock.tick() |
| 585 | if font is None: |
| 586 | font = pygame.font.SysFont("m3x6", 16) |
| 587 | framerate = clock.get_fps() |
| 588 | if framerate <= 1_000_000: |
| 589 | fps = font.render(str(int(clock.get_fps())) , 1, pygame.Color("RED")) |
| 590 | pixels.close() |
| 591 | screen.blit(fps, (0, 0)) |
| 592 | pixels = pygame.PixelArray(screen) |
| 593 | consume("@graphics", knowledge) |
| 594 | return True |
| 595 | return False |
| 596 | def display(ruleset, knowledge, snapshots=[]): |
| 597 | nonlocal pixels |
| 598 | if match(("@graphics", ["display"]), knowledge) is not None: |
| 599 | pixels.close() |
| 600 | pygame.display.flip() |
| 601 | pixels = pygame.PixelArray(screen) |
| 602 | consume("@graphics", knowledge) |
| 603 | return True |
| 604 | return False |
| 605 | def draw_text(ruleset, knowledge, snapshots=[]): |
| 606 | nonlocal font |
| 607 | nonlocal screen |
| 608 | nonlocal pixels |
| 609 | if (variables := match(("@graphics", ["draw-text", "$x", "$y"]), knowledge)) is not None: |
| 610 | x = int(variables["x"]) |
| 611 | y = int(variables["y"]) |
| 612 | if font is None: |
| 613 | font = pygame.font.SysFont("m3x6", 16) |
| 614 | text = ''.join(chr(int(n[0])) for n in reversed(knowledge["@text to draw"][0])) |
| 615 | knowledge["@text to draw"] = [[], 0] |
| 616 | drawn_text = font.render(text, False, pygame.Color("WHITE")) |
| 617 | pixels.close() |
| 618 | pygame.draw.rect(screen, (0, 0, 0), (0, 48, 64, 64)) |
| 619 | screen.blit(drawn_text, (x, y)) |
| 620 | pixels = pygame.PixelArray(screen) |
| 621 | consume("@graphics", knowledge) |
| 622 | return True |
| 623 | return False |
| 624 | |
| 625 | |
| 626 | def set_resolution(ruleset, knowledge, snapshots=[]): |
| 627 | nonlocal screen |
| 628 | nonlocal pixels |
| 629 | nonlocal resolution |
| 630 | if (variables := match(("@graphics", ["set-resolution", "$x", "$y"]), knowledge)) is not None: |
| 631 | if screen is None: |
| 632 | pygame.init() |
| 633 | x = int(variables["x"]) |
| 634 | y = int(variables["y"]) |
| 635 | screen = pygame.display.set_mode((x, y)) |
| 636 | pygame.display.set_caption("Nova") |
| 637 | icon = pygame.Surface((32, 32)) |
| 638 | icon.fill("black") |
| 639 | icon.set_colorkey((0, 0, 0)) |
| 640 | pygame.display.set_icon(icon) |
| 641 | pixels = pygame.PixelArray(screen) |
| 642 | resolution = (x, y) |
| 643 | consume("@graphics", knowledge) |
| 644 | return True |
| 645 | elif (variables := match(("@graphics", ["set-resolution", "fullscreen", "$x", "$y"]), knowledge)) is not None: |
| 646 | if screen is None: |
| 647 | pygame.init() |
| 648 | x = int(variables["x"]) |
| 649 | y = int(variables["y"]) |
| 650 | screen = pygame.display.set_mode((x, y), flags=pygame.FULLSCREEN | pygame.SCALED) |
| 651 | pygame.display.set_caption("Nova") |
| 652 | icon = pygame.Surface((32, 32)) |
| 653 | icon.fill("black") |
| 654 | icon.set_colorkey((0, 0, 0)) |
| 655 | pygame.display.set_icon(icon) |
| 656 | pixels = pygame.PixelArray(screen) |
| 657 | resolution = (x, y) |
| 658 | consume("@graphics", knowledge) |
| 659 | return True |
| 660 | elif (variables := match(("@graphics", ["set-resolution", "fullscreen"]), knowledge)) is not None: |
| 661 | if screen is None: |
| 662 | pygame.init() |
| 663 | screen = pygame.display.set_mode((0, 0), flags=pygame.FULLSCREEN) |
| 664 | pygame.display.set_caption("Nova") |
| 665 | icon = pygame.Surface((32, 32)) |
| 666 | icon.fill("black") |
| 667 | icon.set_colorkey((0, 0, 0)) |
| 668 | pygame.display.set_icon(icon) |
| 669 | pixels = pygame.PixelArray(screen) |
| 670 | resolution = pygame.display.get_surface().get_size() |
| 671 | consume("@graphics", knowledge) |
| 672 | return True |
| 673 | return False |
| 674 | return [ |
| 675 | set_pixel, |
| 676 | draw_line, |
| 677 | draw_rect, |
| 678 | draw_circle, |
| 679 | draw_text, |
| 680 | display, |
| 681 | poll_input, |
| 682 | draw_fps, |
| 683 | clear_screen, |
| 684 | check_key, |
| 685 | check_mouse_button, |
| 686 | get_mouse_position, |
| 687 | set_resolution |
| 688 | ] |
| 689 | " |
| 690 | ~code~ |
| 691 | ~name~ |
| 692 | "The Midi Module" |
| 693 | ~name~ |
| 694 | ~code~ |
| 695 | " |
| 696 | def midi(): |
| 697 | output = None |
| 698 | def set_output(ruleset, knowledge, snapshots=[]): |
| 699 | nonlocal output |
| 700 | if (variables := match(("@midi", ["set-output", "$x"]), knowledge)) is not None: |
| 701 | if output is None: |
| 702 | pygame.midi.init() |
| 703 | else: |
| 704 | del output |
| 705 | output = pygame.midi.Output(int(variables["x"])) |
| 706 | consume("@midi", knowledge) |
| 707 | return True |
| 708 | return False |
| 709 | def abort(ruleset, knowledge, snapshots=[]): |
| 710 | nonlocal output |
| 711 | if (variables := match(("@midi", ["abort"]), knowledge)) is not None: |
| 712 | output.abort() |
| 713 | consume("@midi", knowledge) |
| 714 | return True |
| 715 | return False |
| 716 | def set_instrument(ruleset, knowledge, snapshots=[]): |
| 717 | nonlocal output |
| 718 | if (variables := match(("@midi", ["set-instrument", "$x"]), knowledge)) is not None: |
| 719 | output.set_instrument(int(variables["x"])) |
| 720 | consume("@midi", knowledge) |
| 721 | return True |
| 722 | return False |
| 723 | def note(ruleset, knowledge, snapshots=[]): |
| 724 | nonlocal output |
| 725 | if (variables := match(("@midi", ["note", "$x", "$y", "$z", "$w"]), knowledge)) is not None: |
| 726 | state = variables["x"] |
| 727 | note = int(variables["y"]) |
| 728 | velocity = int(variables["z"]) |
| 729 | channel = int(variables["w"]) |
| 730 | if state == "on": |
| 731 | output.note_on(note, velocity, channel) |
| 732 | elif state == "off": |
| 733 | output.note_off(note, velocity, channel) |
| 734 | else: |
| 735 | return False |
| 736 | consume("@midi", knowledge) |
| 737 | return True |
| 738 | elif (variables := match(("@midi", ["note", "$x", "$y", "$z"]), knowledge)) is not None: |
| 739 | state = variables["x"] |
| 740 | note = int(variables["y"]) |
| 741 | velocity = int(variables["z"]) |
| 742 | if state == "on": |
| 743 | output.note_on(note, velocity) |
| 744 | elif state == "off": |
| 745 | output.note_off(note, velocity) |
| 746 | else: |
| 747 | return False |
| 748 | consume("@midi", knowledge) |
| 749 | return True |
| 750 | return False |
| 751 | return [ |
| 752 | set_output, |
| 753 | abort, |
| 754 | set_instrument, |
| 755 | note |
| 756 | ] |
| 757 | " |
| 758 | ~code~ |
| 759 | |
| 760 | ~name~ |
| 761 | "The Time Module" |
| 762 | ~name~ |
| 763 | ~code~ |
| 764 | " |
| 765 | def _time(): |
| 766 | def sleep(ruleset, knowledge, snapshots=[]): |
| 767 | if (variables := match(("@time", ["sleep", "$x", "$y"]), knowledge)) is not None: |
| 768 | duration = int(variables["x"]) |
| 769 | unit = variables["y"] |
| 770 | if unit == "seconds": |
| 771 | time.sleep(duration) |
| 772 | elif unit == "milliseconds": |
| 773 | time.sleep(0.001 * duration) |
| 774 | else: |
| 775 | return False |
| 776 | consume("@time", knowledge) |
| 777 | return True |
| 778 | return False |
| 779 | return [ |
| 780 | sleep |
| 781 | ] |
| 782 | " |
| 783 | ~code~ |
| 784 | |
| 785 | ~name~ |
| 786 | "The Stdio Module" |
| 787 | ~name~ |
| 788 | ~code~ |
| 789 | " |
| 790 | def stdio(): |
| 791 | def read(ruleset, knowledge, snapshots=[]): |
| 792 | if (variables := match(("@stdio", ["read", "$x"]), knowledge)) is not None: |
| 793 | try: |
| 794 | amount = int(variables["x"]) |
| 795 | bytes = sys.stdin.read(amount) |
| 796 | consume("@stdio", knowledge) |
| 797 | for byte in reversed(bytes.encode()): |
| 798 | variables["x"] = str(byte) |
| 799 | produce(("@stdio", ["$x"]), knowledge, variables) |
| 800 | return True |
| 801 | except KeyboardInterrupt: |
| 802 | consume("@stdio", knowledge) |
| 803 | return True |
| 804 | except: |
| 805 | pass |
| 806 | elif (variables := match(("@stdio", ["read"]), knowledge)) is not None: |
| 807 | try: |
| 808 | variables["x"] = str(ord(sys.stdin.read(1))) |
| 809 | consume("@stdio", knowledge) |
| 810 | produce(("@stdio", ["$x"]), knowledge, variables) |
| 811 | return True |
| 812 | except KeyboardInterrupt: |
| 813 | consume("@stdio", knowledge) |
| 814 | return True |
| 815 | except: |
| 816 | pass |
| 817 | return False |
| 818 | def write(ruleset, knowledge, snapshots=[]): |
| 819 | if (variables := match(("@stdio", ["write", "$x", "$y"]), knowledge)) is not None: |
| 820 | try: |
| 821 | byte = int(variables["x"]) |
| 822 | length = int(variables["y"]) |
| 823 | if byte < 0: |
| 824 | sys.stdout.buffer.write(byte.to_bytes(length, signed=True)) |
| 825 | else: |
| 826 | sys.stdout.buffer.write(byte.to_bytes(length)) |
| 827 | sys.stdout.buffer.flush() |
| 828 | consume("@stdio", knowledge) |
| 829 | variables["x"] = str(length) |
| 830 | produce(("@stdio", ["wrote", "$x"]), knowledge, variables) |
| 831 | return True |
| 832 | except OverflowError: |
| 833 | length = (byte.bit_length() + 7) // 8 |
| 834 | if length == 0: |
| 835 | length = 1 |
| 836 | if byte < 0: |
| 837 | sys.stdout.buffer.write(byte.to_bytes(length, signed=True)) |
| 838 | else: |
| 839 | sys.stdout.buffer.write(byte.to_bytes(length)) |
| 840 | sys.stdout.buffer.flush() |
| 841 | consume("@stdio", knowledge) |
| 842 | variables["x"] = str(length) |
| 843 | produce(("@stdio", ["wrote", "$x"]), knowledge, variables) |
| 844 | return True |
| 845 | except: |
| 846 | pass |
| 847 | elif (variables := match(("@stdio", ["write", "$x"]), knowledge)) is not None: |
| 848 | try: |
| 849 | byte = int(variables["x"]) |
| 850 | length = (byte.bit_length() + 7) // 8 |
| 851 | if length == 0: |
| 852 | length = 1 |
| 853 | if byte < 0: |
| 854 | sys.stdout.buffer.write(byte.to_bytes(length, signed=True)) |
| 855 | else: |
| 856 | sys.stdout.buffer.write(byte.to_bytes(length)) |
| 857 | sys.stdout.buffer.flush() |
| 858 | consume("@stdio", knowledge) |
| 859 | variables["x"] = str(length) |
| 860 | produce(("@stdio", ["wrote", "$x"]), knowledge, variables) |
| 861 | return True |
| 862 | except: |
| 863 | pass |
| 864 | return False |
| 865 | return [ |
| 866 | read, write |
| 867 | ] |
| 868 | " |
| 869 | ~code~ |
| 870 | |
| 871 | ~name~ |
| 872 | "The NFC Module" |
| 873 | ~name~ |
| 874 | ~code~ |
| 875 | " |
| 876 | def _nfc(): |
| 877 | clf = None |
| 878 | last_card = None |
| 879 | def nfc_read_card(): |
| 880 | nonlocal clf |
| 881 | payload = [] |
| 882 | identifier = None |
| 883 | card_types = [RemoteTarget('106A'), RemoteTarget('106B'), RemoteTarget('212F')] |
| 884 | target = clf.sense(*card_types) |
| 885 | if target is not None: |
| 886 | tag = nfc.tag.activate(clf, target) |
| 887 | if tag is not None and tag.ndef is not None: |
| 888 | for record in tag.ndef.records: |
| 889 | text = zlib.decompress(base64.b64decode(record.text)).decode() |
| 890 | payload += list(parse(text)) |
| 891 | identifier = tag.identifier |
| 892 | return (payload, identifier) |
| 893 | def open(ruleset, knowledge, snapshots=[]): |
| 894 | nonlocal clf |
| 895 | if (variables := match(("@nfc", ["open"]), knowledge)) is not None: |
| 896 | try: |
| 897 | consume("@nfc", knowledge) |
| 898 | clf = nfc.ContactlessFrontend('usb') |
| 899 | return True |
| 900 | except: |
| 901 | produce(("@nfc", ["no-device"]), knowledge) |
| 902 | return True |
| 903 | return False |
| 904 | def close(ruleset, knowledge, snapshots=[]): |
| 905 | nonlocal clf |
| 906 | if (variables := match(("@nfc", ["close"]), knowledge)) is not None: |
| 907 | try: |
| 908 | consume("@nfc", knowledge) |
| 909 | clf.close() |
| 910 | return True |
| 911 | except: |
| 912 | produce(("@nfc", ["no-device"]), knowledge) |
| 913 | return True |
| 914 | return False |
| 915 | def read_card(ruleset, knowledge, snapshots=[]): |
| 916 | nonlocal last_card |
| 917 | if (variables := match(("@nfc", ["read-card"]), knowledge)) is not None: |
| 918 | try: |
| 919 | consume("@nfc", knowledge) |
| 920 | (payload, identifier) = nfc_read_card() |
| 921 | variables["x"] = "".join([hex(byte)[2:] for byte in identifier]) |
| 922 | last_card = payload |
| 923 | produce(("@nfc", ["card", "$x"]), knowledge, variables) |
| 924 | return True |
| 925 | except: |
| 926 | produce(("@nfc", ["read-failed"]), knowledge) |
| 927 | return True |
| 928 | return False |
| 929 | def load_card_rules(ruleset, knowledge, snapshots=[]): |
| 930 | nonlocal last_card |
| 931 | if (variables := match(("@nfc", ["load-rules"]), knowledge)) is not None: |
| 932 | try: |
| 933 | for rule in rules(reversed(last_card)): |
| 934 | ruleset.insert(0, rule) |
| 935 | consume("@nfc", knowledge) |
| 936 | return True |
| 937 | except: |
| 938 | pass |
| 939 | return False |
| 940 | def load_card_facts(ruleset, knowledge, snapshots=[]): |
| 941 | if (variables := match(("@nfc", ["load-facts"]), knowledge)) is not None: |
| 942 | try: |
| 943 | prepare(last_card, knowledge) |
| 944 | consume("@nfc", knowledge) |
| 945 | return True |
| 946 | except: |
| 947 | pass |
| 948 | return False |
| 949 | return [ |
| 950 | open, |
| 951 | close, |
| 952 | read_card, |
| 953 | load_card_rules, |
| 954 | load_card_facts |
| 955 | ] |
| 956 | " |
| 957 | ~code~ |
| 958 | |
| 959 | ~name~ |
| 960 | "Let's include a REPL" |
| 961 | ~name~ |
| 962 | ~code~ |
| 963 | " |
| 964 | def repl(rulesets, primitives, debug): |
| 965 | try: |
| 966 | knowledge, _ = run(rulesets) |
| 967 | print_knowledge(knowledge, debug) |
| 968 | current_stack = "" |
| 969 | command = input("::> ") |
| 970 | while command != "@quit": |
| 971 | if command in {"@run", '!'}: |
| 972 | knowledge, _ = run(rulesets, primitives, knowledge) |
| 973 | if debug: |
| 974 | print_knowledge(knowledge, debug) |
| 975 | if match(("@signal", ["quit"]), knowledge) != None: |
| 976 | break |
| 977 | elif command.startswith('@'): |
| 978 | current_stack = command[1:] |
| 979 | elif command.startswith('.'): |
| 980 | command = command[1:] |
| 981 | knowledge, _ = run(list(parse(command)) + list(rules(rulesets)), primitives, knowledge, debug) |
| 982 | if debug: |
| 983 | print_knowledge(knowledge, debug) |
| 984 | if match(("@signal", ["quit"]), knowledge) != None: |
| 985 | break |
| 986 | elif command.startswith(':'): |
| 987 | command = command[1:] |
| 988 | rulesets = list(parse(command)) + rulesets |
| 989 | knowledge, _ = run(rulesets, primitives, knowledge, debug) |
| 990 | if debug: |
| 991 | print_knowledge(knowledge, debug) |
| 992 | if match(("@signal", ["quit"]), knowledge) != None: |
| 993 | break |
| 994 | elif len(command) != 0: |
| 995 | command = "||:" + current_stack + ':' + command |
| 996 | knowledge, _ = run(list(parse(command)) + list(rules(rulesets)), primitives, knowledge, debug) |
| 997 | if debug: |
| 998 | print_knowledge(knowledge, debug) |
| 999 | if match(("@signal", ["quit"]), knowledge) != None: |
| 1000 | break |
| 1001 | else: |
| 1002 | if debug: |
| 1003 | print_knowledge(knowledge, debug) |
| 1004 | command = input(":" + current_stack + ":> ") |
| 1005 | except EOFError: |
| 1006 | pass |
| 1007 | except KeyboardInterrupt: |
| 1008 | pass |
| 1009 | " |
| 1010 | ~code~ |
| 1011 | |
| 1012 | ~name~ |
| 1013 | "Import Python core libraries" |
| 1014 | ~name~ |
| 1015 | ~code~ |
| 1016 | "import sys, time, random, copy" |
| 1017 | ~code~ |
| 1018 | |
| 1019 | ~name~ |
| 1020 | "Set up PyGame" |
| 1021 | ~name~ |
| 1022 | ~code~ |
| 1023 | " |
| 1024 | try: |
| 1025 | import warnings |
| 1026 | warnings.filterwarnings("ignore") |
| 1027 | from os import environ |
| 1028 | environ['PYGAME_HIDE_SUPPORT_PROMPT'] = '1' |
| 1029 | import pygame |
| 1030 | import pygame.midi |
| 1031 | pygame_supported = True |
| 1032 | except: |
| 1033 | pygame_supported = False |
| 1034 | pass |
| 1035 | " |
| 1036 | ~code~ |
| 1037 | |
| 1038 | ~name~ |
| 1039 | "Set up NFC" |
| 1040 | ~name~ |
| 1041 | ~code~ |
| 1042 | " |
| 1043 | try: |
| 1044 | import nfc, ndef, zlib, base64 |
| 1045 | from nfc.clf import RemoteTarget |
| 1046 | nfc_supported = True |
| 1047 | except: |
| 1048 | nfc_supported = False |
| 1049 | pass |
| 1050 | " |
| 1051 | ~code~ |
| 1052 | |
| 1053 | |
| 1054 | ~name~ |
| 1055 | "Finally, the main program" |
| 1056 | ~name~ |
| 1057 | ~code~ |
| 1058 | " |
| 1059 | def main(name, arguments): |
| 1060 | primitives = _time() + [math] + arrays() + stdio() |
| 1061 | if pygame_supported: |
| 1062 | primitives += graphics() + midi() |
| 1063 | if nfc_supported: |
| 1064 | primitives += _nfc() |
| 1065 | rulesets = [] |
| 1066 | debug = 0 |
| 1067 | prompt = False |
| 1068 | trace = False |
| 1069 | if not arguments: |
| 1070 | usage(name) |
| 1071 | return |
| 1072 | for argument in arguments: |
| 1073 | if argument == '-d': |
| 1074 | debug += 1 |
| 1075 | elif argument == '-r': |
| 1076 | prompt = True |
| 1077 | elif argument == '-t': |
| 1078 | trace = True |
| 1079 | else: |
| 1080 | with open(argument) as file: |
| 1081 | rulesets += list(parse(file.read())) |
| 1082 | if prompt: |
| 1083 | repl(rulesets, primitives, debug) |
| 1084 | else: |
| 1085 | if len(rulesets) == 0: |
| 1086 | usage(name) |
| 1087 | return |
| 1088 | if debug: |
| 1089 | knowledge, snapshots = run(rulesets, primitives, None, debug, trace) |
| 1090 | if trace: |
| 1091 | for index in range(len(snapshots)): |
| 1092 | print("Snapshot", str(index) + ':') |
| 1093 | print("----------------") |
| 1094 | print_knowledge(snapshots[index], debug) |
| 1095 | print("") |
| 1096 | else: |
| 1097 | print_knowledge(knowledge, debug) |
| 1098 | else: |
| 1099 | run(rulesets, primitives, None, debug, trace) |
| 1100 | |
| 1101 | if __name__ == "__main__": |
| 1102 | main(sys.argv[0], sys.argv[1:]) |
| 1103 | " |
| 1104 | ~code~ |