{"id":21885646,"url":"https://github.com/sheraff/calculator","last_synced_at":"2026-05-19T15:03:46.407Z","repository":{"id":81102094,"uuid":"450838853","full_name":"Sheraff/calculator","owner":"Sheraff","description":null,"archived":false,"fork":false,"pushed_at":"2025-02-16T17:13:13.000Z","size":803,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-16T17:24:01.916Z","etag":null,"topics":["abstract-syntax-tree","maths","parser","plugin-system","tokenizer"],"latest_commit_sha":null,"homepage":"https://sheraff.github.io/calculator","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Sheraff.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-01-22T14:22:38.000Z","updated_at":"2025-02-16T17:12:15.000Z","dependencies_parsed_at":null,"dependency_job_id":"e59fdb91-6a83-463d-8858-d7b5f0c0124e","html_url":"https://github.com/Sheraff/calculator","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sheraff%2Fcalculator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sheraff%2Fcalculator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sheraff%2Fcalculator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sheraff%2Fcalculator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Sheraff","download_url":"https://codeload.github.com/Sheraff/calculator/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244894307,"owners_count":20527669,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["abstract-syntax-tree","maths","parser","plugin-system","tokenizer"],"created_at":"2024-11-28T10:28:28.438Z","updated_at":"2026-05-19T15:03:41.364Z","avatar_url":"https://github.com/Sheraff.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Calculator\n\n\u003cimg width=\"770\" alt=\"Screen Shot 2022-06-18 at 22 04 26\" src=\"https://user-images.githubusercontent.com/1325721/174455551-5f660975-0a50-441c-8e50-4f278f3aaf82.png\"\u003e\n\nA build of this project is avaliable @ [sheraff.github.io/calculator](https://sheraff.github.io/calculator/).\n\nOn each user input, a **Web Worker** parses the string into an **Abstract Syntax Tree** (AST) in order to resolve the operations. The parser is built following the **Open-Closed Principle** where functionality is added by **Dependency Injection**.\n\n## Engines\n\n- node: `16.13.2`\n- npm: `8.1.2`\n\n## Scripts\n\n- `npm start` to run locally\n- `npm test` for some Jest coverage of underlying math parser\n\n## Folders\n\n```\n./src\n  |\n  |- /App: main component\n  |\n  |- /assets\n  |      |- /hooks: general purpose React hooks\n  |      |- /scripts: math parsing logic\n  |      |- /styles: global sheet \u0026 sass variables\n  |\n  |- /components\n         |- /Caret: replicate native caret on a single line of text\n         |- /Dynamic: various uses of \u003cValue\u003e based on AST data\n         |- /History: store previous results\n         |- /Input: the \u003cinput\u003e on which this app is based\n         |- /NumPad: keyboard UI, dispatches user input\n         |- /Output: orchestration of \u003cInput\u003e, \u003cParsed\u003e, \u003cResult\u003e\n         |- /Parsed: \"mirror image\" of \u003cInput\u003e but with AST data\n         |- /Result: live result of current \u003cinput\u003e string\n         |- /Value: display AST node, handles text selection \u0026 hover priority\n```\n\n## Easter egg\nUse numpad to input `123456789` to enable / disable the \"free input\" mode. This allows you to test the limits of the `MathParser` class. \n\nWhen using a mouse, you can hover the second line of text to observe how values are parsed and which operation takes priority. Or alternatively, you can use the \u003ckbd\u003e←\u003c/kbd\u003e and \u003ckbd\u003e→\u003c/kbd\u003e keys to move the caret in the *input* (`\u003cInput\u003e` component) and see it mapped to the *output* (`\u003cParsed\u003e` component).\n\nWhen the text overflows its allocated space, both the *input* and the *output* should have their scrolls synced.\n\n## Math Parser\n\n- MathParser/index.js: based on a list of plugins, processes an incoming string to produce an AST (Abstract Syntax Tree). The procedure only has a few steps, but all plugins must implement these steps:\n  1. `tokenize` converts an array of chars into an array of `Token` objects\n  2. `reduce` converts an array of `Token` into a `Node` tree\n  3. `resolve` walks the `Node` tree and computes the numerical value of each `Node`\n  4. `stringify` walks the `Node` tree and computes the display string of each `Node`\n  5. `mapRange` walks the `Node` tree and maps the indexes of the input string to indexes in the output string\n\n- MathParser/plugins: each plugin extends `MathParser/classes/Plugin.js` which implements one function for each of the 5 steps of `MathParser`. A plugin may also override its own `Token` or `Node` type.\n\n## Nice to have\n\n- History of operations: appears after having at least 1 result\n- Use of keyboard: can be unlocked by using the interface to enter `123456789`\n- Dark mode: will respond to browser preferences based on `prefers-color-scheme` query\n- Complex math: each MathParser plugin implements a little bit of math. You can try the following strings: `+, -, *, /, ×, ÷, ^, π, pi, e, gold, phi, ɸ, tau, τ, infinity, ∞, !, ², ³, %, sin, cos, tan, log, ln, sqrt, √, abs, floor, ceil, round, sin⁻¹, cos⁻¹, tan⁻¹, asin, acos, atan`\n- Responsive layout: most phone sizes should work, horizontal or vertical\n- Tests: not a full coverage, but the math parsing logic is backed by Jest, try `npm test`\n\n## Limits\n\n- I did not take the time to eject from Create-React-App and configure the project\n- Factorials of float numbers are not implemented correctly (for example `3.1!`)\n- Very big numbers are not supported\n- In a real project, I might have used an external package for \"float math\"\n- Only tested on latest desktop Chrome and mobile Android\n- Accessibility was not *really* taken into consideration for lack of time\n- Code is only loosely typed using JSDoc\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsheraff%2Fcalculator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsheraff%2Fcalculator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsheraff%2Fcalculator/lists"}