├── .github └── workflows │ └── main.yml ├── .gitignore ├── LICENSE ├── README.md ├── bun.lock ├── components.json ├── eslint.config.mjs ├── flake.nix ├── index.html ├── package.json ├── patches └── babel-plugin-annotate-pure-calls@0.4.0.patch ├── scratchpad └── tsconfig.json ├── setupTests.ts ├── src ├── Program.ts ├── components │ └── ui │ │ └── textarea.tsx ├── errors │ └── kenneth │ │ ├── eval.ts │ │ └── parse.ts ├── layers │ └── default.ts ├── lib │ └── utils.ts ├── programs │ ├── init.ts │ ├── lexer-state.ts │ ├── next-token.ts │ └── run-and-interpret.ts ├── runtimes │ └── default.ts ├── schemas │ ├── built-in │ │ ├── diff.ts │ │ └── index.ts │ ├── chars │ │ └── next-token.ts │ ├── infix-operator.ts │ ├── nodes │ │ ├── exps │ │ │ ├── array.ts │ │ │ ├── boolean.ts │ │ │ ├── call.ts │ │ │ ├── diff.ts │ │ │ ├── function.ts │ │ │ ├── ident.ts │ │ │ ├── if.ts │ │ │ ├── index.ts │ │ │ ├── infix.ts │ │ │ ├── int.ts │ │ │ ├── prefix.ts │ │ │ ├── str.ts │ │ │ ├── union.ts │ │ │ └── unions │ │ │ │ ├── constant.ts │ │ │ │ ├── int-ident-infix.ts │ │ │ │ └── term.ts │ │ ├── interfaces │ │ │ ├── internal-expression.ts │ │ │ ├── internal-node.ts │ │ │ └── internal-statement.ts │ │ ├── program.ts │ │ ├── stmts │ │ │ ├── block.ts │ │ │ ├── exp.ts │ │ │ ├── let.ts │ │ │ ├── return.ts │ │ │ └── union.ts │ │ └── union.ts │ ├── objs │ │ ├── array.ts │ │ ├── bool.ts │ │ ├── built-in.ts │ │ ├── call.ts │ │ ├── diff-function.ts │ │ ├── error.ts │ │ ├── function.ts │ │ ├── ident.ts │ │ ├── infix.ts │ │ ├── int.ts │ │ ├── null.ts │ │ ├── return.ts │ │ ├── string.ts │ │ ├── union.ts │ │ └── unions │ │ │ └── polynomials.ts │ ├── prefix-operator.ts │ ├── token-types │ │ ├── finite.ts │ │ ├── infinite.ts │ │ └── union.ts │ ├── token │ │ ├── asterisk.ts │ │ ├── bang.ts │ │ ├── base.ts │ │ ├── diff.ts │ │ ├── eq.ts │ │ ├── exponent.ts │ │ ├── false.ts │ │ ├── function-literal.ts │ │ ├── greater-than.ts │ │ ├── grouped.ts │ │ ├── ident.ts │ │ ├── if.ts │ │ ├── int.ts │ │ ├── lbracket.ts │ │ ├── left-paren.ts │ │ ├── less-than.ts │ │ ├── let.ts │ │ ├── minus.ts │ │ ├── not-eq.ts │ │ ├── plus.ts │ │ ├── rbracket.ts │ │ ├── return.ts │ │ ├── slash.ts │ │ ├── string.ts │ │ ├── true.ts │ │ └── unions │ │ │ ├── all.ts │ │ │ ├── boolean.ts │ │ │ ├── parse-fn │ │ │ ├── infix.ts │ │ │ └── prefix.ts │ │ │ ├── parse-statement.ts │ │ │ └── prefix.ts │ └── utils │ │ └── create-token-literal-schema.ts ├── scratch │ ├── nerdamer.ts │ ├── recursive.ts │ ├── trace.ts │ ├── two.ts │ └── yield-undefined.ts ├── services │ ├── ast │ │ └── index.ts │ ├── cli.ts │ ├── diff │ │ ├── helper.ts │ │ └── obj.ts │ ├── evaluator │ │ ├── constants.ts │ │ └── index.ts │ ├── expectations │ │ ├── exp │ │ │ └── eq.ts │ │ └── obj │ │ │ └── eq.ts │ ├── lexer │ │ ├── index.ts │ │ └── state.ts │ ├── math │ │ └── index.ts │ ├── object │ │ ├── builtins.ts │ │ ├── environment.ts │ │ └── index.ts │ ├── parser │ │ ├── constant-folding.ts │ │ ├── index.ts │ │ ├── parsing-functions.ts │ │ ├── precedence.ts │ │ └── state.ts │ ├── repl │ │ ├── constants.ts │ │ └── index.ts │ ├── test.ts │ └── tokens.ts ├── tests │ ├── evaluator │ │ └── utils.ts │ ├── parser │ │ ├── statements │ │ │ ├── let.ts │ │ │ └── return.ts │ │ └── utils │ │ │ ├── test-bool-exp.ts │ │ │ ├── test-identifier.ts │ │ │ ├── test-infix-expression.ts │ │ │ ├── test-int-exp.ts │ │ │ ├── test-literal-expression.ts │ │ │ └── test-str-exp.ts │ └── vitest │ │ ├── eval │ │ └── eval.test.ts │ │ ├── lex │ │ └── lex.test.ts │ │ └── parse │ │ ├── helper.ts │ │ └── parse.test.ts └── vite │ ├── App.css │ ├── App.tsx │ ├── assets │ └── react.svg │ ├── examples.ts │ └── main.tsx ├── tsconfig.base.json ├── tsconfig.build.json ├── tsconfig.json ├── tsconfig.src.json ├── tsconfig.test.json ├── vite.config.ts └── vitest.config.ts /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Check 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | branches: [main] 7 | push: 8 | branches: [main] 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | permissions: 15 | contents: read 16 | 17 | jobs: 18 | # types: 19 | # name: Types 20 | # runs-on: ubuntu-latest 21 | # timeout-minutes: 10 22 | # steps: 23 | # - uses: actions/checkout@v4 24 | # - uses: pnpm/action-setup@v4 25 | # - uses: actions/setup-node@v4 26 | # with: 27 | # node-version: '22' 28 | # - run: bun install 29 | # - run: bun check 30 | 31 | # lint: 32 | # name: Lint 33 | # runs-on: ubuntu-latest 34 | # timeout-minutes: 10 35 | # steps: 36 | # - uses: actions/checkout@v4 37 | # - uses: bun/action-setup@v4 38 | # - uses: actions/setup-node@v4 39 | # with: 40 | # node-version: '22' 41 | # - run: bun install 42 | # - run: bun lint 43 | 44 | test: 45 | name: Test 46 | runs-on: ubuntu-latest 47 | timeout-minutes: 10 48 | steps: 49 | - uses: actions/checkout@v4 50 | - uses: oven-sh/setup-bun@v2 51 | - run: bun install 52 | - run: bun vitest 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # Dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # server 9 | .out 10 | 11 | # Local env files 12 | .env 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | # Testing 19 | coverage 20 | 21 | # Turbo 22 | .turbo 23 | 24 | # Vercel 25 | .vercel 26 | 27 | # Build Outputs 28 | .next/ 29 | out/ 30 | build 31 | dist 32 | 33 | 34 | # Debug 35 | npm-debug.log* 36 | yarn-debug.log* 37 | yarn-error.log* 38 | 39 | # Misc 40 | .DS_Store 41 | *.pem 42 | 43 | .tsbuildinfo/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024-present Andrés Duarte Rengifo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # effect-monkey 2 | 3 | [effect](https://effect.website) implementation of monkey interpreter from [Writing An Interpreter In Go](https://interpreterbook.com) 4 | 5 | with extensions for symbolic differentiation. 6 | 7 | ## Symbolic Differentiation 8 | 9 | - [x] constant rule 10 | - [x] sum rule 11 | - [x] power rule 12 | - [x] product rule 13 | - [x] quotient rule 14 | - [x] chain rule 15 | 16 | ## REPL 17 | 18 | [repl](https://monkey.andres.duarterengifo.com) 19 | 20 | 21 | ## TODO 22 | 23 | - [x] view local traces 24 | - [ ] arrays 25 | - [ ] hashes 26 | - [ ] diff multi-statement functions 27 | - [ ] graph functions 28 | - [x] trig derivatives 29 | - [ ] integrate with chain rule 30 | - [ ] exp/ln derivatives 31 | - [ ] integrate with chain rule 32 | - [ ] partial derivatives 33 | - [ ] pass ident as second arg. 34 | - [ ] release as package 35 | - [ ] errors should error -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "src/vite/App.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { fixupPluginRules } from "@eslint/compat" 2 | import { FlatCompat } from "@eslint/eslintrc" 3 | import js from "@eslint/js" 4 | import tsParser from "@typescript-eslint/parser" 5 | import codegen from "eslint-plugin-codegen" 6 | import _import from "eslint-plugin-import" 7 | import simpleImportSort from "eslint-plugin-simple-import-sort" 8 | import sortDestructureKeys from "eslint-plugin-sort-destructure-keys" 9 | import path from "node:path" 10 | import { fileURLToPath } from "node:url" 11 | 12 | const __filename = fileURLToPath(import.meta.url) 13 | const __dirname = path.dirname(__filename) 14 | const compat = new FlatCompat({ 15 | baseDirectory: __dirname, 16 | recommendedConfig: js.configs.recommended, 17 | allConfig: js.configs.all 18 | }) 19 | 20 | export default [ 21 | { 22 | ignores: ["**/dist", "**/build", "**/docs", "**/*.md"] 23 | }, 24 | ...compat.extends( 25 | "eslint:recommended", 26 | "plugin:@typescript-eslint/eslint-recommended", 27 | "plugin:@typescript-eslint/recommended", 28 | "plugin:@effect/recommended" 29 | ), 30 | { 31 | plugins: { 32 | import: fixupPluginRules(_import), 33 | "sort-destructure-keys": sortDestructureKeys, 34 | "simple-import-sort": simpleImportSort, 35 | codegen 36 | }, 37 | 38 | languageOptions: { 39 | parser: tsParser, 40 | ecmaVersion: 2018, 41 | sourceType: "module" 42 | }, 43 | 44 | settings: { 45 | "import/parsers": { 46 | "@typescript-eslint/parser": [".ts", ".tsx"] 47 | }, 48 | 49 | "import/resolver": { 50 | typescript: { 51 | alwaysTryTypes: true 52 | } 53 | } 54 | }, 55 | 56 | rules: { 57 | "codegen/codegen": "error", 58 | "no-fallthrough": "off", 59 | "no-irregular-whitespace": "off", 60 | "object-shorthand": "error", 61 | "prefer-destructuring": "off", 62 | "sort-imports": "off", 63 | 64 | "no-restricted-syntax": ["error", { 65 | selector: "CallExpression[callee.property.name='push'] > SpreadElement.arguments", 66 | message: "Do not use spread arguments in Array.push" 67 | }], 68 | 69 | "no-unused-vars": "off", 70 | "prefer-rest-params": "off", 71 | "prefer-spread": "off", 72 | "import/first": "error", 73 | "import/newline-after-import": "error", 74 | "import/no-duplicates": "error", 75 | "import/no-unresolved": "off", 76 | "import/order": "off", 77 | "simple-import-sort/imports": "off", 78 | "sort-destructure-keys/sort-destructure-keys": "error", 79 | "deprecation/deprecation": "off", 80 | 81 | "@typescript-eslint/array-type": ["warn", { 82 | default: "generic", 83 | readonly: "generic" 84 | }], 85 | 86 | "@typescript-eslint/member-delimiter-style": 0, 87 | "@typescript-eslint/no-non-null-assertion": "off", 88 | "@typescript-eslint/ban-types": "off", 89 | "@typescript-eslint/no-explicit-any": "off", 90 | "@typescript-eslint/no-empty-interface": "off", 91 | "@typescript-eslint/consistent-type-imports": "warn", 92 | 93 | "@typescript-eslint/no-unused-vars": ["error", { 94 | argsIgnorePattern: "^_", 95 | varsIgnorePattern: "^_" 96 | }], 97 | 98 | "@typescript-eslint/ban-ts-comment": "off", 99 | "@typescript-eslint/camelcase": "off", 100 | "@typescript-eslint/explicit-function-return-type": "off", 101 | "@typescript-eslint/explicit-module-boundary-types": "off", 102 | "@typescript-eslint/interface-name-prefix": "off", 103 | "@typescript-eslint/no-array-constructor": "off", 104 | "@typescript-eslint/no-use-before-define": "off", 105 | "@typescript-eslint/no-namespace": "off", 106 | 107 | "@effect/dprint": ["error", { 108 | config: { 109 | indentWidth: 2, 110 | lineWidth: 120, 111 | semiColons: "asi", 112 | quoteStyle: "alwaysDouble", 113 | trailingCommas: "never", 114 | operatorPosition: "maintain", 115 | "arrowFunction.useParentheses": "force" 116 | } 117 | }] 118 | } 119 | } 120 | ] 121 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; 4 | }; 5 | outputs = {nixpkgs, ...}: let 6 | forAllSystems = function: 7 | nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed 8 | (system: function nixpkgs.legacyPackages.${system}); 9 | in { 10 | formatter = forAllSystems (pkgs: pkgs.alejandra); 11 | devShells = forAllSystems (pkgs: { 12 | default = pkgs.mkShell { 13 | packages = with pkgs; [ 14 | corepack 15 | nodejs_22 16 | # For systems that do not ship with Python by default (required by `node-gyp`) 17 | python3 18 | ]; 19 | }; 20 | }); 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | 14 | 15 | 16 | 17 | effect-monkey 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@a33/kenneth", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "description": "monkey lang in ts/effect", 6 | "publishConfig": { 7 | "access": "public", 8 | "directory": "dist" 9 | }, 10 | "scripts": { 11 | "check": "tsc -b tsconfig.json", 12 | "lint": "eslint \"**/{src,test,examples,scripts,dtslint}/**/*.{ts,mjs}\"", 13 | "lint-fix": "pnpm lint --fix", 14 | "test": "bun test", 15 | "vitest": "vitest", 16 | "coverage": "vitest run --coverage", 17 | "repl": " bun src/services/cli.ts repl", 18 | "dev": "vite dev", 19 | "build": "vite build" 20 | }, 21 | "dependencies": { 22 | "@biomejs/biome": "1.9.4", 23 | "@effect/cli": "0.54.1", 24 | "@effect/opentelemetry": "0.42.7", 25 | "@effect/platform": "0.75.1", 26 | "@effect/platform-bun": "0.55.1", 27 | "@effect/platform-node": "0.71.1", 28 | "@opentelemetry/exporter-trace-otlp-http": "0.57.1", 29 | "@opentelemetry/sdk-metrics": "1.30.1", 30 | "@opentelemetry/sdk-trace-base": "1.30.1", 31 | "@opentelemetry/sdk-trace-node": "1.30.1", 32 | "@opentelemetry/sdk-trace-web": "1.30.1", 33 | "@tailwindcss/vite": "^4.0.6", 34 | "@types/react": "^19.0.9", 35 | "@types/react-dom": "^19.0.3", 36 | "@vitejs/plugin-react-swc": "^3.8.0", 37 | "class-variance-authority": "^0.7.1", 38 | "clsx": "^2.1.1", 39 | "effect": "3.12.7", 40 | "eval": "0.1.8", 41 | "lucide-react": "^0.475.0", 42 | "nerdamer-prime": "^1.2.4", 43 | "react": "^19.0.0", 44 | "react-dom": "^19.0.0", 45 | "tailwind-merge": "^3.0.1", 46 | "tailwindcss-animate": "^1.0.7" 47 | }, 48 | "devDependencies": { 49 | "@babel/cli": "7.26.4", 50 | "@babel/core": "7.26.7", 51 | "@babel/plugin-transform-export-namespace-from": "7.25.9", 52 | "@babel/plugin-transform-modules-commonjs": "7.26.3", 53 | "@effect/build-utils": "0.7.8", 54 | "@effect/eslint-plugin": "0.2.0", 55 | "@effect/language-service": "0.2.0", 56 | "@effect/vitest": "0.17.3", 57 | "@eslint/compat": "1.2.5", 58 | "@eslint/eslintrc": "3.2.0", 59 | "@eslint/js": "9.19.0", 60 | "@types/bun": "1.2.0", 61 | "@types/node": "22.10.10", 62 | "@typescript-eslint/eslint-plugin": "8.21.0", 63 | "@typescript-eslint/parser": "8.21.0", 64 | "@vitest/coverage-v8": "3.0.7", 65 | "autoprefixer": "^10.4.20", 66 | "babel-plugin-annotate-pure-calls": "0.5.0", 67 | "eslint": "9.19.0", 68 | "eslint-import-resolver-typescript": "3.7.0", 69 | "eslint-plugin-codegen": "0.29.0", 70 | "eslint-plugin-import": "2.31.0", 71 | "eslint-plugin-simple-import-sort": "12.1.1", 72 | "eslint-plugin-sort-destructure-keys": "2.0.0", 73 | "postcss": "^8.5.2", 74 | "tailwindcss": "^4.0.6", 75 | "tsx": "4.19.2", 76 | "typescript": "5.7.3", 77 | "vite": "^6.1.0", 78 | "vitest": "3.0.4" 79 | }, 80 | "effect": { 81 | "generateExports": { 82 | "include": ["**/*.ts"] 83 | }, 84 | "generateIndex": { 85 | "include": ["**/*.ts"] 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /patches/babel-plugin-annotate-pure-calls@0.4.0.patch: -------------------------------------------------------------------------------- 1 | diff --git a/lib/index.js b/lib/index.js 2 | index 2182884e21874ebb37261e2375eec08ad956fc9a..ef5630199121c2830756e00c7cc48cf1078c8207 100644 3 | --- a/lib/index.js 4 | +++ b/lib/index.js 5 | @@ -78,7 +78,7 @@ const isInAssignmentContext = path => { 6 | 7 | parentPath = _ref.parentPath; 8 | 9 | - if (parentPath.isVariableDeclaration() || parentPath.isAssignmentExpression()) { 10 | + if (parentPath.isVariableDeclaration() || parentPath.isAssignmentExpression() || parentPath.isClassDeclaration()) { 11 | return true; 12 | } 13 | } while (parentPath !== statement); 14 | -------------------------------------------------------------------------------- /scratchpad/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "noEmit": true, 5 | "declaration": false, 6 | "declarationMap": false, 7 | "composite": false, 8 | "incremental": false 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /setupTests.ts: -------------------------------------------------------------------------------- 1 | import * as it from "@effect/vitest" 2 | 3 | it.addEqualityTesters() 4 | -------------------------------------------------------------------------------- /src/Program.ts: -------------------------------------------------------------------------------- 1 | import * as Effect from "effect/Effect" 2 | 3 | Effect.runPromise(Effect.log("Hello, World!")) 4 | -------------------------------------------------------------------------------- /src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "../../lib/utils" 4 | 5 | const Textarea = React.forwardRef< 6 | HTMLTextAreaElement, 7 | React.ComponentProps<"textarea"> 8 | >(({ className, ...props }, ref) => { 9 | return ( 10 |