Compare commits

...

4 Commits

Author SHA1 Message Date
stupidcomputer 0d73e80c59 add a section on running tests on the project 2024-06-16 20:14:49 -05:00
stupidcomputer e8da284365 add a proper command line interface 2024-06-16 20:12:56 -05:00
stupidcomputer 200d01f9ad remove some vestigial packaging stuf; redo this later 2024-06-16 20:12:56 -05:00
stupidcomputer 38877d36ab add more tests for the ISA; ISA bugfix
added more tests to the ISA -- covers all the instructions except RST
(reset).

fixed a bug in the ISA -- when comparing two numbers, we really compared
their pointers. in C, it's the difference between

```
int a = 10;
int b = 12;

int *ap = &a;
int *bp = &b;

if (ap < bp) { /* do stuff */ }
```

the problem is that you're comparing the pointers, *not* the values they
refer to. it instead should be like

if (*ap < *bp) { /* do stuff */ }

because you need to *dereference* the value before comparing. that's
what happens in cli/data/computer.py now.
2024-06-16 20:12:49 -05:00
9 changed files with 212 additions and 85 deletions

View File

@ -1,15 +1,14 @@
# desmos-computer # desmos-computer
## What is this? ## What is this?
- a client-server architecture for synchronizing a file containing Desmos expressions to a graph, - a client-server architecture for synchronizing a file containing Desmos expressions to a graph, (`cli/lib/server.py` and `cli/lib/clientside.py`)
- a test suite for evaluating the 'correctness' of Desmos expressions, - an instruction set architecture for Turing machines whose cells contain IEEE 754 compliant integers in each cell, (`cli/data/computer.py`)
- an instruction set architecture for Turing machines whose cells contain IEEE 754 compliant integers in each cell, - an assembler for that instruction set architecture, (not yet!)
- an assembler for that instruction set architecture, - and other utilities. (disassembler, etc.)
- and other utilities.
## How does the ISA work? ## How does the ISA work?
The 'CPU', implemented in Desmos, takes in a list (aka an array, Turing tape, etc.) and starts execution at cell 1 (lists have 1-based indexes in Desmos). The 'CPU', implemented in Desmos, takes in a list (aka an array, Turing tape, etc.) and starts execution at cell 1 (lists have 1-based indexes in Desmos).
The list also serves as the memory, as well. (Think like Befunge's `p` command. The list also serves as the memory, as well. (Think like Befunge's `p` command.)
*Todo: disconnect opcode definitions and opcodes from the actual CPU implementation. Because of this, don't rely on this table! Check the implementation in `data/computer.desmos`.* *Todo: disconnect opcode definitions and opcodes from the actual CPU implementation. Because of this, don't rely on this table! Check the implementation in `data/computer.desmos`.*
@ -43,12 +42,21 @@ Things we're optimizing for:
In general: *embed the intelligence into the machine code, **not** the CPU/ISA!* In general: *embed the intelligence into the machine code, **not** the CPU/ISA!*
## Things to do ## Things to do
- [ ] Write a test suite for evaluating arbitrary Desmos expressions and getting their expected outputs. - [x] Write a test suite for the various instruction of the ISA executing *in Desmos*
- [ ] Write an assembler to compile a custom Assembly language to native Desmos list format. - [ ] Write an assembler to compile a custom Assembly language to native Desmos list format.
- [ ] Simplify all this into a command line tool. - [ ] Simplify all this into a command line tool.
- [ ] Simplify the synchronization stack. - [x] Simplify the synchronization stack.
- [ ] Write documentation for all of this. - [ ] Write documentation for all of this.
## Running tests
- Enter the nix-shell, then start a web browser.
- Navigate to `https://desmos.com/calculator`, and open the dev console
- Run `python3 -m cli sync -c` to copy the userscript to your clipboard
- Run the userscript in the console
- Run `python3 -m unittest ./cli/tests/isa.py`, or other test groups if needed
- Keep the Desmos tab focused, as it may impede the ISA testing process
## License ## License
This project is licensed under the AGPLv3. See the `LICENSE` file for more information. This project is licensed under the AGPLv3. See the `LICENSE` file for more information.

View File

