From d0ea50796835ac64e96863af02bdd237a416ae48 Mon Sep 17 00:00:00 2001 From: randomuser Date: Tue, 14 Nov 2023 22:33:34 -0600 Subject: [PATCH] add main files used before version control --- .gitignore | 2 + .vscode/launch.json | 16 +++++ cli.py | 15 ++++ computer-spec/computer.md | 32 +++++++++ console.js | 46 ++++++++++++ data/computer.desmos | 8 +++ data/testing.desmos | 6 ++ parser.py | 148 ++++++++++++++++++++++++++++++++++++++ server.py | 94 ++++++++++++++++++++++++ shell.nix | 7 ++ 10 files changed, 374 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 cli.py create mode 100644 computer-spec/computer.md create mode 100644 console.js create mode 100644 data/computer.desmos create mode 100644 data/testing.desmos create mode 100644 parser.py create mode 100644 server.py create mode 100644 shell.nix diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0540009 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +venv/ \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..b15b3f6 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Python: Current File", + "type": "python", + "request": "launch", + "program": "${file}", + "args": [ + "./data/computer.desmos" + ], + "console": "integratedTerminal", + "justMyCode": true + } + ] +} \ No newline at end of file diff --git a/cli.py b/cli.py new file mode 100644 index 0000000..b468fd9 --- /dev/null +++ b/cli.py @@ -0,0 +1,15 @@ +from server import main +import argparse + +def entry(): + parser = argparse.ArgumentParser( + prog="desmos-sync", + description="Synchronize from local file to Desmos calculator", + ) + + parser.add_argument("filename") + args = parser.parse_args() + main(args.filename) + +if __name__ == "__main__": + entry() \ No newline at end of file diff --git a/computer-spec/computer.md b/computer-spec/computer.md new file mode 100644 index 0000000..766ee6d --- /dev/null +++ b/computer-spec/computer.md @@ -0,0 +1,32 @@ +INSTRUCTION SET FOR DESMOS COMPUTER + +FLAGS +----- + +EQUALS +GREATER THAN +LESS THAN + +INSTRUCTIONS +------------ + +STO ( value, address ) -> store a value into an address +ADD ( addra, addrb, toaddr ) -> add a value from two addresses to a third address +SUB ( addra, addrb, toaddr ) -> subtract a value from two addresses to a third address +MUL ( addra, addrb, toaddr ) -> multiply a value from two addresses to a third address +DIV ( addra, addrb, toaddr ) -> divide a value from two addresses to a third address +CMP ( addra, addrb ) -> compare two values, store result in flags + - set all flags to false + - if equal, set EQUALS + - if addra > addrb, set GREATER THAN + - if addra < addrb, set LESS THAN + +ELD ( address ) -> store the value of EQUALS flag to address (1 if true, 0 if false) +GLD ( address ) -> store the value of GREATER THAN flag to address (1 if true, 0 if false) +LLD ( address ) -> store the value of LESS THAN flag to address (1 if true, 0 if false) + +JMP ( address ) -> change execution to address +BE ( address ) -> branch if equal +BNE ( address ) -> branch if not equal +BG ( address ) -> branch if greater +BL ( address ) -> branch if less diff --git a/console.js b/console.js new file mode 100644 index 0000000..24b4bfb --- /dev/null +++ b/console.js @@ -0,0 +1,46 @@ +function main() { + let socket = new WebSocket("ws://localhost:8765"); + var ids = []; + + socket.onopen = function(e) { + socket.send("client ping"); + } + + socket.onmessage = function(e) { + var message = JSON.parse(e.data); + + console.log(message.message) + if (message.message === "clear") { + for(i in ids) { + console.log("removing") + Calc.removeExpression({ + id: ids[i], + }) + } + + ids = []; + } else if (message.message === "expression") { + Calc.setExpression({ + type: "expression", + latex: message.payload, + id: message.id, + }) + ids.push(message.id) + console.log(ids) + } else if (message.message === "ticker") { + var state = Calc.getState(); + + state.expressions.ticker = { + handlerLatex: message.payload, + minStepLatex: message.rate, + open: true, + }; + + Calc.setState(JSON.stringify(state)) + } else { + console.log("unknown message type.") + } + + console.log(message); + } +} \ No newline at end of file diff --git a/data/computer.desmos b/data/computer.desmos new file mode 100644 index 0000000..a77648f --- /dev/null +++ b/data/computer.desmos @@ -0,0 +1,8 @@ +ticker 1 : loopaction, loop +# Main memory structure +: B=\left[0for i=\left[1...4000\right]\right] + +# Operations on memory +: set(l, i, v) = \left[ifval(l, i, c, v) for c = \left[1...length(l)\right]\right] +: setlistval(i, v) = B -> set(B, i, v) +: incx(i,v) = setlistval(i, B[i]+v) \ No newline at end of file diff --git a/data/testing.desmos b/data/testing.desmos new file mode 100644 index 0000000..b6afd5d --- /dev/null +++ b/data/testing.desmos @@ -0,0 +1,6 @@ +: testing = 32 +: b = testing +: m = 3 +: y = m * x + b +: y = 2m * x + b +: \frac{x^{2+3}}{3}*4*testing \ No newline at end of file diff --git a/parser.py b/parser.py new file mode 100644 index 0000000..1120a7a --- /dev/null +++ b/parser.py @@ -0,0 +1,148 @@ +# desmos reserved keywords. think function names, and other verbs +# like with, etc. +reserved_keywords = "sin cos tan csc sec cot mean median min max quertile quantile stdev stdevp var mad cov covp corr spearman stats count total join sort shuffle unique for histogram dotplot boxplot normaldist tdist poissondist binomialdist uniformdist pdf cdf inversecdf random ttest tscore ittest frac sinh cosh tanh csch sech coth polygon distance midpoint rgb hsv lcm gcd mod ceil floor round sign nPr nCr log with cdot to in length left right" +reserved_keywords = reserved_keywords.split(' ') + +def continue_lines(string): + # if there's a '\' and then a newline, ignore the newline. + return string.replace('\\\n', '') + +def replace_multiplication(string): + return string.replace('*', '\\cdot ') + +def replace_actions(string): + return string.replace(' ->', '\\to ').replace('->', '\\to ') + +def replace_tabs(string): + return string.replace('\t', ' ') + +def split_linewise(string): + return string.split('\n') + +def merge_multiple_spaces(string): + return ' '.join(string.split()) + +def make_spaces_permanant(string): + return string.replace(' ', '\\ ') + +def subscriptize(string): + output = "" + buffer = "" + for char in (string + "\n"): # add a newline so there's always a non-alpha char at the end + if char.isalpha(): + buffer += char + + else: + if buffer: + # check if our buffer is a reserved word; if so, do + # not expand. + if not buffer in reserved_keywords: + if len(buffer) > 1: + output += "{}_{{{}}}".format(buffer[0], buffer[1:]) + else: + output += buffer[0] + else: + output += buffer + + buffer = "" + output += char + + return output.rstrip() + +class Statement: + def __init__(self, commands, latex): + self.commands = commands + self.latex = latex + + def __getitem__(self, item): + try: + return self.commands[item] + except KeyError: + return None + + def __repr__(self): + return "{} : {}".format(str(self.commands), self.latex) + + @classmethod + def from_line(cls, line): + if not line: + return None + + if line[0] == "#": # ignore comments + return None + + splitted = line.split(':') + commands = splitted[0] + latex = ':'.join(splitted[1:]) + + commands = commands.split(' ') + iterator = iter(commands) + commands = dict(zip(iterator, iterator)) + + try: + del commands[''] + except KeyError: + pass + + return cls(commands, latex) + + @classmethod + def from_lines(cls, lines): + output = [] + for line in lines: + output.append(cls.from_line(line)) + + return list( + filter( + lambda item: item is not None, + output + ) + ) + + +class Parser: + """ + Implements the parsing of the Desmos local DSL. + + General syntax: + option1 value1 option2 value2 option3 value3 : latex + + in latex statements, multiplication between variables is *not* implicit. + this is because + `testing` + becomes + `t_{esting}`. + + In order to multiple two variables together, use + `a * b` + instead of + `ab`. + + """ + def __init__(self, file): + self.file = file + self.ast = [] + + def parse(self): + with open(self.file, "r") as f: + text = f.read() + + text = continue_lines(text) + text = replace_multiplication(text) + text = replace_actions(text) + text = replace_tabs(text) + lines = split_linewise(text) + lines = Statement.from_lines(lines) + + for index, line in enumerate(lines): + if not line["comment"]: + # now, remove multiple spaces in lines, replacing them with one. + lines[index].latex = merge_multiple_spaces(line.latex) + + # convert things like testing to t_{esting} + lines[index].latex = subscriptize(line.latex) + + # make the spaces escaped and 'permanant' + lines[index].latex = make_spaces_permanant(line.latex) + + self.ast = lines \ No newline at end of file diff --git a/server.py b/server.py new file mode 100644 index 0000000..4ea0b49 --- /dev/null +++ b/server.py @@ -0,0 +1,94 @@ +from websockets.server import serve +import asyncio +import os +import time +import json +import random +import queue +import functools +from parser import Parser +from watchdog.events import FileSystemEventHandler +from watchdog.events import FileModifiedEvent +from watchdog.observers import Observer + +class FSEHandler(FileSystemEventHandler): + def __init__(self, queue, *args, **kwargs): + self.queue = queue + super().__init__(*args, **kwargs) + + def on_modified(self, event): + self.queue.put("") + +async def serv(websocket, file): + message = await websocket.recv() + lmtime = 0 + epsilon = 0.25 # tweak this to what makes sense. 0.25 seconds makes sense to me. + q = queue.Queue() + + # make it read the file initially + q.put("") + + # setup the watchdog + observer = Observer() + event_handler = FSEHandler(q) + observer.schedule(event_handler, file) + observer.start() + + print("client connected") + + while True: + # there are sometimes multiple writes bundled close together -- so + # debounce them. + q.get() + if time.time() - lmtime < epsilon: + continue + else: + lmtime = time.time() + + parser = Parser(file) + parser.parse() + + await websocket.send( + json.dumps( + { + "message": "clear", + "payload": "none", + } + ) + ) + + for line in parser.ast: + if line["ticker"]: + await websocket.send( + json.dumps( + { + "message": "ticker", + "rate": line.commands["ticker"], + "payload": line.latex, + } + ) + ) + + continue + + if not line["comment"]: + await websocket.send( + json.dumps( + { + "message": "expression", + "id": "placeholder" + str(random.randint(1, 100100)), + "payload": line.latex, + } + ) + ) + +async def start_server(file): + wrapper = functools.partial(serv, file=file) + async with serve(wrapper, "localhost", 8765): + await asyncio.Future() + +def main(file): + asyncio.run(start_server(file)) + +if __name__ == "__main__": + main("data/testing.desmos") \ No newline at end of file diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..9fcbc4b --- /dev/null +++ b/shell.nix @@ -0,0 +1,7 @@ +{ pkgs ? import {} }: +let + my-python-packages = ps: with ps; [ + pip + ]; + my-python = pkgs.python3.withPackages my-python-packages; +in my-python.env \ No newline at end of file