@ -2,29 +2,86 @@ from .lib.server import DesmosGraphServer
from .lib.graphparser import DesmosGraph, DesmosGraphOverride from .lib.graphparser import DesmosGraph, DesmosGraphOverride
from .lib.clientside import payload as JSGraphPayload from .lib.clientside import payload as JSGraphPayload
from .tests.isa import test_entry_point from .data.computer import payload as ComputerPayload
from .data.testing import payload as TestingPayload
import pyperclip
import argparse
def handle_sync(parser):
if parser.copy_userscript:
pyperclip.copy(JSGraphPayload)
print("copied userscript to clipboard")
if parser.filename:
graph = DesmosGraph.from_file(parser.filename)
if parser.override:
override = DesmosGraphOverride.from_file(parser.override)
graph.include_override(override)
server = DesmosGraphServer()
server.instructions_to_run = []
server.append_inst({
"type": "insert_graph",
"graph": graph,
})
server.start(no_stop=True)
def handle_data(parser):
if parser.dataname:
if parser.dataname == "computer.desmos":
print(ComputerPayload)
elif parser.dataname == "testing.desmos":
print(TestingPayload)
return
if parser.list:
parsers = ["computer.desmos", "testing.desmos"]
print('\n'.join(parsers))
def main(): def main():
# graph = DesmosGraph.from_file("data/computer.desmos") parser = argparse.ArgumentParser(
# override = DesmosGraphOverride.from_file("test.override") prog="desmosisa",
# description="a smörgåsbord of utilities for desmos, including some implementations of an desmos-based isa",
# graph.include_override(override) )
# server = DesmosGraphServer() subparsers = parser.add_subparsers(dest="subparser_name")
# server.append_inst({ sync_parser = subparsers.add_parser("sync", help="desmos calculator synchronization utilities")
# "type": "insert_graph", sync_parser.add_argument(
# "graph": graph, 'filename',
# }) nargs='?',
# server.append_inst({ help="filename of DesmosExpressions to synchronize with client"
# "type": "test_graph", )
# "graph": graph, sync_parser.add_argument(
# "name": "test and assert addition", '-o', '--override',
# "expectedOutput": [1, 4, 6, 0, 0, 4], help="filename of DesmosOverride file, to override certain expressions in the DesmosExpression file",
# "expression": "B", action="store"
# }) )
# server.start() sync_parser.add_argument(
# print(server.outputs) '-c', '--copy-userscript',
# help="copy the userscript to the clipboard, to be pasted into the JS console within the calculator.",
test_entry_point() action="store_true"
)
data_parser = subparsers.add_parser("data", help="access various prebuilt files")
data_parser.add_argument(
'dataname',
nargs='?',
help='name of the datafile requested'
)
data_parser.add_argument(
'-l', '--list',
help='list available datafiles',
action='store_true'
)
args = parser.parse_args()
if args.subparser_name == "sync":
handle_sync(args)
if args.subparser_name == "data":
handle_data(args)
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@ -22,10 +22,10 @@ id testing : B = []
: idiv(a, b, t_o) = setlistval(t_o, odiv(B[a], B[b])) : idiv(a, b, t_o) = setlistval(t_o, odiv(B[a], B[b]))
: imul(a, b, t_o) = setlistval(t_o, omul(B[a], B[b])) : imul(a, b, t_o) = setlistval(t_o, omul(B[a], B[b]))
: icmp(a, b, z) = { \ : icmp(a, b, z) = { \\
a = b : equals -> 1 , \ B[a] = B[b] : equals -> 1 , \\
a > b : greater -> 1 , \ B[a] > B[b] : greater -> 1 , \\
a < b : less -> 1 \ B[a] < B[b] : less -> 1 \\
} }
: irst(a, b, c) = equals -> 0, greater -> 0, less -> 0 : irst(a, b, c) = equals -> 0, greater -> 0, less -> 0
: ield(addr, b) = setlistval(addr, equals) : ield(addr, b) = setlistval(addr, equals)
@ -40,7 +40,6 @@ id testing : B = []
: isto(v, addr) = setlistval(addr, v) : isto(v, addr) = setlistval(addr, v)
: imov(from, target) = setlistval(target, B[from]) : imov(from, target) = setlistval(target, B[from])
: ipsto(value, ptr) = setlistval(B[ptr], value)
# registers # registers
# instruction pointer # instruction pointer
@ -64,27 +63,26 @@ id testing : B = []
: paramthree = 0 : paramthree = 0
# main execution flows # main execution flows
: load(addr) = jumped -> 0, inst -> B[addr], \ : load(addr) = jumped -> 0, inst -> B[addr], \\
paramone -> B[addr + 1], \ paramone -> B[addr + 1], \\
paramtwo -> B[addr + 2], \ paramtwo -> B[addr + 2], \\
paramthree -> B[addr + 3] paramthree -> B[addr + 3]
: exec = { \ : exec = { \\
inst = sto : isto(paramone, paramtwo), \ inst = sto : isto(paramone, paramtwo), \\
inst = psto : ipsto(paramone, paramtwo), \ inst = mov : imov(paramone, paramtwo), \\
inst = mov : imov(paramone, paramtwo), \ inst = add : iadd(paramone, paramtwo, paramthree), \\
inst = add : iadd(paramone, paramtwo, paramthree), \ inst = cmp : icmp(paramone, paramtwo, paramthree), \\
inst = cmp : icmp(paramone, paramtwo, paramthree), \ inst = eld : ield(paramone, paramtwo), \\
inst = eld : ield(paramone, paramtwo), \ inst = gld : igld(paramone, paramtwo), \\
inst = gld : igld(paramone, paramtwo), \ inst = lld : illd(paramone, paramtwo), \\
inst = lld : illd(paramone, paramtwo), \ inst = jmp : ijmp(paramone, paramtwo, paramthree), \\
inst = jmp : ijmp(paramone, paramtwo, paramthree), \ inst = be : ibe(paramone, paramtwo, paramthree), \\
inst = be : ibe(paramone, paramtwo, paramthree), \ inst = bne : ibne(paramone, paramtwo, paramthree), \\
inst = bne : ibne(paramone, paramtwo, paramthree), \ inst = bg : ibg(paramone, paramtwo, paramthree), \\
inst = bg : ibg(paramone, paramtwo, paramthree), \ inst = bl : ibl(paramone, paramtwo, paramthree), \\
inst = bl : ibl(paramone, paramtwo, paramthree), \ inst = sub : isub(paramone, paramtwo, paramthree), \\
inst = sub : isub(paramone, paramtwo, paramthree), \ inst = mul : imul(paramone, paramtwo, paramthree), \\
inst = mul : imul(paramone, paramtwo, paramthree), \ inst = div : idiv(paramone, paramtwo, paramthree) \\
inst = div : idiv(paramone, paramtwo, paramthree) \
} }
: incip = {jumped = 0 : ip -> ip + instwidth[inst] + 1} : incip = {jumped = 0 : ip -> ip + instwidth[inst] + 1}
@ -97,7 +95,6 @@ id testing : B = []
: main = loopaction, loop : main = loopaction, loop
: sto = 1 : sto = 1
: psto = 16
: mov = 17 : mov = 17
: add = 2 : add = 2
: cmp = 3 : cmp = 3
@ -114,5 +111,5 @@ id testing : B = []
: div = 14 : div = 14
: rst = 15 : rst = 15
: instwidth = [2,3,1,1,1,1,1,1,1,1,3,3,3,3,0,2,2] : instwidth = [2,3,2,1,1,1,1,1,1,1,3,3,3,3,0,2,2]
""" """

View File

@ -4,5 +4,5 @@ payload = """
: m = 3 : m = 3
: y = m * x + b : y = m * x + b
: y = 2m * x + b : y = 2m * x + b
: \frac{x^{2+3}}{3}*4*testing : \\frac{x^{2+3}}{3}*4*testing
""" """

View File

@ -11,6 +11,8 @@ function arraysEqual(a, b) {
return true; return true;
} }
var refresh = true;
function main() { function main() {
let socket = new WebSocket("ws://localhost:8764"); let socket = new WebSocket("ws://localhost:8764");
var toCompare = ""; var toCompare = "";
@ -21,9 +23,11 @@ function main() {
} }
socket.onclose = function(e) { socket.onclose = function(e) {
setTimeout(function() { if (refresh) {
main(); setTimeout(function() {
}, 1000); main();
}, 1000);
}
} }
socket.onmessage = function(e) { socket.onmessage = function(e) {
@ -83,5 +87,5 @@ function main() {
console.log(`[LOG] couldn't parse message ${e.data}`) console.log(`[LOG] couldn't parse message ${e.data}`)
} }
} }
} main(); }
""" """

View File

@ -89,18 +89,25 @@ class DesmosGraphServer:
} }
) )
self.stop.set_result("sotp please!!!!") if self.stop:
self.stop.set_result("sotp please!!!!")
async def main(self, stop): async def main(self, stop):
async with serve(self.ws_main, "localhost", 8764): async with serve(self.ws_main, "localhost", 8764):
self.stop = stop self.stop = stop
await stop if not stop:
await asyncio.Future()
else:
await stop
def start(self): def start(self, no_stop=False):
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
stop = loop.create_future() stop = loop.create_future()
loop.run_until_complete(self.main(stop)) if no_stop:
loop.run_until_complete(self.main(stop=None))
else:
loop.run_until_complete(self.main(stop=stop))
def append_inst(self, inst): def append_inst(self, inst):
self.instructions_to_run.append(inst) self.instructions_to_run.append(inst)

View File

@ -31,7 +31,6 @@ def instruction_test_helper(override_text, expected_output):
}) })
server.start() server.start()
time.sleep(1)
return server.outputs[-1]["output"] == "true" return server.outputs[-1]["output"] == "true"
class ISATest(unittest.TestCase): class ISATest(unittest.TestCase):
@ -82,3 +81,77 @@ class ISATest(unittest.TestCase):
[14, 1, 6, 5, 3.5, 4], [14, 1, 6, 5, 3.5, 4],
) )
) )
def test_jmp(self):
self.assertTrue(
instruction_test_helper(
[7, 10, 0, 0, 0, 0, 0, 0, 0, 1, 3, 3], # jump to addr 10
[7, 10, 3, 0, 0, 0, 0, 0, 0, 1, 3, 3]
)
)
def test_cmp_eq(self):
self.assertTrue(
instruction_test_helper(
# check if 3 is equal to 3. if so, store 3 into addr 3, otherwise store 1 into address 2
[3, 1, 1, 8, 10, 1, 1, 2, 0, 1, 3, 3],
[3, 1, 3, 8, 10, 1, 1, 2, 0, 1, 3, 3],
)
)
def test_cmp_neq(self):
self.assertTrue(
instruction_test_helper(
# check if 3 is equal to 1. if not, store 3 into addr 3, otherwise store 1 into address 2
[3, 1, 2, 9, 10, 1, 1, 2, 0, 1, 3, 3],
[3, 1, 3, 9, 10, 1, 1, 2, 0, 1, 3, 3],
)
)
def test_cmp_gt(self):
self.assertTrue(
instruction_test_helper(
[3, 1, 2, 10, 10, 1, 1, 2, 0, 1, 3, 3],
[3, 1, 3, 10, 10, 1, 1, 2, 0, 1, 3, 3],
)
)
def test_cmp_lt(self):
self.assertTrue(
instruction_test_helper(
[3, 1, 4, 11, 10, 1, 1, 2, 0, 1, 3, 3],
[3, 1, 4, 11, 10, 1, 1, 2, 0, 1, 3, 3],
)
)
def test_eld(self):
self.assertTrue(
instruction_test_helper(
[3, 1, 1, 4, 1],
[1, 1, 1, 4, 1],
)
)
def test_gld(self):
self.assertTrue(
instruction_test_helper(
[3, 4, 1, 5, 1],
[1, 4, 1, 5, 1],
)
)
def test_lld(self):
self.assertTrue(
instruction_test_helper(
[3, 1, 4, 6, 1],
[1, 1, 4, 6, 1],
)
)
def test_mov(self):
self.assertTrue(
instruction_test_helper(
[17, 1, 2],
[17, 17, 2]
)
)

View File

@ -1,3 +0,0 @@
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"

View File

@ -1,16 +0,0 @@
import setuptools
setuptools.setup(
name='desmos-sync',
version='0.1',
author='Ryan Marina',
description='synchronize Desmos expressions between the local filesystem and the web calculator',
packages=["desmossync"],
entry_points = {
"console-scripts": [ "desmos-sync=desmossync.cli.entry" ]
},
install_requires=[
'setuptools',
'websockets',
'watchdog'
]
)