├── .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 |
18 | )
19 | })
20 | Textarea.displayName = "Textarea"
21 |
22 | export { Textarea }
23 |
--------------------------------------------------------------------------------
/src/errors/kenneth/eval.ts:
--------------------------------------------------------------------------------
1 | import { Data } from 'effect'
2 | import type { TokenType } from 'src/schemas/token-types/union'
3 |
4 | export class KennethEvalError extends Data.TaggedError('KennethEvalError')<{
5 | message: string
6 | file?: string
7 | expected?: TokenType
8 | actual?: TokenType
9 | }> {}
10 |
--------------------------------------------------------------------------------
/src/errors/kenneth/parse.ts:
--------------------------------------------------------------------------------
1 | import { Data } from 'effect'
2 | import type { TokenType } from 'src/schemas/token-types/union'
3 |
4 | export class KennethParseError extends Data.TaggedError('KennethParseError')<{
5 | message: string
6 | file?: string
7 | expected?: TokenType
8 | actual?: TokenType
9 | }> {}
10 |
--------------------------------------------------------------------------------
/src/layers/default.ts:
--------------------------------------------------------------------------------
1 | import { Layer } from 'effect'
2 | import { Lexer } from '../services/lexer'
3 | import { LexerStateService } from '../services/lexer/state'
4 | import { Parser } from '../services/parser'
5 | import { ParserStateService } from '../services/parser/state'
6 | import { Evaluator } from '../services/evaluator'
7 |
8 | export const defaultLayer = Layer.mergeAll(
9 | Lexer.Default,
10 | LexerStateService.Default,
11 | Parser.Default,
12 | ParserStateService.Default,
13 | Evaluator.Default,
14 | )
15 |
--------------------------------------------------------------------------------
/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { clsx, type ClassValue } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/src/programs/init.ts:
--------------------------------------------------------------------------------
1 | import { Effect } from 'effect'
2 | import { runPromiseInDefault } from '../runtimes/default'
3 | import { Lexer } from '../services/lexer'
4 |
5 | const initLexerProgram = (input: string) =>
6 | Effect.gen(function* () {
7 | const lexer = yield* Lexer
8 | yield* lexer.init(input)
9 | })
10 |
11 | export const initLexer = (input: string) =>
12 | runPromiseInDefault(initLexerProgram(input))
13 |
--------------------------------------------------------------------------------
/src/programs/lexer-state.ts:
--------------------------------------------------------------------------------
1 | import { Effect } from 'effect'
2 | import { runPromiseInDefault } from '../runtimes/default'
3 | import { LexerStateService } from '../services/lexer/state'
4 |
5 | const lexerStateProgram = Effect.gen(function* () {
6 | const lexerState = yield* LexerStateService
7 | return yield* lexerState.getAll
8 | })
9 |
10 | export const getLexerState = () => runPromiseInDefault(lexerStateProgram)
11 |
--------------------------------------------------------------------------------
/src/programs/next-token.ts:
--------------------------------------------------------------------------------
1 | import { Effect } from 'effect'
2 | import { runPromiseInDefault } from '../runtimes/default'
3 | import { Lexer } from '../services/lexer'
4 |
5 | const nextTokenProgram = Effect.gen(function* () {
6 | const lexer = yield* Lexer
7 | return yield* lexer.nextToken
8 | })
9 |
10 | export const nextToken = () => runPromiseInDefault(nextTokenProgram)
11 |
--------------------------------------------------------------------------------
/src/programs/run-and-interpret.ts:
--------------------------------------------------------------------------------
1 | import { ErrorObj } from "@/schemas/objs/error";
2 | import { Effect } from "effect";
3 | import { runPromiseInDefault } from "../runtimes/default";
4 | import { Evaluator } from "../services/evaluator";
5 |
6 | const runAndInterpretProgram = (input: string) =>
7 | Effect.gen(function* () {
8 | const evaluator = yield* Evaluator;
9 | return yield* evaluator.runAndInterpret(input);
10 | });
11 |
12 | export const runAndInterpret = (input: string) =>
13 | runPromiseInDefault(
14 | runAndInterpretProgram(input).pipe(
15 | Effect.catchAll((error) => {
16 | return Effect.succeed(ErrorObj.make({ message: error.message }));
17 | }),
18 | ),
19 | );
20 |
--------------------------------------------------------------------------------
/src/runtimes/default.ts:
--------------------------------------------------------------------------------
1 | import { type Effect, ManagedRuntime } from 'effect'
2 | import type { Lexer } from '../services/lexer'
3 | import { defaultLayer } from '../layers/default'
4 | import type { LexerStateService } from '../services/lexer/state'
5 | import type { Evaluator } from '../services/evaluator'
6 |
7 | export const runtimeDefault = ManagedRuntime.make(defaultLayer)
8 |
9 | export const runPromiseInDefault = (
10 | eff: Effect.Effect,
11 | ) => runtimeDefault.runPromise(eff)
12 |
--------------------------------------------------------------------------------
/src/schemas/built-in/diff.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 |
3 | // would be nice to express this as a subset of builtin functions
4 | // to at the type level avoid adding something here that isn't in builtin
5 | export const BuiltInDiffFunc = Schema.Literal("sin", "cos", "tan", "ln", "exp");
6 |
7 | export type DiffFunction = typeof BuiltInDiffFunc.Type;
8 |
--------------------------------------------------------------------------------
/src/schemas/built-in/index.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 |
3 | export const BuiltInFunc = Schema.Literal(
4 | "len",
5 | "diff",
6 | "sin",
7 | "cos",
8 | "tan",
9 | "ln",
10 | "exp",
11 | "pi",
12 | "e",
13 | );
14 |
15 | export type BuiltInFunc = typeof BuiltInFunc.Type;
16 |
--------------------------------------------------------------------------------
/src/schemas/chars/next-token.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 | import { TokenType } from "../token-types/union";
3 |
4 | // I'm conviced this is just finite-tokentypes
5 | export const lexerSymbolSchema = Schema.Literal(
6 | TokenType.ASSIGN,
7 | TokenType.BANG,
8 | TokenType.MINUS,
9 | TokenType.EXPONENT,
10 | TokenType.SLASH,
11 | TokenType.ASTERISK,
12 | TokenType.LT,
13 | TokenType.GT,
14 | TokenType.SEMICOLON,
15 | TokenType.LPAREN,
16 | TokenType.RPAREN,
17 | TokenType.COMMA,
18 | TokenType.PLUS,
19 | TokenType.LBRACE,
20 | TokenType.RBRACE,
21 | TokenType.EOF,
22 | TokenType.QUOTE,
23 | TokenType.LBRACKET,
24 | TokenType.RBRACKET,
25 | );
26 |
27 | export type LexerNextTokenSymbol = typeof lexerSymbolSchema.Type;
28 |
--------------------------------------------------------------------------------
/src/schemas/infix-operator.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from 'effect'
2 | import { TokenType } from './token-types/union'
3 |
4 | export const infixOperatorSchema = Schema.Literal(
5 | TokenType.PLUS,
6 | TokenType.MINUS,
7 | TokenType.ASTERISK,
8 | TokenType.SLASH,
9 | TokenType.LT,
10 | TokenType.GT,
11 | TokenType.EQ,
12 | TokenType.NOT_EQ,
13 | TokenType.EXPONENT,
14 | )
15 |
16 | export type InfixOperator = typeof infixOperatorSchema.Type
17 |
--------------------------------------------------------------------------------
/src/schemas/nodes/exps/array.ts:
--------------------------------------------------------------------------------
1 | import { type Token, tokenSchema } from "@/schemas/token/unions/all";
2 | import { Schema } from "effect";
3 | import { Exp } from "./union";
4 |
5 | export type ArrayExp = {
6 | readonly _tag: "ArrayExp";
7 | readonly token: Token;
8 | readonly elements: readonly Exp[];
9 | };
10 |
11 | export const ArrayExp = Schema.TaggedStruct("ArrayExp", {
12 | token: tokenSchema,
13 | elements: Schema.Array(Schema.suspend((): Schema.Schema => Exp)),
14 | });
15 |
--------------------------------------------------------------------------------
/src/schemas/nodes/exps/boolean.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 | import { tokenSchema } from "src/schemas/token/unions/all";
3 |
4 | export const BoolExp = Schema.TaggedStruct("BoolExp", {
5 | token: tokenSchema,
6 | value: Schema.Boolean,
7 | });
8 |
9 | export type BoolExp = typeof BoolExp.Type;
10 |
11 | export const BoolExpEq = Schema.equivalence(BoolExp);
12 |
13 | export const nativeToBoolExp = (bool: boolean) =>
14 | BoolExp.make({
15 | token: {
16 | _tag: `${bool}`,
17 | literal: `${bool}`,
18 | },
19 | value: bool,
20 | });
21 |
--------------------------------------------------------------------------------
/src/schemas/nodes/exps/call.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 | import { type Token, tokenSchema } from "src/schemas/token/unions/all";
3 | import { Exp } from "./union";
4 |
5 | export type CallExp = {
6 | readonly _tag: "CallExp";
7 | readonly token: Token;
8 | readonly fn: Exp;
9 | readonly args: readonly Exp[];
10 | };
11 |
12 | export const CallExp = Schema.TaggedStruct("CallExp", {
13 | token: tokenSchema,
14 | fn: Schema.suspend((): Schema.Schema => Exp),
15 | args: Schema.Array(Schema.suspend((): Schema.Schema => Exp)),
16 | });
17 |
--------------------------------------------------------------------------------
/src/schemas/nodes/exps/diff.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 | import { type DiffToken, diffTokenSchema } from "src/schemas/token/diff";
3 | import { IdentExp } from "./ident";
4 | import { Exp } from "./union";
5 |
6 | export type DiffExp = {
7 | readonly _tag: "DiffExp";
8 | readonly token: DiffToken;
9 | readonly exp: Exp;
10 | readonly params: readonly IdentExp[];
11 | };
12 |
13 | export const DiffExp = Schema.TaggedStruct("DiffExp", {
14 | token: diffTokenSchema,
15 | exp: Schema.suspend((): Schema.Schema => Exp),
16 | params: Schema.Array(Schema.suspend((): Schema.Schema => IdentExp)),
17 | });
18 |
--------------------------------------------------------------------------------
/src/schemas/nodes/exps/function.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 | import {
3 | type FnToken,
4 | fnTokenSchema,
5 | } from "src/schemas/token/function-literal";
6 | import { BlockStmt } from "../stmts/block";
7 | import { IdentExp } from "./ident";
8 |
9 | export type FuncExp = {
10 | readonly _tag: "FuncExp";
11 | readonly token: FnToken;
12 | parameters: readonly IdentExp[];
13 | readonly body: BlockStmt;
14 | };
15 |
16 | export const FuncExp = Schema.TaggedStruct("FuncExp", {
17 | token: fnTokenSchema,
18 | parameters: Schema.Array(
19 | Schema.suspend((): Schema.Schema => IdentExp),
20 | ),
21 | body: BlockStmt,
22 | });
23 |
--------------------------------------------------------------------------------
/src/schemas/nodes/exps/ident.ts:
--------------------------------------------------------------------------------
1 | import { Effect, Schema } from "effect";
2 | import { KennethParseError } from "../../../errors/kenneth/parse";
3 | import { identTokenSchema } from "../../../schemas/token/ident";
4 |
5 | export const IdentExp = Schema.TaggedStruct("IdentExp", {
6 | token: identTokenSchema,
7 | value: Schema.String,
8 | });
9 |
10 | export type IdentExp = typeof IdentExp.Type;
11 |
12 | export const IdentExpEq = Schema.equivalence(IdentExp);
13 |
14 | export const expectIdentEquivalence = (a: IdentExp, b: IdentExp) =>
15 | Effect.gen(function* () {
16 | return !IdentExpEq(a, b)
17 | ? yield* new KennethParseError({
18 | message: "we expected ident to equal x",
19 | })
20 | : undefined;
21 | });
22 |
23 | export const nativeToIdentExp = (value: string) =>
24 | IdentExp.make({
25 | token: {
26 | _tag: "IDENT",
27 | literal: value,
28 | },
29 | value,
30 | });
31 |
--------------------------------------------------------------------------------
/src/schemas/nodes/exps/if.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 | import { type IfToken, ifTokenSchema } from "src/schemas/token/if";
3 | import { BlockStmt } from "../stmts/block";
4 | import { Exp } from "./union";
5 |
6 | export type IfExp = {
7 | readonly _tag: "IfExp";
8 | readonly token: IfToken;
9 | readonly condition: Exp;
10 | readonly consequence: BlockStmt;
11 | readonly alternative?: BlockStmt | undefined;
12 | };
13 |
14 | export const IfExp = Schema.TaggedStruct("IfExp", {
15 | token: ifTokenSchema,
16 | condition: Schema.suspend((): Schema.Schema => Exp),
17 | consequence: BlockStmt,
18 | alternative: Schema.optional(BlockStmt),
19 | });
20 |
--------------------------------------------------------------------------------
/src/schemas/nodes/exps/index.ts:
--------------------------------------------------------------------------------
1 | import { type Token, tokenSchema } from "@/schemas/token/unions/all";
2 | import { Schema } from "effect";
3 | import { Exp } from "./union";
4 |
5 | export type IndexExp = {
6 | readonly _tag: "IndexExp";
7 | readonly token: Token;
8 | readonly left: Exp;
9 | readonly index: Exp;
10 | };
11 |
12 | export const IndexExp = Schema.TaggedStruct("IndexExp", {
13 | token: tokenSchema,
14 | left: Schema.suspend((): Schema.Schema => Exp),
15 | index: Schema.suspend((): Schema.Schema => Exp),
16 | });
17 |
--------------------------------------------------------------------------------
/src/schemas/nodes/exps/infix.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 | import {
3 | type InfixOperator,
4 | infixOperatorSchema,
5 | } from "src/schemas/infix-operator";
6 | import { type Token, tokenSchema } from "src/schemas/token/unions/all";
7 | import { Exp } from "./union";
8 |
9 | export type InfixExp = {
10 | readonly _tag: "InfixExp";
11 | readonly token: Token;
12 | readonly operator: InfixOperator;
13 | readonly left: Exp;
14 | readonly right: Exp;
15 | };
16 |
17 | export const InfixExp = Schema.TaggedStruct("InfixExp", {
18 | token: tokenSchema,
19 | operator: infixOperatorSchema,
20 | left: Schema.suspend((): Schema.Schema => Exp),
21 | right: Schema.suspend((): Schema.Schema => Exp),
22 | });
23 |
24 | export const OpInfixExp = (op: InfixOperator) => (left: Exp) => (right: Exp) =>
25 | InfixExp.make({
26 | token: {
27 | _tag: op,
28 | literal: op,
29 | },
30 | operator: op,
31 | left,
32 | right,
33 | });
34 |
--------------------------------------------------------------------------------
/src/schemas/nodes/exps/int.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 | import { type IntToken, intTokenSchema } from "../../../schemas/token/int";
3 |
4 | export type IntExp = {
5 | readonly _tag: "IntExp";
6 | readonly token: IntToken;
7 | readonly value: number;
8 | };
9 |
10 | export const IntExp = Schema.TaggedStruct("IntExp", {
11 | token: intTokenSchema,
12 | value: Schema.Number,
13 | });
14 |
15 | export const IntExpEq = Schema.equivalence(IntExp);
16 |
17 | export const nativeToIntExp = (int: number) =>
18 | IntExp.make({
19 | token: {
20 | _tag: "INT",
21 | literal: `${int}`,
22 | },
23 | value: int,
24 | });
25 |
--------------------------------------------------------------------------------
/src/schemas/nodes/exps/prefix.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 | import {
3 | type PrefixOperator,
4 | prefixOperatorSchema,
5 | } from "src/schemas/prefix-operator";
6 | import { type Token, tokenSchema } from "src/schemas/token/unions/all";
7 | import { Exp } from "./union";
8 |
9 | export type PrefixExp = {
10 | readonly _tag: "PrefixExp";
11 | readonly token: Token;
12 | readonly operator: PrefixOperator;
13 | readonly right: Exp;
14 | };
15 |
16 | export const PrefixExp = Schema.TaggedStruct("PrefixExp", {
17 | token: tokenSchema,
18 | operator: prefixOperatorSchema,
19 | right: Schema.suspend((): Schema.Schema => Exp),
20 | });
21 |
22 | export const opPrefixExp = (op: PrefixOperator) => (right: Exp) =>
23 | PrefixExp.make({
24 | token: {
25 | _tag: op,
26 | literal: op,
27 | },
28 | operator: op,
29 | right,
30 | });
31 |
--------------------------------------------------------------------------------
/src/schemas/nodes/exps/str.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 | import { type StringToken, stringTokenSchema } from "src/schemas/token/string";
3 |
4 | export type StrExp = {
5 | readonly _tag: "StrExp";
6 | readonly token: StringToken;
7 | readonly value: string;
8 | };
9 |
10 | export const StrExp = Schema.TaggedStruct("StrExp", {
11 | token: stringTokenSchema,
12 | value: Schema.String,
13 | });
14 |
15 | export const StrExpEq = Schema.equivalence(StrExp);
16 |
17 | export const nativeToStrExp = (str: string) =>
18 | StrExp.make({
19 | token: {
20 | _tag: "STRING",
21 | literal: str,
22 | },
23 | value: str,
24 | });
25 |
--------------------------------------------------------------------------------
/src/schemas/nodes/exps/union.ts:
--------------------------------------------------------------------------------
1 | import { Data, Match, Schema } from "effect";
2 | import { IndexExp } from ".";
3 | import { ArrayExp } from "./array";
4 | import { BoolExp, nativeToBoolExp } from "./boolean";
5 | import { CallExp } from "./call";
6 | import { DiffExp } from "./diff";
7 | import { FuncExp } from "./function";
8 | import { IdentExp } from "./ident";
9 | import { IfExp } from "./if";
10 | import { InfixExp } from "./infix";
11 | import { IntExp, nativeToIntExp } from "./int";
12 | import { PrefixExp } from "./prefix";
13 | import { StrExp, nativeToStrExp } from "./str";
14 |
15 | export type Exp =
16 | | BoolExp
17 | | CallExp
18 | | FuncExp
19 | | IdentExp
20 | | IfExp
21 | | InfixExp
22 | | IntExp
23 | | PrefixExp
24 | | StrExp
25 | | DiffExp
26 | | ArrayExp
27 | | IndexExp;
28 |
29 | export const Exp = Schema.suspend(
30 | (): Schema.Schema =>
31 | Schema.Union(
32 | BoolExp,
33 | CallExp,
34 | FuncExp,
35 | IdentExp,
36 | IfExp,
37 | InfixExp,
38 | IntExp,
39 | PrefixExp,
40 | StrExp,
41 | DiffExp,
42 | ArrayExp,
43 | IndexExp,
44 | ),
45 | );
46 |
47 | export const { $is: isExp, $match: matchExp } = Data.taggedEnum();
48 |
49 | export const isBoolExp = isExp("BoolExp");
50 | export const isCallExp = isExp("CallExp");
51 | export const isFuncExp = isExp("FuncExp");
52 | export const isIdentExp = isExp("IdentExp");
53 | export const isIfExp = isExp("IfExp");
54 | export const isInfixExp = isExp("InfixExp");
55 | export const isIntExp = isExp("IntExp");
56 | export const isPrefixExp = isExp("PrefixExp");
57 | export const isStrExp = isExp("StrExp");
58 |
59 | type NativeToExpReturn = T extends boolean
60 | ? BoolExp
61 | : T extends number
62 | ? IntExp
63 | : T extends string
64 | ? StrExp
65 | : never;
66 |
67 | function nativeToExpImpl(
68 | native: number | boolean | string,
69 | ): BoolExp | IntExp | StrExp {
70 | return Match.value(native).pipe(
71 | Match.when(Match.boolean, (bool) => nativeToBoolExp(bool)),
72 | Match.when(Match.number, (num) => nativeToIntExp(num)),
73 | Match.when(Match.string, (str) => nativeToStrExp(str)),
74 | Match.exhaustive,
75 | );
76 | }
77 |
78 | export function nativeToExp(
79 | native: T,
80 | ): NativeToExpReturn {
81 | return nativeToExpImpl(native) as NativeToExpReturn;
82 | }
83 |
--------------------------------------------------------------------------------
/src/schemas/nodes/exps/unions/constant.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 | import { BoolExp } from "../boolean";
3 | import { IntExp } from "../int";
4 | import { StrExp } from "../str";
5 |
6 | export type ConstantExp = IntExp | StrExp | BoolExp;
7 |
8 | export const ConstantExp = Schema.suspend(
9 | (): Schema.Schema => Schema.Union(IntExp, StrExp, BoolExp),
10 | );
11 |
--------------------------------------------------------------------------------
/src/schemas/nodes/exps/unions/int-ident-infix.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 | import { IdentExp } from "../ident";
3 | import { InfixExp } from "../infix";
4 | import { IntExp } from "../int";
5 |
6 | export const polynomialExpSchema = Schema.Union(IntExp, IdentExp, InfixExp);
7 |
8 | export type PolynomialExp = typeof polynomialExpSchema.Type;
9 |
--------------------------------------------------------------------------------
/src/schemas/nodes/exps/unions/term.ts:
--------------------------------------------------------------------------------
1 | import { TokenType } from "@/schemas/token-types/union";
2 | import type { IdentExp } from "../ident";
3 | import { OpInfixExp } from "../infix";
4 | import { nativeToIntExp } from "../int";
5 |
6 | export const newTerm = (coeff: number, x: IdentExp, power: number) =>
7 | OpInfixExp(TokenType.ASTERISK)(nativeToIntExp(coeff))(
8 | OpInfixExp(TokenType.EXPONENT)(x)(nativeToIntExp(power)),
9 | );
10 |
--------------------------------------------------------------------------------
/src/schemas/nodes/interfaces/internal-expression.ts:
--------------------------------------------------------------------------------
1 | export interface IExpression {
2 | readonly expressionNode: () => void // recognize that this is a dummy method, there is probably a more typescript standard way to do this.
3 | }
4 |
--------------------------------------------------------------------------------
/src/schemas/nodes/interfaces/internal-node.ts:
--------------------------------------------------------------------------------
1 | export type INode = {};
2 |
--------------------------------------------------------------------------------
/src/schemas/nodes/interfaces/internal-statement.ts:
--------------------------------------------------------------------------------
1 | export interface IStatement {
2 | readonly statementNode: () => void
3 | }
4 |
--------------------------------------------------------------------------------
/src/schemas/nodes/program.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 | import { type Token, tokenSchema } from "../token/unions/all";
3 | import { Stmt } from "./stmts/union";
4 |
5 | export type Program = {
6 | readonly _tag: "Program";
7 | readonly token: Token;
8 | statements: readonly Stmt[];
9 | };
10 |
11 | export const Program = Schema.TaggedStruct("Program", {
12 | token: tokenSchema,
13 | statements: Schema.Array(Schema.suspend((): Schema.Schema => Stmt)),
14 | });
15 |
--------------------------------------------------------------------------------
/src/schemas/nodes/stmts/block.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 | import { type Token, tokenSchema } from "../../../schemas/token/unions/all";
3 | import { Stmt } from "./union";
4 |
5 | export type BlockStmt = {
6 | readonly _tag: "BlockStmt";
7 | readonly token: Token;
8 | statements: readonly Stmt[];
9 | };
10 |
11 | export const BlockStmt = Schema.TaggedStruct("BlockStmt", {
12 | token: tokenSchema,
13 | statements: Schema.Array(Schema.suspend((): Schema.Schema => Stmt)),
14 | });
15 |
--------------------------------------------------------------------------------
/src/schemas/nodes/stmts/exp.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 | import { type Token, tokenSchema } from "../../../schemas/token/unions/all";
3 | import { Exp } from "../exps/union";
4 |
5 | export type ExpStmt = {
6 | readonly _tag: "ExpStmt";
7 | readonly token: Token;
8 | readonly expression: Exp;
9 | };
10 |
11 | export const ExpStmt = Schema.TaggedStruct("ExpStmt", {
12 | token: tokenSchema,
13 | expression: Schema.suspend((): Schema.Schema => Exp),
14 | });
15 |
--------------------------------------------------------------------------------
/src/schemas/nodes/stmts/let.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 | import { type LetToken, letTokenSchema } from "../../../schemas/token/let";
3 | import { IdentExp } from "../exps/ident";
4 | import { Exp } from "../exps/union";
5 | export type LetStmt = {
6 | readonly _tag: "LetStmt";
7 | readonly name: IdentExp;
8 | readonly token: LetToken;
9 | readonly value: Exp;
10 | };
11 |
12 | export const LetStmt = Schema.TaggedStruct("LetStmt", {
13 | name: Schema.suspend((): Schema.Schema => IdentExp),
14 | token: letTokenSchema,
15 | value: Schema.suspend((): Schema.Schema => Exp),
16 | });
17 |
--------------------------------------------------------------------------------
/src/schemas/nodes/stmts/return.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 | import { type Token, tokenSchema } from "../../../schemas/token/unions/all";
3 | import { Exp } from "../exps/union";
4 |
5 | export type ReturnStmt = {
6 | readonly _tag: "ReturnStmt";
7 | readonly token: Token;
8 | readonly value: Exp;
9 | };
10 |
11 | export const ReturnStmt = Schema.TaggedStruct("ReturnStmt", {
12 | token: tokenSchema,
13 | value: Schema.suspend((): Schema.Schema => Exp),
14 | });
15 |
--------------------------------------------------------------------------------
/src/schemas/nodes/stmts/union.ts:
--------------------------------------------------------------------------------
1 | import { Data, Schema } from "effect";
2 | import { BlockStmt } from "./block";
3 | import { ExpStmt } from "./exp";
4 | import { LetStmt } from "./let";
5 | import { ReturnStmt } from "./return";
6 |
7 | export type Stmt = BlockStmt | ExpStmt | LetStmt | ReturnStmt;
8 |
9 | export const Stmt = Schema.suspend(
10 | (): Schema.Schema =>
11 | Schema.Union(BlockStmt, ExpStmt, LetStmt, ReturnStmt),
12 | );
13 |
14 | export const { $is: isStmt, $match: matchStmt } = Data.taggedEnum();
15 |
16 | export const isBlockStmt = isStmt("BlockStmt");
17 | export const isExpStmt = isStmt("ExpStmt");
18 | export const isLetStmt = isStmt("LetStmt");
19 | export const isReturnStmt = isStmt("ReturnStmt");
20 |
--------------------------------------------------------------------------------
/src/schemas/nodes/union.ts:
--------------------------------------------------------------------------------
1 | import { Data, Match, Pretty, Schema } from "effect";
2 | import { Exp } from "./exps/union";
3 | import { Program } from "./program";
4 |
5 | import { Stmt } from "./stmts/union";
6 |
7 | export type KNode = Exp | Stmt | Program;
8 | export const kNodeSchema = Schema.Union(Exp, Stmt, Program);
9 |
10 | export const { $is: isKNode, $match: matchKnode } = Data.taggedEnum();
11 |
12 | export const tokenLiteral = (node: KNode) =>
13 | Match.value(node).pipe(
14 | Match.tag("Program", (program) =>
15 | program.statements.length > 0
16 | ? `${program.statements[0].token.literal}`
17 | : "",
18 | ),
19 | Match.orElse(() => `${node.token.literal}`),
20 | );
21 |
22 | export const prettyNode = Pretty.make(kNodeSchema);
23 |
24 | export const nodeString = (node: KNode): string =>
25 | Match.value(node).pipe(
26 | Match.tag(
27 | "CallExp",
28 | (callExp) =>
29 | `${nodeString(callExp.fn)}(${callExp.args.map((arg) => nodeString(arg)).join(", ")})`,
30 | ),
31 | Match.tag(
32 | "FuncExp",
33 | (funcExp) => `
34 | ${funcExp.token.literal}
35 | (
36 | ${funcExp.parameters.map((param) => nodeString(param)).join(", ")}
37 | )
38 | ${nodeString(funcExp.body)}
39 | `,
40 | ),
41 | Match.tag("IfExp", (ifExp) => {
42 | const res: string = `
43 | if
44 | ${nodeString(ifExp.condition)}
45 |
46 | ${nodeString(ifExp.consequence)}
47 | `;
48 | return ifExp.alternative
49 | ? `${res}
50 | else
51 | ${nodeString(ifExp.alternative)}
52 | `
53 | : res;
54 | }),
55 | Match.tag(
56 | "InfixExp",
57 | (infixExp) =>
58 | `(${nodeString(infixExp.left)} ${infixExp.operator} ${nodeString(infixExp.right)})`,
59 | ),
60 | Match.tag(
61 | "PrefixExp",
62 | (prefixExp) => `(${prefixExp.operator}${nodeString(prefixExp.right)})`,
63 | ),
64 | Match.tag("IdentExp", (identExp) => identExp.value),
65 | Match.tag(
66 | "ArrayExp",
67 | (arrayExp) =>
68 | `[${arrayExp.elements.map((e) => nodeString(e)).join(", ")}]`,
69 | ),
70 | Match.tag(
71 | "IndexExp",
72 | ({ left, index }) => `(${nodeString(left)}[${nodeString(index)}])`,
73 | ),
74 | Match.tag("BlockStmt", (blockStmt) =>
75 | blockStmt.statements.map((stmt) => nodeString(stmt)).join("\n"),
76 | ),
77 | Match.tag("ExpStmt", (expStmt) => nodeString(expStmt.expression)),
78 | Match.tag(
79 | "LetStmt",
80 | (letStmt) =>
81 | `${letStmt.token.literal} ${nodeString(letStmt.name)} = ${nodeString(letStmt.value)};`,
82 | ),
83 | Match.tag(
84 | "ReturnStmt",
85 | (returnStmt) =>
86 | `${returnStmt.token.literal} ${nodeString(returnStmt.value)};`,
87 | ),
88 | Match.tag("Program", (program) =>
89 | program.statements.map((statement) => nodeString(statement)).join(""),
90 | ),
91 | Match.orElse(() => tokenLiteral(node)),
92 | );
93 |
--------------------------------------------------------------------------------
/src/schemas/objs/array.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 | import { Obj, type ObjEncoded } from "./union";
3 |
4 | export interface ArrayObj {
5 | readonly _tag: "ArrayObj";
6 | readonly elements: readonly Obj[];
7 | }
8 |
9 | export interface ArrayObjEncoded {
10 | readonly _tag: "ArrayObj";
11 | readonly elements: readonly ObjEncoded[];
12 | }
13 |
14 | export const ArrayObj = Schema.TaggedStruct("ArrayObj", {
15 | elements: Schema.Array(
16 | Schema.suspend((): Schema.Schema => Obj),
17 | ),
18 | });
19 |
--------------------------------------------------------------------------------
/src/schemas/objs/bool.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 |
3 | const fields = {
4 | value: Schema.Boolean,
5 | };
6 |
7 | export interface BooleanObj extends Schema.Struct.Type {
8 | readonly _tag: "BooleanObj";
9 | }
10 |
11 | export const BooleanObj = Schema.TaggedStruct("BooleanObj", {
12 | ...fields,
13 | });
14 |
15 | export const FALSE = BooleanObj.make({ value: false });
16 | export const TRUE = BooleanObj.make({ value: true });
17 |
18 | export const nativeBoolToObjectBool = (input: boolean) =>
19 | input ? TRUE : FALSE;
20 |
--------------------------------------------------------------------------------
/src/schemas/objs/built-in.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 | import { BuiltInFunc } from "../built-in";
3 | import { BuiltInDiffFunc } from "../built-in/diff";
4 |
5 | const fields = {
6 | fn: BuiltInFunc, //repeated
7 | };
8 |
9 | export interface BuiltInObj extends Schema.Struct.Type {
10 | readonly _tag: "BuiltInObj";
11 | }
12 |
13 | export const BuiltInObj = Schema.TaggedStruct("BuiltInObj", {
14 | ...fields,
15 | });
16 |
17 | // Define fields for the stricter variant
18 | const diffFields = {
19 | fn: BuiltInDiffFunc,
20 | };
21 |
22 | // Create the interface for the stricter variant
23 | export interface BuiltInDiffObj extends Schema.Struct.Type {
24 | readonly _tag: "BuiltInObj";
25 | }
26 |
27 | // Create the schema for the stricter variant
28 | export const BuiltInDiffObj = Schema.TaggedStruct("BuiltInObj", {
29 | ...diffFields,
30 | });
31 |
--------------------------------------------------------------------------------
/src/schemas/objs/call.ts:
--------------------------------------------------------------------------------
1 | // TODO: call exp for soft eval of trig functions.
2 |
3 | import { Schema } from "effect";
4 | import type { BuiltInFunc } from "../built-in";
5 | import { Exp } from "../nodes/exps/union";
6 | import { BuiltInObj } from "./built-in";
7 | import { FunctionObj, type FunctionObjEncoded } from "./function";
8 |
9 | export interface CallObj {
10 | readonly _tag: "CallObj";
11 | readonly fn: FunctionObj | BuiltInObj;
12 | readonly args: readonly Exp[];
13 | }
14 |
15 | export interface CallObjEncoded {
16 | readonly _tag: "CallObj";
17 | readonly fn: FunctionObjEncoded | BuiltInObj;
18 | readonly args: readonly Exp[];
19 | }
20 |
21 | export const CallObj = Schema.TaggedStruct("CallObj", {
22 | fn: Schema.suspend(
23 | (): Schema.Schema<
24 | FunctionObj | BuiltInObj,
25 | FunctionObjEncoded | BuiltInObj
26 | > => Schema.Union(FunctionObj, BuiltInObj),
27 | ),
28 | args: Schema.Array(Schema.suspend((): Schema.Schema => Exp)),
29 | });
30 |
31 | export const BuiltInCallObj = (fn: BuiltInFunc) => (args: readonly Exp[]) =>
32 | CallObj.make({
33 | fn: BuiltInObj.make({ fn }),
34 | args,
35 | });
36 |
--------------------------------------------------------------------------------
/src/schemas/objs/diff-function.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 | import { IdentExp } from "../nodes/exps/ident";
3 | import { BlockStmt } from "../nodes/stmts/block";
4 |
5 | const fields = {
6 | env: Schema.Unknown,
7 | };
8 |
9 | export interface DiffFunctionObj extends Schema.Struct.Type {
10 | readonly _tag: "DiffFunctionObj";
11 | readonly params: readonly IdentExp[];
12 | readonly body: BlockStmt;
13 | }
14 |
15 | export const DiffFunctionObj = Schema.TaggedStruct("DiffFunctionObj", {
16 | ...fields,
17 | params: Schema.Array(Schema.suspend((): Schema.Schema => IdentExp)),
18 | body: Schema.suspend((): Schema.Schema => BlockStmt),
19 | });
20 |
--------------------------------------------------------------------------------
/src/schemas/objs/error.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 |
3 | const fields = {
4 | message: Schema.String,
5 | };
6 |
7 | export interface ErrorObj extends Schema.Struct.Type {
8 | readonly _tag: "ErrorObj";
9 | }
10 |
11 | export const ErrorObj = Schema.TaggedStruct("ErrorObj", {
12 | ...fields,
13 | });
14 |
--------------------------------------------------------------------------------
/src/schemas/objs/function.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Environment,
3 | type EnvironmentEncoded,
4 | } from "@/services/object/environment";
5 | import { Schema } from "effect";
6 | import { IdentExp } from "../nodes/exps/ident";
7 | import { BlockStmt } from "../nodes/stmts/block";
8 |
9 | export interface FunctionObj {
10 | readonly _tag: "FunctionObj";
11 | readonly env: Environment;
12 | readonly params: readonly IdentExp[];
13 | readonly body: BlockStmt;
14 | }
15 |
16 | export interface FunctionObjEncoded {
17 | readonly _tag: "FunctionObj";
18 | readonly env: EnvironmentEncoded;
19 | readonly params: readonly IdentExp[];
20 | readonly body: BlockStmt;
21 | }
22 |
23 | export const FunctionObj = Schema.TaggedStruct("FunctionObj", {
24 | env: Environment,
25 | params: Schema.Array(Schema.suspend((): Schema.Schema => IdentExp)),
26 | body: Schema.suspend((): Schema.Schema => BlockStmt),
27 | });
28 |
--------------------------------------------------------------------------------
/src/schemas/objs/ident.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 | import { IdentExp } from "../nodes/exps/ident";
3 |
4 | export interface IdentObj {
5 | readonly _tag: "IdentObj";
6 | readonly identExp: IdentExp;
7 | }
8 |
9 | export const IdentObj = Schema.TaggedStruct("IdentObj", {
10 | identExp: Schema.suspend((): Schema.Schema => IdentExp),
11 | });
12 |
13 | export const identObjEq = Schema.equivalence(IdentObj);
14 |
--------------------------------------------------------------------------------
/src/schemas/objs/infix.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 | import { type InfixOperator, infixOperatorSchema } from "../infix-operator";
3 | import { TokenType } from "../token-types/union";
4 | import { Obj, type ObjEncoded } from "./union";
5 |
6 | const fields = {
7 | operator: infixOperatorSchema,
8 | };
9 |
10 | export interface InfixObj extends Schema.Struct.Type {
11 | readonly _tag: "InfixObj";
12 | readonly left: Obj;
13 | readonly right: Obj;
14 | }
15 |
16 | export interface InfixObjEncoded extends Schema.Struct.Type {
17 | readonly _tag: "InfixObj";
18 | readonly left: ObjEncoded;
19 | readonly right: ObjEncoded;
20 | }
21 |
22 | export const InfixObj = Schema.TaggedStruct("InfixObj", {
23 | ...fields,
24 | left: Schema.suspend((): Schema.Schema => Obj),
25 | right: Schema.suspend((): Schema.Schema => Obj),
26 | operator: infixOperatorSchema,
27 | });
28 |
29 | export const infixObjEq = Schema.equivalence(InfixObj);
30 |
31 | export const OpInfixObj = (op: InfixOperator) => (left: Obj, right: Obj) =>
32 | InfixObj.make({
33 | operator: op,
34 | left,
35 | right,
36 | });
37 |
38 | export const asteriskInfixObj = OpInfixObj(TokenType.ASTERISK);
39 | export const exponentInfixObj = OpInfixObj(TokenType.EXPONENT);
40 |
--------------------------------------------------------------------------------
/src/schemas/objs/int.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 |
3 | const fields = {
4 | value: Schema.Number,
5 | };
6 |
7 | export interface IntegerObj extends Schema.Struct.Type {
8 | readonly _tag: "IntegerObj";
9 | }
10 |
11 | export const IntegerObj = Schema.TaggedStruct("IntegerObj", {
12 | ...fields,
13 | });
14 |
15 | export const intObjEq = Schema.equivalence(IntegerObj);
16 |
17 | export const ZERO = IntegerObj.make({ value: 0 });
18 | export const ONE = IntegerObj.make({ value: 1 });
19 |
--------------------------------------------------------------------------------
/src/schemas/objs/null.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 |
3 | export const NullObj = Schema.TaggedStruct("NullObj", {});
4 |
5 | export type NullObj = typeof NullObj.Type;
6 |
7 | export const NULL = NullObj.make();
8 |
--------------------------------------------------------------------------------
/src/schemas/objs/return.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 | import { Obj, type ObjEncoded } from "./union";
3 |
4 | export interface ReturnObj {
5 | readonly _tag: "ReturnObj";
6 | readonly value: Obj;
7 | }
8 | export interface ReturnObjEncoded {
9 | readonly _tag: "ReturnObj";
10 | readonly value: ObjEncoded;
11 | }
12 |
13 | export const ReturnObj = Schema.TaggedStruct("ReturnObj", {
14 | value: Schema.suspend((): Schema.Schema => Obj),
15 | });
16 |
17 | //export type ReturnObj = typeof returnObjSchema.Type
18 |
--------------------------------------------------------------------------------
/src/schemas/objs/string.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 |
3 | const fields = {
4 | value: Schema.String,
5 | };
6 |
7 | export interface StringObj extends Schema.Struct.Type {
8 | readonly _tag: "StringObj";
9 | }
10 |
11 | export const StringObj = Schema.TaggedStruct("StringObj", {
12 | ...fields,
13 | });
14 |
--------------------------------------------------------------------------------
/src/schemas/objs/union.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 | import { ArrayObj, type ArrayObjEncoded } from "./array";
3 | import { BooleanObj } from "./bool";
4 | import { BuiltInObj } from "./built-in";
5 | import { CallObj, type CallObjEncoded } from "./call";
6 | import { ErrorObj } from "./error";
7 | import { FunctionObj, type FunctionObjEncoded } from "./function";
8 | import { IdentObj } from "./ident";
9 | import { InfixObj, type InfixObjEncoded } from "./infix";
10 | import { IntegerObj } from "./int";
11 | import { NullObj } from "./null";
12 | import { ReturnObj, type ReturnObjEncoded } from "./return";
13 | import { StringObj } from "./string";
14 |
15 | export type Obj =
16 | | BooleanObj
17 | | BuiltInObj
18 | | ErrorObj
19 | | FunctionObj
20 | | IntegerObj
21 | | NullObj
22 | | ReturnObj
23 | | StringObj
24 | | IdentObj
25 | | InfixObj
26 | | ArrayObj
27 | | CallObj;
28 |
29 | export type ObjEncoded =
30 | | Exclude
31 | | FunctionObjEncoded
32 | | ReturnObjEncoded
33 | | InfixObjEncoded
34 | | ArrayObjEncoded
35 | | CallObjEncoded;
36 |
37 | export const Obj = Schema.suspend(
38 | (): Schema.Schema =>
39 | Schema.Union(
40 | BooleanObj,
41 | BuiltInObj,
42 | ErrorObj,
43 | FunctionObj,
44 | IntegerObj,
45 | NullObj,
46 | ReturnObj,
47 | StringObj,
48 | IdentObj,
49 | InfixObj,
50 | ArrayObj,
51 | CallObj,
52 | ),
53 | );
54 |
--------------------------------------------------------------------------------
/src/schemas/objs/unions/polynomials.ts:
--------------------------------------------------------------------------------
1 | import type { IdentExp } from "@/schemas/nodes/exps/ident";
2 | import { TokenType } from "@/schemas/token-types/union";
3 | import { diffPolynomial } from "@/services/diff/obj";
4 | import { Effect, Match, Schema } from "effect";
5 | import type { ParseError } from "effect/ParseResult";
6 | import { CallObj, type CallObjEncoded } from "../call";
7 | import { IdentObj } from "../ident";
8 | import { InfixObj, type InfixObjEncoded, OpInfixObj } from "../infix";
9 | import { IntegerObj } from "../int";
10 |
11 | export type PolynomialObj = IntegerObj | IdentObj | InfixObj | CallObj;
12 | export type PolynomialObjEncoded =
13 | | IntegerObj
14 | | IdentObj
15 | | InfixObjEncoded
16 | | CallObjEncoded;
17 |
18 | export const PolynomialObj = Schema.suspend(
19 | (): Schema.Schema =>
20 | Schema.Union(IntegerObj, IdentObj, InfixObj, CallObj),
21 | );
22 |
23 | export const newTerm = (coeff: number, x: IdentObj, power: number) =>
24 | InfixObj.make({
25 | left: IntegerObj.make({ value: coeff }),
26 | operator: TokenType.ASTERISK,
27 | right: InfixObj.make({
28 | left: x,
29 | operator: TokenType.EXPONENT,
30 | right: IntegerObj.make({ value: power }),
31 | }),
32 | });
33 |
34 | const diffBoth = (f: PolynomialObj, g: PolynomialObj, x: IdentExp) =>
35 | Effect.all([diffPolynomial(f, x), diffPolynomial(g, x)]);
36 |
37 | export const quotientRule = (f: PolynomialObj, g: PolynomialObj, x: IdentExp) =>
38 | diffBoth(f, g, x).pipe(
39 | Effect.flatMap(([df, dg]) =>
40 | Effect.succeed(
41 | InfixObj.make({
42 | left: InfixObj.make({
43 | left: OpInfixObj(TokenType.ASTERISK)(df, g),
44 | operator: TokenType.MINUS,
45 | right: OpInfixObj(TokenType.ASTERISK)(f, dg),
46 | }),
47 | operator: TokenType.SLASH,
48 | right: InfixObj.make({
49 | left: g,
50 | operator: TokenType.EXPONENT,
51 | right: IntegerObj.make({ value: 2 }),
52 | }),
53 | }),
54 | ),
55 | ),
56 | );
57 |
58 | export const productRule = (f: PolynomialObj, g: PolynomialObj, x: IdentExp) =>
59 | diffBoth(f, g, x).pipe(
60 | Effect.flatMap(([df, dg]) =>
61 | Effect.succeed(
62 | InfixObj.make({
63 | left: OpInfixObj(TokenType.ASTERISK)(df, g),
64 | operator: TokenType.PLUS,
65 | right: OpInfixObj(TokenType.ASTERISK)(f, dg),
66 | }),
67 | ),
68 | ),
69 | );
70 |
71 | export const sumAndDifferenceRule = (
72 | f: PolynomialObj,
73 | g: PolynomialObj,
74 | x: IdentExp,
75 | operator: typeof TokenType.PLUS | typeof TokenType.MINUS,
76 | ) =>
77 | diffBoth(f, g, x).pipe(
78 | Effect.flatMap(([df, dg]) =>
79 | Effect.succeed(
80 | InfixObj.make({
81 | left: df,
82 | operator,
83 | right: dg,
84 | }),
85 | ),
86 | ),
87 | );
88 |
89 | export const powerRule = (coeff: IntegerObj, power: IntegerObj, x: IdentExp) =>
90 | newTerm(
91 | coeff.value * power.value,
92 | IdentObj.make({ identExp: x }),
93 | power.value - 1,
94 | );
95 |
96 | export const constantRule = () => Effect.succeed(IntegerObj.make({ value: 0 }));
97 |
98 | export const recursivelySubstitute = (
99 | f: PolynomialObj,
100 | g: PolynomialObj,
101 | x: IdentExp,
102 | ): Effect.Effect =>
103 | Match.value(f).pipe(
104 | Match.tag("IdentObj", () => Effect.succeed(g)),
105 | Match.tag("IntegerObj", (intObj) => Effect.succeed(intObj)),
106 | Match.tag("InfixObj", ({ left, operator, right }) =>
107 | Effect.all([
108 | Schema.decodeUnknown(PolynomialObj)(left),
109 | Schema.decodeUnknown(PolynomialObj)(right),
110 | ]).pipe(
111 | Effect.flatMap(([left, right]) =>
112 | Effect.all([
113 | recursivelySubstitute(left, g, x),
114 | recursivelySubstitute(right, g, x),
115 | ]).pipe(
116 | Effect.flatMap(([left, right]) =>
117 | Effect.succeed(
118 | InfixObj.make({
119 | left,
120 | operator,
121 | right,
122 | }),
123 | ),
124 | ),
125 | ),
126 | ),
127 | ),
128 | ),
129 | Match.tag("CallObj", ({ fn }) =>
130 | Effect.succeed(CallObj.make({ fn, args: [g] })),
131 | ),
132 | Match.exhaustive,
133 | );
134 |
135 | export const chainRule = (f: PolynomialObj, g: PolynomialObj, x: IdentExp) =>
136 | Effect.all([diffPolynomial(f, x), diffPolynomial(g, x)]).pipe(
137 | Effect.flatMap(([left, right]) =>
138 | recursivelySubstitute(left, g, x).pipe(
139 | Effect.flatMap((left) =>
140 | Effect.succeed(
141 | InfixObj.make({
142 | left,
143 | operator: TokenType.ASTERISK,
144 | right,
145 | }),
146 | ),
147 | ),
148 | ),
149 | ),
150 | );
151 |
--------------------------------------------------------------------------------
/src/schemas/prefix-operator.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from 'effect'
2 | import { TokenType } from './token-types/union'
3 |
4 | export const prefixOperatorSchema = Schema.Literal(
5 | TokenType.BANG,
6 | TokenType.MINUS,
7 | )
8 |
9 | export type PrefixOperator = typeof prefixOperatorSchema.Type
10 |
--------------------------------------------------------------------------------
/src/schemas/token-types/finite.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 |
3 | export const FiniteTokenType = {
4 | ASSIGN: "=",
5 | PLUS: "+",
6 | MINUS: "-",
7 | SLASH: "/",
8 | COMMA: ",",
9 | SEMICOLON: ";",
10 | LPAREN: "(",
11 | RPAREN: ")",
12 | LBRACE: "{",
13 | RBRACE: "}",
14 | LBRACKET: "[",
15 | RBRACKET: "]",
16 | BANG: "!",
17 | LT: "<",
18 | GT: ">",
19 | ASTERISK: "*",
20 | QUOTE: '"',
21 | EOF: "\0",
22 | EQ: "==",
23 | NOT_EQ: "!=",
24 | EXPONENT: "**",
25 | LET: "let",
26 | IF: "if",
27 | ELSE: "else",
28 | FUNCTION: "fn",
29 | TRUE: "true",
30 | FALSE: "false",
31 | RETURN: "return",
32 | DIFF: "diff",
33 | ILLEGAL: "", // doesn't quite fit.
34 | } as const;
35 |
36 | export const finiteTokenTypeSchema = Schema.Literal(
37 | ...Object.values(FiniteTokenType),
38 | );
39 |
40 | export type FiniteTokenType = typeof finiteTokenTypeSchema.Type;
41 |
--------------------------------------------------------------------------------
/src/schemas/token-types/infinite.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from 'effect'
2 |
3 | export const InfiniteTokenType = {
4 | STRING: 'STRING',
5 | INT: 'INT',
6 | IDENT: 'IDENT',
7 | } as const
8 | export const infiniteTokenTypeSchema = Schema.Literal(
9 | ...Object.values(InfiniteTokenType),
10 | )
11 |
12 | export type InfiniteTokenType = typeof infiniteTokenTypeSchema.Type
13 |
--------------------------------------------------------------------------------
/src/schemas/token-types/union.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from 'effect'
2 |
3 | import { finiteTokenTypeSchema, FiniteTokenType } from './finite'
4 | import { infiniteTokenTypeSchema, InfiniteTokenType } from './infinite'
5 |
6 | export const tokenTypeSchema = Schema.Union(
7 | finiteTokenTypeSchema,
8 | infiniteTokenTypeSchema,
9 | )
10 |
11 | export type TokenType = typeof tokenTypeSchema.Type
12 |
13 | export const TokenType = { ...FiniteTokenType, ...InfiniteTokenType }
14 |
--------------------------------------------------------------------------------
/src/schemas/token/asterisk.ts:
--------------------------------------------------------------------------------
1 | import { TokenType } from '../token-types/union'
2 | import { createTokenLiteralSchema } from '../utils/create-token-literal-schema'
3 |
4 | export const asteriskTokenSchema = createTokenLiteralSchema(TokenType.ASTERISK)
5 |
6 | export type AsteriskToken = typeof asteriskTokenSchema.Type
7 |
--------------------------------------------------------------------------------
/src/schemas/token/bang.ts:
--------------------------------------------------------------------------------
1 | import { TokenType } from '../token-types/union'
2 | import { createTokenLiteralSchema } from '../utils/create-token-literal-schema'
3 |
4 | export const bangTokenSchema = createTokenLiteralSchema(TokenType.BANG)
5 |
6 | export type BangToken = typeof bangTokenSchema.Type
7 |
--------------------------------------------------------------------------------
/src/schemas/token/base.ts:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/schemas/token/diff.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from 'effect'
2 | import { TokenType } from '../token-types/union'
3 |
4 | export const diffTokenSchema = Schema.TaggedStruct(TokenType.DIFF, {
5 | literal: Schema.String,
6 | })
7 |
8 | export type DiffToken = typeof diffTokenSchema.Type
9 |
--------------------------------------------------------------------------------
/src/schemas/token/eq.ts:
--------------------------------------------------------------------------------
1 | import { TokenType } from '../token-types/union'
2 | import { createTokenLiteralSchema } from '../utils/create-token-literal-schema'
3 |
4 | export const eqTokenSchema = createTokenLiteralSchema(TokenType.EQ)
5 |
6 | export type EqToken = typeof eqTokenSchema.Type
7 |
--------------------------------------------------------------------------------
/src/schemas/token/exponent.ts:
--------------------------------------------------------------------------------
1 | import { TokenType } from '../token-types/union'
2 | import { createTokenLiteralSchema } from '../utils/create-token-literal-schema'
3 |
4 | export const exponentTokenSchema = createTokenLiteralSchema(TokenType.EXPONENT)
5 |
6 | export type ExponentToken = typeof exponentTokenSchema.Type
7 |
--------------------------------------------------------------------------------
/src/schemas/token/false.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from 'effect'
2 | import { TokenType } from '../token-types/union'
3 |
4 | export const falseTokenSchema = Schema.TaggedStruct(TokenType.FALSE, {
5 | literal: Schema.String,
6 | })
7 |
8 | export type FalseToken = typeof falseTokenSchema.Type
9 |
--------------------------------------------------------------------------------
/src/schemas/token/function-literal.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from 'effect'
2 | import { TokenType } from '../token-types/union'
3 |
4 | export const fnTokenSchema = Schema.TaggedStruct(TokenType.FUNCTION, {
5 | literal: Schema.String,
6 | })
7 |
8 | export type FnToken = typeof fnTokenSchema.Type
9 |
--------------------------------------------------------------------------------
/src/schemas/token/greater-than.ts:
--------------------------------------------------------------------------------
1 | import { TokenType } from '../token-types/union'
2 | import { createTokenLiteralSchema } from '../utils/create-token-literal-schema'
3 |
4 | export const gtTokenSchema = createTokenLiteralSchema(TokenType.GT)
5 |
6 | export type GtToken = typeof gtTokenSchema.Type
7 |
--------------------------------------------------------------------------------
/src/schemas/token/grouped.ts:
--------------------------------------------------------------------------------
1 | import { TokenType } from '../token-types/union'
2 | import { createTokenLiteralSchema } from '../utils/create-token-literal-schema'
3 |
4 | export const groupedTokenSchema = createTokenLiteralSchema(TokenType.LPAREN)
5 |
6 | export type GroupedToken = typeof groupedTokenSchema.Type
7 |
--------------------------------------------------------------------------------
/src/schemas/token/ident.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from 'effect'
2 | import { TokenType } from '../token-types/union'
3 |
4 | export const identTokenSchema = Schema.TaggedStruct(TokenType.IDENT, {
5 | literal: Schema.String,
6 | })
7 |
8 | export type IdentToken = typeof identTokenSchema.Type
9 |
--------------------------------------------------------------------------------
/src/schemas/token/if.ts:
--------------------------------------------------------------------------------
1 | import { TokenType } from '../token-types/union'
2 | import { createTokenLiteralSchema } from '../utils/create-token-literal-schema'
3 |
4 | export const ifTokenSchema = createTokenLiteralSchema(TokenType.IF)
5 |
6 | export type IfToken = typeof ifTokenSchema.Type
7 |
--------------------------------------------------------------------------------
/src/schemas/token/int.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from 'effect'
2 | import { TokenType } from '../token-types/union'
3 |
4 | export const intTokenSchema = Schema.TaggedStruct(TokenType.INT, {
5 | _tag: Schema.Literal(TokenType.INT),
6 | literal: Schema.String,
7 | })
8 |
9 | export type IntToken = typeof intTokenSchema.Type
10 |
--------------------------------------------------------------------------------
/src/schemas/token/lbracket.ts:
--------------------------------------------------------------------------------
1 | import { TokenType } from "../token-types/union";
2 | import { createTokenLiteralSchema } from "../utils/create-token-literal-schema";
3 |
4 | export const LBRACKET = createTokenLiteralSchema(TokenType.LBRACKET);
5 |
6 | export type LBRACKET = typeof LBRACKET.Type;
7 |
--------------------------------------------------------------------------------
/src/schemas/token/left-paren.ts:
--------------------------------------------------------------------------------
1 | import { TokenType } from '../token-types/union'
2 | import { createTokenLiteralSchema } from '../utils/create-token-literal-schema'
3 |
4 | export const lpTokenSchema = createTokenLiteralSchema(TokenType.LPAREN)
5 |
6 | export type LpToken = typeof lpTokenSchema.Type
7 |
--------------------------------------------------------------------------------
/src/schemas/token/less-than.ts:
--------------------------------------------------------------------------------
1 | import { TokenType } from '../token-types/union'
2 | import { createTokenLiteralSchema } from '../utils/create-token-literal-schema'
3 |
4 | export const ltTokenSchema = createTokenLiteralSchema(TokenType.LT)
5 |
6 | export type LtToken = typeof ltTokenSchema.Type
7 |
--------------------------------------------------------------------------------
/src/schemas/token/let.ts:
--------------------------------------------------------------------------------
1 | import { TokenType } from '../token-types/union'
2 | import { createTokenLiteralSchema } from '../utils/create-token-literal-schema'
3 |
4 | export const letTokenSchema = createTokenLiteralSchema(TokenType.LET)
5 |
6 | export type LetToken = typeof letTokenSchema.Type
7 |
--------------------------------------------------------------------------------
/src/schemas/token/minus.ts:
--------------------------------------------------------------------------------
1 | import { TokenType } from '../token-types/union'
2 | import { createTokenLiteralSchema } from '../utils/create-token-literal-schema'
3 |
4 | export const minusTokenSchema = createTokenLiteralSchema(TokenType.MINUS)
5 |
6 | export type MinusToken = typeof minusTokenSchema.Type
7 |
--------------------------------------------------------------------------------
/src/schemas/token/not-eq.ts:
--------------------------------------------------------------------------------
1 | import { TokenType } from '../token-types/union'
2 | import { createTokenLiteralSchema } from '../utils/create-token-literal-schema'
3 |
4 | export const notEqTokenSchema = createTokenLiteralSchema(TokenType.NOT_EQ)
5 |
6 | export type NotEqToken = typeof notEqTokenSchema.Type
7 |
--------------------------------------------------------------------------------
/src/schemas/token/plus.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from 'effect'
2 | import { TokenType } from '../token-types/union'
3 |
4 | export const plusTokenSchema = Schema.TaggedStruct(TokenType.PLUS, {
5 | literal: Schema.Literal(TokenType.PLUS),
6 | })
7 |
8 | export type PlusToken = typeof plusTokenSchema.Type
9 |
--------------------------------------------------------------------------------
/src/schemas/token/rbracket.ts:
--------------------------------------------------------------------------------
1 | import { TokenType } from "../token-types/union";
2 | import { createTokenLiteralSchema } from "../utils/create-token-literal-schema";
3 |
4 | export const RBRACKET = createTokenLiteralSchema(TokenType.RBRACKET);
5 |
6 | export type RBRACKET = typeof RBRACKET.Type;
7 |
--------------------------------------------------------------------------------
/src/schemas/token/return.ts:
--------------------------------------------------------------------------------
1 | import { TokenType } from '../token-types/union'
2 | import { createTokenLiteralSchema } from '../utils/create-token-literal-schema'
3 |
4 | export const returnTokenSchema = createTokenLiteralSchema(TokenType.RETURN)
5 |
6 | export type ReturnToken = typeof returnTokenSchema.Type
7 |
--------------------------------------------------------------------------------
/src/schemas/token/slash.ts:
--------------------------------------------------------------------------------
1 | import { TokenType } from '../token-types/union'
2 | import { createTokenLiteralSchema } from '../utils/create-token-literal-schema'
3 |
4 | export const slashTokenSchema = createTokenLiteralSchema(TokenType.SLASH)
5 |
6 | export type SlashToken = typeof slashTokenSchema.Type
7 |
--------------------------------------------------------------------------------
/src/schemas/token/string.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from 'effect'
2 | import { TokenType } from '../token-types/union'
3 |
4 | export const stringTokenSchema = Schema.TaggedStruct(TokenType.STRING, {
5 | literal: Schema.String,
6 | })
7 |
8 | export type StringToken = typeof stringTokenSchema.Type
9 |
--------------------------------------------------------------------------------
/src/schemas/token/true.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from 'effect'
2 | import { TokenType } from '../token-types/union'
3 |
4 | export const trueTokenSchema = Schema.TaggedStruct(TokenType.TRUE, {
5 | literal: Schema.String,
6 | })
7 |
8 | export type TrueToken = typeof trueTokenSchema.Type
9 |
--------------------------------------------------------------------------------
/src/schemas/token/unions/all.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from 'effect'
2 |
3 | import { tokenTypeSchema } from 'src/schemas/token-types/union'
4 |
5 | export const tokenSchema = Schema.Struct({
6 | _tag: tokenTypeSchema,
7 | literal: Schema.Union(Schema.String, Schema.Number),
8 | })
9 |
10 | export type Token = typeof tokenSchema.Type
11 |
--------------------------------------------------------------------------------
/src/schemas/token/unions/boolean.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from 'effect'
2 | import { falseTokenSchema } from '../false'
3 | import { trueTokenSchema } from '../true'
4 |
5 | export const boolTokenSchema = Schema.Union(trueTokenSchema, falseTokenSchema)
6 |
7 | export type BoolToken = typeof boolTokenSchema.Type
8 |
--------------------------------------------------------------------------------
/src/schemas/token/unions/parse-fn/infix.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 | import { asteriskTokenSchema } from "../../asterisk";
3 | import { eqTokenSchema } from "../../eq";
4 | import { exponentTokenSchema } from "../../exponent";
5 | import { gtTokenSchema } from "../../greater-than";
6 | import { LBRACKET } from "../../lbracket";
7 | import { lpTokenSchema } from "../../left-paren";
8 | import { ltTokenSchema } from "../../less-than";
9 | import { minusTokenSchema } from "../../minus";
10 | import { notEqTokenSchema } from "../../not-eq";
11 | import { plusTokenSchema } from "../../plus";
12 | import { slashTokenSchema } from "../../slash";
13 |
14 | export const infixParseFnTokenSchema = Schema.Union(
15 | plusTokenSchema,
16 | minusTokenSchema,
17 | slashTokenSchema,
18 | asteriskTokenSchema,
19 | eqTokenSchema,
20 | notEqTokenSchema,
21 | ltTokenSchema,
22 | gtTokenSchema,
23 | lpTokenSchema,
24 | exponentTokenSchema,
25 | LBRACKET,
26 | );
27 |
28 | export type InfixParseFnToken = typeof infixParseFnTokenSchema.Type;
29 |
--------------------------------------------------------------------------------
/src/schemas/token/unions/parse-fn/prefix.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from "effect";
2 | import { fnTokenSchema } from "../../function-literal";
3 | import { groupedTokenSchema } from "../../grouped";
4 | import { identTokenSchema } from "../../ident";
5 | import { ifTokenSchema } from "../../if";
6 | import { intTokenSchema } from "../../int";
7 | import { LBRACKET } from "../../lbracket";
8 | import { stringTokenSchema } from "../../string";
9 | import { boolTokenSchema } from "../boolean";
10 | import { prefixTokenSchema } from "../prefix";
11 |
12 | export const prefixParseFnTokenSchema = Schema.Union(
13 | identTokenSchema,
14 | intTokenSchema,
15 | ifTokenSchema,
16 | fnTokenSchema,
17 | boolTokenSchema,
18 | prefixTokenSchema,
19 | stringTokenSchema,
20 | groupedTokenSchema,
21 | LBRACKET,
22 | );
23 |
24 | export type PrefixParseFnToken = typeof prefixParseFnTokenSchema.Type;
25 |
--------------------------------------------------------------------------------
/src/schemas/token/unions/parse-statement.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from 'effect'
2 | import { letTokenSchema } from '../let'
3 | import { returnTokenSchema } from '../return'
4 |
5 | export const parseStatementTokenSchema = Schema.Union(
6 | letTokenSchema,
7 | returnTokenSchema,
8 | )
9 |
10 | export type ParseStatementToken = typeof parseStatementTokenSchema.Type
11 |
--------------------------------------------------------------------------------
/src/schemas/token/unions/prefix.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from 'effect'
2 | import { bangTokenSchema } from '../bang'
3 | import { minusTokenSchema } from '../minus'
4 |
5 | export const prefixTokenSchema = Schema.Union(bangTokenSchema, minusTokenSchema)
6 |
7 | export type PrefixToken = typeof prefixTokenSchema.Type
8 |
--------------------------------------------------------------------------------
/src/schemas/utils/create-token-literal-schema.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from 'effect'
2 | import type { TokenType } from 'src/schemas/token-types/union'
3 |
4 | export const createTokenLiteralSchema = (tokenType: T) =>
5 | Schema.TaggedStruct(tokenType, {
6 | literal: Schema.Literal(tokenType),
7 | })
8 |
--------------------------------------------------------------------------------
/src/scratch/nerdamer.ts:
--------------------------------------------------------------------------------
1 | import nerdamer from "nerdamer-prime";
2 |
3 | nerdamer.setFunction("g(y)=y^3");
4 |
5 | const e = nerdamer("diff(2 * g(x) + x,x)");
6 |
7 | const b = nerdamer("diff(x^2+2*(cos(x)+x*x),x)");
8 |
9 | console.log(e.text());
10 |
11 | console.log(b.text());
12 |
--------------------------------------------------------------------------------
/src/scratch/recursive.ts:
--------------------------------------------------------------------------------
1 | // import { Console, Effect } from 'effect'
2 | // import { Schema } from 'effect'
3 |
4 | // // interface Exp1 extends Schema.Schema.Encoded {
5 | // // type: 'expression'
6 | // // value: number | ExpEncoded
7 | // // }
8 |
9 | // // interface Exp2 extends Schema.Schema.Encoded {
10 | // // type: 'expression'
11 | // // value: string
12 | // // }xqx
13 |
14 | // // interface Exp3 extends Schema.Schema.Encoded {
15 | // // type: 'expression'
16 | // // value: string
17 | // // }
18 |
19 | // class Exp2 extends Schema.TaggedClass()('Exp2', {
20 | // value: Schema.String,
21 | // }) {
22 | // expressionNode() {}
23 | // }
24 |
25 | // type Exp1E = {
26 | // _tag: 'Exp1'
27 | // value: ExpEncoded
28 | // }
29 |
30 | // class Exp1 extends Schema.TaggedClass()('Exp1', {
31 | // value: Schema.suspend((): Schema.Schema => Exp),
32 | // }) {
33 | // expressionNode() {}
34 | // }
35 |
36 | // export type Exp1Encoded = Omit, 'value'>
37 | // export type Exp2Encoded = Schema.Schema.Encoded
38 | // // export type Exp3Encoded = Schema.Schema.Encoded
39 |
40 | // export type Exp = Exp1 | Exp2
41 | // export type ExpEncoded = Exp1E | Exp2Encoded
42 | // // export type ExpEncoded = Schema.Schema.Encoded
43 |
44 | // // export type ExpEncoded = Exp1Encoded | Exp2Encoded | Exp3Encoded
45 |
46 | // const Exp = Schema.Union(Exp1, Exp2)
47 |
48 | // const program = Effect.gen(function* () {
49 | // yield* Console.log(
50 | // Schema.decodeUnknownSync(Exp)({
51 | // _tag: 'Exp1',
52 | // value: {
53 | // _tag: 'Exp2',
54 | // value: 'cow',
55 | // },
56 | // }),
57 | // )
58 | // // yield* Effect.log('Welcome to the Effect Playground!')
59 | // // const exp2 = new Exp2({ type: 'expression', value: 'something' })
60 | // // const exp1 = new Exp1({ type: 'expression', value: exp2 })
61 | // // if (typeof exp1.value === 'number') {
62 | // // } else {
63 | // // yield* Effect.log(exp1.value.value)
64 | // // }
65 | // }).pipe(
66 | // Effect.withSpan('program', {
67 | // attributes: { source: 'Playground' },
68 | // }),
69 | // )
70 |
71 | // Effect.runSync(program)
72 |
--------------------------------------------------------------------------------
/src/scratch/trace.ts:
--------------------------------------------------------------------------------
1 | import { Evaluator } from "@/services/evaluator";
2 | import { objInspect } from "@/services/object";
3 | import { Parser } from "@/services/parser";
4 | import { NodeSdk } from "@effect/opentelemetry";
5 | import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
6 | import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
7 | import { Effect, Layer, ManagedRuntime } from "effect";
8 |
9 | // Function to simulate a task with possible subtasks
10 | const task = (
11 | name: string,
12 | delay: number,
13 | children: ReadonlyArray> = [],
14 | ) =>
15 | Effect.gen(function* () {
16 | yield* Effect.log(name);
17 | yield* Effect.sleep(`${delay} millis`);
18 | for (const child of children) {
19 | yield* child;
20 | }
21 | yield* Effect.sleep(`${delay} millis`);
22 | }).pipe(Effect.withSpan(name));
23 |
24 | const poll = task("/poll", 1);
25 |
26 | // Create a program with tasks and subtasks
27 | const exampleProgram = task("client", 2, [
28 | task("/api", 3, [
29 | task("/authN", 4, [task("/authZ", 5)]),
30 | task("/payment Gateway", 6, [task("DB", 7), task("Ext. Merchant", 8)]),
31 | task("/dispatch", 9, [
32 | task("/dispatch/search", 10),
33 | Effect.all([poll, poll, poll], { concurrency: "inherit" }),
34 | task("/pollDriver/{id}", 11),
35 | ]),
36 | ]),
37 | ]);
38 |
39 | const NodeSdkLive = NodeSdk.layer(() => ({
40 | resource: { serviceName: "example" },
41 | spanProcessor: new BatchSpanProcessor(new OTLPTraceExporter()),
42 | }));
43 |
44 | Effect.runPromise(
45 | exampleProgram.pipe(
46 | Effect.provide(NodeSdkLive),
47 | Effect.catchAllCause(Effect.logError),
48 | ),
49 | );
50 |
51 | const program = (input: string) =>
52 | Effect.gen(function* () {
53 | const evaluator = yield* Evaluator;
54 | const returnValue = yield* evaluator.runAndInterpret(input);
55 | const evaluated = objInspect(returnValue.evaluation);
56 | return evaluated;
57 | });
58 |
59 | ManagedRuntime.make(
60 | Layer.mergeAll(Parser.Default, Evaluator.Default),
61 | ).runPromise(
62 | program("diff(fn (x) { 1 / (2 * x + 3) })(3)").pipe(
63 | Effect.provide(NodeSdkLive),
64 | Effect.catchAllCause(Effect.logError),
65 | ),
66 | );
67 |
--------------------------------------------------------------------------------
/src/scratch/two.ts:
--------------------------------------------------------------------------------
1 | import { nodeString } from "@/schemas/nodes/union";
2 | import { Console, Effect } from "effect";
3 | import { Schema } from "effect";
4 | import { nativeToExp } from "src/schemas/nodes/exps/union";
5 | import { ReturnStmt } from "src/schemas/nodes/stmts/return";
6 | import {
7 | type Stmt,
8 | isExpStmt,
9 | Stmt,
10 | } from "src/schemas/nodes/stmts/union";
11 |
12 | const program = Effect.gen(function* () {
13 | const istmt = nativeToExp(6);
14 | const rstmt = ReturnStmt.make({
15 | token: { _tag: "let", literal: "let" },
16 | value: istmt,
17 | }) as Stmt;
18 | yield* Console.log(Schema.decodeUnknownSync(Stmt)(rstmt));
19 |
20 | if (isExpStmt(rstmt)) {
21 | console.log(`${nodeString(rstmt)}`);
22 | }
23 | }).pipe(
24 | Effect.withSpan("program", {
25 | attributes: { source: "Playground" },
26 | }),
27 | );
28 |
29 | Effect.runSync(program);
30 |
--------------------------------------------------------------------------------
/src/scratch/yield-undefined.ts:
--------------------------------------------------------------------------------
1 | // import { Effect } from 'effect'
2 |
3 | // const program = Effect.gen(function* () {
4 | // let cow: { hey: Effect.Effect } | undefined = {
5 | // hey: Effect.succeed('hey'),
6 | // }
7 | // cow = Math.random() < 0.5 ? undefined : { hey: Effect.succeed('hey') }
8 |
9 | // return yield* cow?.hey
10 | // })
11 |
--------------------------------------------------------------------------------
/src/services/ast/index.ts:
--------------------------------------------------------------------------------
1 | import { isKNode } from 'src/schemas/nodes/union'
2 |
3 | export const isLetStatement = isKNode('LetStmt')
4 | export const isReturnStatement = isKNode('ReturnStmt')
5 | export const isExpressionStatement = isKNode('ExpStmt')
6 | export const isIdentExpression = isKNode('IdentExp')
7 | export const isIntegerLiteral = isKNode('IntExp')
8 | export const isPrefixExpression = isKNode('PrefixExp')
9 | export const isInfixExpression = isKNode('InfixExp')
10 | export const isBooleanExpression = isKNode('BoolExp')
11 | export const isIfExpression = isKNode('IfExp')
12 | export const isBlockStatement = isKNode('BlockStmt')
13 | export const isFunctionLiteral = isKNode('FuncExp')
14 | export const isCallExpression = isKNode('CallExp')
15 | export const isStringLiteral = isKNode('StrExp')
16 |
--------------------------------------------------------------------------------
/src/services/cli.ts:
--------------------------------------------------------------------------------
1 | // Import necessary modules from the libraries
2 | import { Args, Command } from "@effect/cli";
3 | import { Terminal } from "@effect/platform";
4 | import { BunContext, BunRuntime } from "@effect/platform-bun";
5 | import { Console, Effect, Layer } from "effect";
6 | import { Eval } from "./evaluator";
7 | import { objInspect } from "./object";
8 | import { createEnvironment } from "./object/environment";
9 | import { Parser } from "./parser";
10 | import { PROMPT } from "./repl/constants";
11 |
12 | // Define the top-level command
13 | const topLevel = Command.make("hello-world", {}, () =>
14 | Console.log("Hello friend, welcome to the Monkey programming language"),
15 | );
16 |
17 | // Define a text argument
18 | const text = Args.text({ name: "text" });
19 |
20 | // Create a command that logs the provided text argument to the console
21 | // this takes text I want to return tokens on input
22 | const echoCommand = Command.make("echo", { text }, ({ text }) =>
23 | Console.log(text),
24 | );
25 |
26 | const replCommand = Command.make(
27 | "repl",
28 | {},
29 | () =>
30 | Effect.gen(function* () {
31 | const terminal = yield* Terminal.Terminal;
32 | const env = createEnvironment();
33 | while (true) {
34 | yield* terminal.display(`${PROMPT}`);
35 | const input = yield* terminal.readLine;
36 | const parser = yield* Parser;
37 | yield* parser.init(input);
38 | const program = yield* parser.parseProgram;
39 | const evaluation = yield* Eval(program)(env, undefined);
40 | yield* Console.log(objInspect(evaluation));
41 | }
42 | }),
43 | // Console.log(input),
44 | );
45 |
46 | const command = topLevel.pipe(
47 | Command.withSubcommands([echoCommand, replCommand]),
48 | );
49 |
50 | // Set up the CLI application
51 | const cli = Command.run(command, {
52 | name: "Hello World CLI",
53 | version: "v1.0.0",
54 | });
55 |
56 | // Prepare and run the CLI application
57 | cli(process.argv).pipe(
58 | Effect.provide(Layer.mergeAll(Parser.Default, BunContext.layer)),
59 | BunRuntime.runMain,
60 | );
61 |
--------------------------------------------------------------------------------
/src/services/diff/helper.ts:
--------------------------------------------------------------------------------
1 | import type { IdentExp } from "@/schemas/nodes/exps/ident";
2 | import type { Exp } from "@/schemas/nodes/exps/union";
3 | import { BlockStmt } from "@/schemas/nodes/stmts/block";
4 | import { ExpStmt } from "@/schemas/nodes/stmts/exp";
5 | import { CallObj } from "@/schemas/objs/call";
6 | import { FunctionObj } from "@/schemas/objs/function";
7 | import type { PolynomialObj } from "@/schemas/objs/unions/polynomials";
8 | import { Effect } from "effect";
9 | import { createEnvironment } from "../object/environment";
10 |
11 | export const makeLambda = (
12 | x: IdentExp,
13 | args: readonly Exp[],
14 | expression: Exp,
15 | ): Effect.Effect =>
16 | Effect.succeed(
17 | CallObj.make({
18 | fn: FunctionObj.make({
19 | env: createEnvironment(),
20 | params: [x],
21 | body: BlockStmt.make({
22 | token: { _tag: "!", literal: "!" },
23 | statements: [
24 | ExpStmt.make({
25 | token: {
26 | _tag: "!",
27 | literal: "!",
28 | },
29 | expression,
30 | }),
31 | ],
32 | }),
33 | }),
34 | args,
35 | }),
36 | );
37 |
--------------------------------------------------------------------------------
/src/services/diff/obj.ts:
--------------------------------------------------------------------------------
1 | import { BuiltInDiffFunc } from "@/schemas/built-in/diff";
2 | import { CallExp } from "@/schemas/nodes/exps/call";
3 | import { OpInfixExp } from "@/schemas/nodes/exps/infix";
4 | import { nativeToIntExp } from "@/schemas/nodes/exps/int";
5 | import { PrefixExp, opPrefixExp } from "@/schemas/nodes/exps/prefix";
6 | import { BuiltInObj } from "@/schemas/objs/built-in";
7 | import { BuiltInCallObj, CallObj } from "@/schemas/objs/call";
8 | import { IdentObj } from "@/schemas/objs/ident";
9 | import { InfixObj } from "@/schemas/objs/infix";
10 | import { IntegerObj, ONE } from "@/schemas/objs/int";
11 | import {
12 | PolynomialObj,
13 | chainRule,
14 | constantRule,
15 | powerRule,
16 | productRule,
17 | quotientRule,
18 | sumAndDifferenceRule,
19 | } from "@/schemas/objs/unions/polynomials";
20 | import { Effect, Match, Schema } from "effect";
21 | import type { ParseError } from "effect/ParseResult";
22 | import { KennethParseError } from "../../errors/kenneth/parse";
23 | import {
24 | IdentExp,
25 | expectIdentEquivalence,
26 | } from "../../schemas/nodes/exps/ident";
27 | import { TokenType } from "../../schemas/token-types/union";
28 | import { makeLambda } from "./helper";
29 |
30 | const baseBuiltInDiffFunc =
31 | (x: IdentExp) =>
32 | ({ fn, args }: CallObj) =>
33 | Schema.decodeUnknown(BuiltInObj)(fn).pipe(
34 | Effect.flatMap((fn) =>
35 | Schema.decodeUnknown(BuiltInDiffFunc)(fn.fn).pipe(
36 | Effect.flatMap((diffFn) =>
37 | Match.value(diffFn).pipe(
38 | Match.when("sin", () =>
39 | Effect.succeed(
40 | CallObj.make({
41 | fn: BuiltInObj.make({ fn: "cos" }),
42 | args,
43 | }),
44 | ),
45 | ),
46 | Match.when("cos", () =>
47 | makeLambda(
48 | x,
49 | args,
50 | opPrefixExp("-")(
51 | CallExp.make({
52 | token: {
53 | _tag: "(",
54 | literal: "(",
55 | },
56 | fn: IdentExp.make({
57 | token: {
58 | _tag: "IDENT",
59 | literal: "sin",
60 | },
61 | value: "sin",
62 | }),
63 | args: [x],
64 | }),
65 | ),
66 | ),
67 | ),
68 | Match.when("tan", () =>
69 | makeLambda(
70 | x,
71 | args,
72 | OpInfixExp("/")(nativeToIntExp(1))(
73 | OpInfixExp("**")(
74 | CallExp.make({
75 | token: {
76 | _tag: "(",
77 | literal: "(",
78 | },
79 | fn: IdentExp.make({
80 | token: {
81 | _tag: "IDENT",
82 | literal: "cos",
83 | },
84 | value: "cos",
85 | }),
86 | args: [x],
87 | }),
88 | )(nativeToIntExp(2)),
89 | ),
90 | ),
91 | ),
92 | Match.when("ln", () =>
93 | makeLambda(x, args, OpInfixExp("/")(nativeToIntExp(1))(x)),
94 | ),
95 | Match.when("exp", () =>
96 | Effect.succeed(BuiltInCallObj("exp")(args)),
97 | ),
98 | Match.exhaustive,
99 | ),
100 | ),
101 | ),
102 | ),
103 | );
104 |
105 | const processTerm = (exp: PolynomialObj, x: IdentExp) =>
106 | Match.value(exp).pipe(
107 | Match.tag("IntegerObj", () => constantRule()),
108 | Match.tag("IdentObj", () => Effect.succeed(powerRule(ONE, ONE, x))),
109 | Match.tag("InfixObj", ({ left, operator, right }) =>
110 | Schema.decodeUnknown(
111 | Schema.Literal(TokenType.ASTERISK, TokenType.EXPONENT),
112 | )(operator).pipe(
113 | Effect.flatMap((operator) =>
114 | Match.value(operator).pipe(
115 | Match.when(TokenType.ASTERISK, () =>
116 | Schema.decodeUnknown(IntegerObj)(left).pipe(
117 | Effect.flatMap((coeff) =>
118 | Match.value(right).pipe(
119 | Match.tag("IdentObj", () => Effect.succeed(coeff)),
120 | Match.tag(
121 | "InfixObj",
122 | ({
123 | left,
124 | operator: secondOperator,
125 | right: secondRight,
126 | }) =>
127 | Effect.all([
128 | Schema.decodeUnknown(
129 | Schema.Literal(TokenType.EXPONENT),
130 | )(secondOperator),
131 | Schema.decodeUnknown(IntegerObj)(secondRight),
132 | ]).pipe(
133 | Effect.flatMap(([operator, power]) =>
134 | Effect.succeed(powerRule(coeff, power, x)),
135 | ),
136 | ),
137 | ),
138 | Match.orElse(() =>
139 | Effect.fail(new KennethParseError({ message: "failed" })),
140 | ),
141 | ),
142 | ),
143 | ),
144 | ),
145 | Match.when(TokenType.EXPONENT, () =>
146 | Schema.decodeUnknown(IdentObj)(left).pipe(
147 | Effect.flatMap(({ identExp }) =>
148 | Effect.gen(function* () {
149 | yield* expectIdentEquivalence(identExp, x);
150 |
151 | const integerObj =
152 | yield* Schema.decodeUnknown(IntegerObj)(right);
153 |
154 | return powerRule(ONE, integerObj, x);
155 | }),
156 | ),
157 | ),
158 | ),
159 | Match.exhaustive,
160 | ),
161 | ),
162 | ),
163 | ),
164 | Match.tag("CallObj", baseBuiltInDiffFunc(x)),
165 | Match.exhaustive,
166 | );
167 |
168 | export const diffPolynomial = (
169 | obj: PolynomialObj,
170 | x: IdentExp,
171 | ): Effect.Effect =>
172 | Match.value(obj).pipe(
173 | Match.tag("IntegerObj", () => constantRule()), // leaf
174 | Match.tag("CallObj", baseBuiltInDiffFunc(x)),
175 | Match.tag("IdentObj", () => Effect.succeed(powerRule(ONE, ONE, x))), // leaf
176 | Match.tag("InfixObj", ({ left, operator, right }) =>
177 | Effect.all([
178 | Schema.decodeUnknown(PolynomialObj)(left),
179 | Schema.decodeUnknown(
180 | Schema.Literal(
181 | TokenType.MINUS,
182 | TokenType.PLUS,
183 | TokenType.ASTERISK,
184 | TokenType.SLASH,
185 | TokenType.EXPONENT,
186 | ),
187 | )(operator),
188 | Schema.decodeUnknown(PolynomialObj)(right),
189 | ]).pipe(
190 | Effect.flatMap(([left, operator, right]) =>
191 | Match.value(operator).pipe(
192 | Match.when(TokenType.ASTERISK, () => productRule(left, right, x)),
193 | Match.when(TokenType.SLASH, () => quotientRule(left, right, x)),
194 | Match.when(TokenType.EXPONENT, (operator) =>
195 | Match.value(left).pipe(
196 | Match.tag("InfixObj", () =>
197 | chainRule(
198 | InfixObj.make({
199 | left: IdentObj.make({ identExp: x }),
200 | operator,
201 | right,
202 | }),
203 | left,
204 | x,
205 | ),
206 | ),
207 | Match.orElse(() => processTerm(obj, x)),
208 | ),
209 | ),
210 | Match.when(TokenType.PLUS, (plus) =>
211 | sumAndDifferenceRule(left, right, x, plus),
212 | ),
213 | Match.when(TokenType.MINUS, (minus) =>
214 | sumAndDifferenceRule(left, right, x, minus),
215 | ),
216 | Match.exhaustive,
217 | ),
218 | ),
219 | ),
220 | ),
221 | Match.exhaustive,
222 | );
223 |
--------------------------------------------------------------------------------
/src/services/evaluator/constants.ts:
--------------------------------------------------------------------------------
1 | import { TokenType } from '../../schemas/token-types/union'
2 |
3 | export const OPERATOR_TO_FUNCTION_MAP = {
4 | [TokenType.PLUS]: (a: number, b: number) => a + b,
5 | [TokenType.MINUS]: (a: number, b: number) => a - b,
6 | [TokenType.ASTERISK]: (a: number, b: number) => a * b,
7 | [TokenType.SLASH]: (a: number, b: number) => a / b,
8 | [TokenType.EXPONENT]: (a: number, b: number) => a ** b,
9 | [TokenType.LT]: (a: number, b: number) => a < b,
10 | [TokenType.GT]: (a: number, b: number) => a > b,
11 | [TokenType.EQ]: (a: T, b: T) => a === b,
12 | [TokenType.NOT_EQ]: (a: T, b: T) => a !== b,
13 | } as const
14 |
15 | export const STRING_OPERATOR_TO_FUNCTION_MAP = {
16 | [TokenType.PLUS]: (a: string, b: string) => a + b,
17 | } as const
18 |
--------------------------------------------------------------------------------
/src/services/evaluator/index.ts:
--------------------------------------------------------------------------------
1 | import type { IndexExp } from "@/schemas/nodes/exps";
2 | import type { ArrayExp } from "@/schemas/nodes/exps/array";
3 | import { CallExp } from "@/schemas/nodes/exps/call";
4 | import { FuncExp } from "@/schemas/nodes/exps/function";
5 | import { ExpStmt } from "@/schemas/nodes/stmts/exp";
6 | import { ArrayObj } from "@/schemas/objs/array";
7 | import { FALSE, TRUE, nativeBoolToObjectBool } from "@/schemas/objs/bool";
8 | import { BuiltInDiffObj, BuiltInObj } from "@/schemas/objs/built-in";
9 | import { CallObj } from "@/schemas/objs/call";
10 | import { FunctionObj } from "@/schemas/objs/function";
11 | import { IdentObj } from "@/schemas/objs/ident";
12 | import { InfixObj } from "@/schemas/objs/infix";
13 | import { IntegerObj } from "@/schemas/objs/int";
14 | import { NULL } from "@/schemas/objs/null";
15 | import { ReturnObj } from "@/schemas/objs/return";
16 | import { StringObj } from "@/schemas/objs/string";
17 | import type { Obj } from "@/schemas/objs/union";
18 | import { PolynomialObj } from "@/schemas/objs/unions/polynomials";
19 | import { fnTokenSchema } from "@/schemas/token/function-literal";
20 | import { Effect, Either, Match, Schema } from "effect";
21 | import type { ParseError } from "effect/ParseResult";
22 | import type { KennethParseError } from "src/errors/kenneth/parse";
23 | import type { DiffExp } from "src/schemas/nodes/exps/diff";
24 | import { IdentExp, IdentExpEq } from "src/schemas/nodes/exps/ident";
25 | import type { IfExp } from "src/schemas/nodes/exps/if";
26 | import { OpInfixExp } from "src/schemas/nodes/exps/infix";
27 | import { nativeToIntExp } from "src/schemas/nodes/exps/int";
28 | import { type Exp, isIdentExp } from "src/schemas/nodes/exps/union";
29 | import type { Program } from "src/schemas/nodes/program";
30 | import { BlockStmt } from "src/schemas/nodes/stmts/block";
31 | import type { Stmt } from "src/schemas/nodes/stmts/union";
32 | import { type KNode, matchKnode, nodeString } from "src/schemas/nodes/union";
33 | import type { PrefixOperator } from "src/schemas/prefix-operator";
34 | import { TokenType } from "src/schemas/token-types/union";
35 | import type { Token } from "src/schemas/token/unions/all";
36 | import { KennethEvalError } from "../../errors/kenneth/eval";
37 | import type { InfixOperator } from "../../schemas/infix-operator";
38 | import { diffPolynomial } from "../diff/obj";
39 | import {
40 | isErrorObj,
41 | isIdentObj,
42 | isInfixObj,
43 | isIntegerObj,
44 | isReturnObj,
45 | isStringObj,
46 | objInspect,
47 | } from "../object";
48 | import { builtInFnMap } from "../object/builtins";
49 | import {
50 | Environment,
51 | createEnvironment,
52 | get,
53 | set,
54 | } from "../object/environment";
55 | import { Parser } from "../parser";
56 | import {
57 | OPERATOR_TO_FUNCTION_MAP,
58 | STRING_OPERATOR_TO_FUNCTION_MAP,
59 | } from "./constants";
60 |
61 | // this error is what we pay for!!!
62 | const nodeEvalMatch = (env: Environment) =>
63 | matchKnode({
64 | Program: ({ statements }) =>
65 | evalProgram(statements, env).pipe(Effect.map(unwrapReturnvalue)),
66 | IdentExp: (ident) =>
67 | Effect.gen(function* () {
68 | if (env.idents.some((id) => IdentExpEq(id, ident))) {
69 | yield* Effect.log(`ident: ${nodeString(ident)} is contaminated`);
70 | return yield* Effect.succeed(IdentObj.make({ identExp: ident }));
71 | }
72 | return yield* evalIdentExpression(ident, env);
73 | }),
74 | LetStmt: ({ value, name }) =>
75 | Effect.gen(function* () {
76 | const val = yield* Eval(value)(env);
77 | set(env)(name.value, val);
78 | return NULL;
79 | }),
80 | ReturnStmt: ({ value }) =>
81 | Eval(value)(env).pipe(
82 | Effect.map((obj) => ReturnObj.make({ value: obj })),
83 | ),
84 | ExpStmt: ({ expression }) => Eval(expression)(env),
85 | IntExp: ({ value }) => Effect.succeed(IntegerObj.make({ value })),
86 | PrefixExp: ({ operator, right }) =>
87 | Eval(right)(env).pipe(
88 | Effect.flatMap((r) => evalPrefixExpression(operator)(r)),
89 | ),
90 | InfixExp: ({ left, operator, right }) =>
91 | Effect.all([Eval(left)(env), Eval(right)(env)]).pipe(
92 | Effect.flatMap(([leftVal, rightVal]) =>
93 | evalInfixExpression(operator)(leftVal)(rightVal),
94 | ),
95 | ),
96 | BoolExp: ({ value }) => Effect.succeed(nativeBoolToObjectBool(value)),
97 | IfExp: (ie) => evalIfExpression(ie, env),
98 | BlockStmt: (stmt) => evalBlockStatement(stmt, env),
99 | FuncExp: ({ parameters, body }) =>
100 | Effect.succeed(FunctionObj.make({ params: parameters, body, env })),
101 | CallExp: ({ fn, args }) =>
102 | Effect.all([Eval(fn)(env), evalExpressions(args, env)]).pipe(
103 | Effect.flatMap(([fnEval, argsEval]) =>
104 | Schema.decodeUnknown(Schema.Union(FunctionObj, BuiltInObj))(
105 | fnEval,
106 | ).pipe(
107 | Effect.flatMap((obj) =>
108 | Effect.gen(function* () {
109 | const identoverlap = env.idents.some((ident) =>
110 | args.some(
111 | (arg) => isIdentExp(arg) && ident.value === arg.value,
112 | ),
113 | );
114 |
115 | const either =
116 | Schema.decodeUnknownEither(BuiltInDiffObj)(fnEval);
117 |
118 | return yield* Either.isRight(either) && identoverlap
119 | ? Effect.succeed(CallObj.make({ fn: either.right, args }))
120 | : applyFunction(obj)(argsEval);
121 | }),
122 | ),
123 | ),
124 | ),
125 | ),
126 | StrExp: ({ value }) => Effect.succeed(StringObj.make({ value })),
127 | DiffExp: (diffExp) => evalDiff(diffExp)(env),
128 | ArrayExp: (arrayExp) => evalArrayExp(arrayExp)(env),
129 | IndexExp: (indexExp) => evalIndexExp(indexExp)(env),
130 | });
131 |
132 | const evalIndexExp = (indexExp: IndexExp) => (env: Environment) =>
133 | Effect.all([Eval(indexExp.left)(env), Eval(indexExp.index)(env)]).pipe(
134 | Effect.flatMap(([left, index]) =>
135 | Effect.all([
136 | Schema.decodeUnknown(ArrayObj)(left),
137 | Schema.decodeUnknown(IntegerObj)(index),
138 | ]).pipe(
139 | Effect.flatMap(([{ elements }, { value }]) =>
140 | Schema.decodeUnknown(
141 | Schema.Number.pipe(Schema.between(0, elements.length - 1)),
142 | )(value).pipe(Effect.flatMap((idx) => Effect.succeed(elements[idx]))),
143 | ),
144 | ),
145 | ),
146 | );
147 |
148 | const evalArrayExp = (arrayExp: ArrayExp) => (env: Environment) =>
149 | Effect.gen(function* () {
150 | const elements = yield* evalExpressions(arrayExp.elements, env);
151 |
152 | if (elements.length === 1 && isErrorObj(elements[0])) {
153 | return elements[0];
154 | }
155 | return ArrayObj.make({ elements });
156 | });
157 |
158 | export const Eval =
159 | (node: KNode) =>
160 | (
161 | env: Environment,
162 | ): Effect.Effect<
163 | Obj,
164 | KennethEvalError | ParseError | KennethParseError,
165 | never
166 | > =>
167 | nodeEvalMatch(env)(node).pipe(Effect.withSpan("eval.Eval"));
168 |
169 | export const evalDiff = (diffExp: DiffExp) => (env: Environment) =>
170 | Effect.gen(function* () {
171 | // yield* logDebug('evalDiff')
172 | // this is during running this function. the diff function to be sure! so it will return a number
173 | // NEED to do a soft eval of all the expressions here.
174 | const newEnv = Environment.make({
175 | store: env.store,
176 | outer: env.outer,
177 | idents: [...env.idents, ...diffExp.params],
178 | });
179 | yield* Effect.log("soft eval:");
180 | const softEval = yield* Eval(diffExp.exp)(newEnv).pipe(
181 | Effect.flatMap(Schema.decodeUnknown(PolynomialObj)),
182 | );
183 |
184 | yield* Effect.log("diff:");
185 | const diffSoftEval = yield* diffPolynomial(softEval, diffExp.params[0]);
186 |
187 | // maybe simplest will be to convert back to exp and Eval.
188 | const convertToExp = (obj: PolynomialObj): Effect.Effect =>
189 | Match.value(obj).pipe(
190 | Match.tag("IntegerObj", ({ value }) =>
191 | Effect.succeed(nativeToIntExp(value)),
192 | ),
193 | Match.tag("IdentObj", ({ identExp }) => Effect.succeed(identExp)),
194 | Match.tag("InfixObj", ({ left, operator, right }) =>
195 | Effect.all(
196 | [left, right].map((obj: Obj) =>
197 | Schema.decodeUnknown(PolynomialObj)(obj).pipe(
198 | Effect.flatMap(convertToExp),
199 | ),
200 | ),
201 | ).pipe(
202 | Effect.flatMap(([leftVal, rightVal]) =>
203 | Effect.succeed(OpInfixExp(operator)(leftVal)(rightVal)),
204 | ),
205 | ),
206 | ),
207 | Match.tag("CallObj", ({ fn, args }) =>
208 | Effect.succeed(
209 | CallExp.make({
210 | token: { _tag: "fn", literal: "fn" },
211 | fn: FuncExp.make({
212 | token: { _tag: "fn", literal: "fn" },
213 | parameters: diffExp.params, // LIMITATION TO A SINGLE VARIABLE FUNCTIONS.
214 | body: BlockStmt.make({
215 | token: { _tag: "!", literal: "!" }, // FIX eventually
216 | statements: [
217 | ExpStmt.make({
218 | token: {
219 | _tag: "!",
220 | literal: "!",
221 | },
222 | expression: CallExp.make({
223 | token: {
224 | _tag: "!",
225 | literal: "!",
226 | },
227 | fn: Match.value(fn).pipe(
228 | Match.tag("BuiltInObj", ({ fn }) =>
229 | IdentExp.make({
230 | token: { _tag: "IDENT", literal: fn },
231 | value: fn,
232 | }),
233 | ),
234 | Match.tag("FunctionObj", ({ params, body }) =>
235 | FuncExp.make({
236 | token: fnTokenSchema.make({ literal: "fn" }),
237 | parameters: params,
238 | body,
239 | }),
240 | ),
241 | Match.exhaustive,
242 | ),
243 | args,
244 | }),
245 | }),
246 | ],
247 | }),
248 | }),
249 | args,
250 | }),
251 | ),
252 | ),
253 | Match.exhaustive,
254 | );
255 |
256 | const expResult = yield* convertToExp(diffSoftEval);
257 |
258 | // yield* logDebug('exp', expResult)
259 | // yield* logDebug('--------------------------')
260 |
261 | return yield* Eval(expResult)(env);
262 | });
263 |
264 | export const applyFunction = (fn: FunctionObj | BuiltInObj) => (args: Obj[]) =>
265 | Match.value(fn).pipe(
266 | Match.tag("BuiltInObj", (fn) => builtInFnMap[fn.fn](...args)),
267 | Match.tag("FunctionObj", (fn) =>
268 | extendFunctionEnv(fn, args).pipe(
269 | Effect.flatMap((env) => Eval(fn.body)(env)),
270 | Effect.map(unwrapReturnvalue),
271 | ),
272 | ),
273 | Match.exhaustive,
274 | Effect.withSpan("eval.applyFunction"),
275 | );
276 |
277 | export const extendFunctionEnv = (fn: FunctionObj, args: Obj[]) =>
278 | Effect.gen(function* () {
279 | const env = createEnvironment(fn.env);
280 | for (let i = 0; i < fn.params.length; i++) {
281 | set(env)(fn.params[i].value, args[i]);
282 | }
283 | return yield* Effect.succeed(env);
284 | }).pipe(Effect.withSpan("eval.extendFunctionEnv"));
285 |
286 | export const unwrapReturnvalue = (obj: Obj) =>
287 | isReturnObj(obj) ? obj.value : obj;
288 |
289 | export const evalExpressions = (exps: readonly Exp[], env: Environment) =>
290 | Effect.all(exps.map((exp) => Eval(exp)(env))).pipe(
291 | Effect.withSpan("eval.evalExpressions"),
292 | );
293 |
294 | export const evalIdentExpression = (ident: IdentExp, env: Environment) =>
295 | get(env)(ident.value).pipe(Effect.withSpan("eval.evalIdentExpression"));
296 |
297 | export const evalIfExpression = (ie: IfExp, env: Environment) =>
298 | Effect.gen(function* () {
299 | const condition = yield* Eval(ie.condition)(env).pipe(Effect.map(isTruthy));
300 | return condition
301 | ? yield* Eval(ie.consequence)(env)
302 | : ie.alternative
303 | ? yield* Eval(ie.alternative)(env)
304 | : NULL;
305 | }).pipe(Effect.withSpan("eval.evalIfExpression"));
306 |
307 | export const isTruthy = (obj: Obj) =>
308 | Match.value(obj).pipe(
309 | Match.tag("NullObj", () => false),
310 | Match.tag("BooleanObj", (obj) => obj.value),
311 | Match.orElse(() => true),
312 | );
313 |
314 | const evalStatements = (stmts: readonly Stmt[], env: Environment) =>
315 | Effect.gen(function* () {
316 | let result: Obj = NULL;
317 | for (const stmt of stmts) {
318 | result = yield* Eval(stmt)(env);
319 | if (isReturnObj(result)) {
320 | return result;
321 | }
322 | }
323 | return result;
324 | });
325 |
326 | export const evalProgram = (stmts: readonly Stmt[], env: Environment) =>
327 | evalStatements(stmts, env).pipe(Effect.withSpan("eval.evalProgram"));
328 |
329 | export const evalBlockStatement = (block: BlockStmt, env: Environment) =>
330 | evalStatements(block.statements, env).pipe(
331 | Effect.withSpan("eval.evalBlockStatement"),
332 | );
333 |
334 | export const evalInfixExpression =
335 | (operator: InfixOperator) => (left: Obj) => (right: Obj) =>
336 | Effect.gen(function* () {
337 | if (
338 | isIdentObj(left) ||
339 | isIdentObj(right) ||
340 | isInfixObj(left) ||
341 | isInfixObj(right)
342 | ) {
343 | return InfixObj.make({ left, operator, right }); // soft eval condition
344 | }
345 | if (isIntegerObj(left) && isIntegerObj(right)) {
346 | return evalIntegerInfixExpression(operator, left, right);
347 | }
348 | if (isStringObj(left) && isStringObj(right)) {
349 | const plus = yield* Schema.decodeUnknown(
350 | Schema.Literal(TokenType.PLUS),
351 | )(operator);
352 | return evalStringInfixExpression(plus, left, right);
353 | }
354 | if (operator === TokenType.EQ) {
355 | return nativeBoolToObjectBool(left === right); // we do object equality I guess?
356 | }
357 | if (operator === TokenType.NOT_EQ) {
358 | return nativeBoolToObjectBool(left !== right);
359 | }
360 | return yield* new KennethEvalError({
361 | message:
362 | left._tag !== right._tag
363 | ? `type mismatch: ${left._tag} ${operator} ${right._tag}`
364 | : `unknown operator: ${left._tag} ${operator} ${right._tag}`,
365 | });
366 | }).pipe(Effect.withSpan("eval.evalInfixExpression"));
367 |
368 | const nativeToObj = (result: number | boolean | string) =>
369 | Match.value(result).pipe(
370 | Match.when(Match.boolean, (bool) => nativeBoolToObjectBool(bool)),
371 | Match.when(Match.number, (num) => IntegerObj.make({ value: num })),
372 | Match.when(Match.string, (str) => StringObj.make({ value: str })),
373 | Match.exhaustive,
374 | );
375 |
376 | export const evalIntegerInfixExpression = (
377 | operator: InfixOperator,
378 | left: IntegerObj,
379 | right: IntegerObj,
380 | ) => nativeToObj(OPERATOR_TO_FUNCTION_MAP[operator](left.value, right.value));
381 |
382 | export const evalStringInfixExpression = (
383 | operator: typeof TokenType.PLUS,
384 | left: StringObj,
385 | right: StringObj,
386 | ) =>
387 | nativeToObj(
388 | STRING_OPERATOR_TO_FUNCTION_MAP[operator](left.value, right.value),
389 | );
390 |
391 | export const evalPrefixExpression =
392 | (operator: PrefixOperator) => (right: Obj) =>
393 | Match.value(operator).pipe(
394 | Match.when(TokenType.BANG, () => evalBangOperatorExpression(right)),
395 | Match.when(TokenType.MINUS, () =>
396 | Schema.decodeUnknown(IntegerObj)(right).pipe(
397 | Effect.flatMap(evalMinusPrefixOperatorExpression),
398 | ),
399 | ),
400 | Match.exhaustive,
401 | );
402 |
403 | export const evalMinusPrefixOperatorExpression = (right: IntegerObj) =>
404 | Effect.succeed(IntegerObj.make({ value: -right.value }));
405 |
406 | export const evalBangOperatorExpression = (right: Obj) =>
407 | Effect.succeed(
408 | Match.value(right).pipe(
409 | Match.tag("BooleanObj", (obj) => (obj.value ? FALSE : TRUE)),
410 | Match.tag("NullObj", () => TRUE),
411 | Match.orElse(() => FALSE),
412 | ),
413 | ).pipe(Effect.withSpan("eval.evalBangOperatorExpression"));
414 |
415 | export type ProgramInterpretation = {
416 | program: Program;
417 | evaluation: Obj;
418 | lexerStory: {
419 | input: string;
420 | tokens: Token[];
421 | pos1History: number[][];
422 | pos2History: number[][];
423 | };
424 | };
425 |
426 | export class Evaluator extends Effect.Service()("Evaluator", {
427 | effect: Effect.gen(function* () {
428 | const parser = yield* Parser;
429 | const run = (input: string) =>
430 | Effect.gen(function* () {
431 | yield* parser.init(input);
432 | const program = yield* parser.parseProgramOptimized;
433 | const env = createEnvironment();
434 | return yield* nodeEvalMatch(env)(program);
435 | }).pipe(Effect.withSpan("eval.run"));
436 |
437 | const runAndInterpret = (
438 | input: string,
439 | ): Effect.Effect<
440 | ProgramInterpretation,
441 | ParseError | KennethEvalError | KennethParseError,
442 | never
443 | > =>
444 | Effect.gen(function* () {
445 | yield* parser.init(input);
446 | const program = yield* parser.parseProgram;
447 | const lexerStory = yield* parser.getLexerStory;
448 | const parserStory = yield* parser.getParserStory;
449 | const env = createEnvironment();
450 | const evaluation = yield* nodeEvalMatch(env)(program);
451 | return {
452 | program,
453 | evaluation,
454 | lexerStory,
455 | parserStory,
456 | };
457 | });
458 |
459 | return {
460 | run,
461 | runAndInterpret,
462 | };
463 | }),
464 | dependencies: [Parser.Default],
465 | }) {}
466 |
--------------------------------------------------------------------------------
/src/services/expectations/exp/eq.ts:
--------------------------------------------------------------------------------
1 | import { KennethEvalError } from "@/errors/kenneth/eval";
2 | import { BoolExp } from "@/schemas/nodes/exps/boolean";
3 | import { IdentExp } from "@/schemas/nodes/exps/ident";
4 | import { IntExp } from "@/schemas/nodes/exps/int";
5 | import { StrExp } from "@/schemas/nodes/exps/str";
6 | import type { Exp } from "@/schemas/nodes/exps/union";
7 | import { Effect, Schema, pipe } from "effect";
8 |
9 | export const expectIdentExpEq = (exp: Exp, expected: string) =>
10 | pipe(
11 | exp,
12 | Schema.decodeUnknown(IdentExp),
13 | Effect.filterOrFail(
14 | (exp) => exp.value === expected,
15 | (exp) =>
16 | new KennethEvalError({
17 | message: `Expected '${expected}' but got '${exp.value}'`,
18 | }),
19 | ),
20 | );
21 |
22 | export const expectStrExpEq = (exp: Exp, expected: string) =>
23 | pipe(
24 | exp,
25 | Schema.decodeUnknown(StrExp),
26 | Effect.filterOrFail(
27 | (exp) => exp.value === expected,
28 | (exp) =>
29 | new KennethEvalError({
30 | message: `Expected '${expected}' but got '${exp.value}'`,
31 | }),
32 | ),
33 | );
34 |
35 | export const expectIntExpEq = (exp: Exp, expected: number) =>
36 | pipe(
37 | exp,
38 | Schema.decodeUnknown(IntExp),
39 | Effect.filterOrFail(
40 | (exp) => exp.value === expected,
41 | (exp) =>
42 | new KennethEvalError({
43 | message: `Expected '${expected}' but got '${exp.value}'`,
44 | }),
45 | ),
46 | );
47 |
48 | export const expectBooleanExpEq = (exp: Exp, expected: boolean) =>
49 | pipe(
50 | exp,
51 | Schema.decodeUnknown(BoolExp),
52 | Effect.filterOrFail(
53 | (exp) => exp.value === expected,
54 | (exp) =>
55 | new KennethEvalError({
56 | message: `Expected '${expected}' but got '${exp.value}'`,
57 | }),
58 | ),
59 | );
60 |
--------------------------------------------------------------------------------
/src/services/expectations/obj/eq.ts:
--------------------------------------------------------------------------------
1 | import { KennethEvalError } from "@/errors/kenneth/eval";
2 | import { BooleanObj } from "@/schemas/objs/bool";
3 | import { IntegerObj } from "@/schemas/objs/int";
4 | import { StringObj } from "@/schemas/objs/string";
5 | import type { Obj } from "@/schemas/objs/union";
6 | import { Effect, Schema, pipe } from "effect";
7 |
8 | export const expectStrObjEq = (obj: Obj, expected: string) =>
9 | pipe(
10 | obj,
11 | Schema.decodeUnknown(StringObj),
12 | Effect.filterOrFail(
13 | ({ value }) => value === expected,
14 | ({ value }) =>
15 | new KennethEvalError({
16 | message: `Expected '${expected}' but got '${value}'`,
17 | }),
18 | ),
19 | );
20 |
21 | export const expectIntObjEq = (obj: Obj, expected: number) =>
22 | pipe(
23 | obj,
24 | Schema.decodeUnknown(IntegerObj),
25 | Effect.filterOrFail(
26 | ({ value }) => value === expected,
27 | ({ value }) =>
28 | new KennethEvalError({
29 | message: `Expected '${expected}' but got '${value}'`,
30 | }),
31 | ),
32 | );
33 |
34 | export const expectBooleanObjEq = (obj: Obj, expected: boolean) =>
35 | pipe(
36 | obj,
37 | Schema.decodeUnknown(BooleanObj),
38 | Effect.filterOrFail(
39 | ({ value }) => value === expected,
40 | ({ value }) =>
41 | new KennethEvalError({
42 | message: `Expected '${expected}' but got '${value}'`,
43 | }),
44 | ),
45 | );
46 |
--------------------------------------------------------------------------------
/src/services/lexer/index.ts:
--------------------------------------------------------------------------------
1 | import { Effect, Either, Match } from "effect";
2 | import { decodeUnknownEither } from "effect/Schema";
3 | import { lexerSymbolSchema } from "../../schemas/chars/next-token";
4 | import { TokenType } from "../../schemas/token-types/union";
5 | import type { Token } from "../../schemas/token/unions/all";
6 | import { lookupIndent } from "../tokens";
7 | import { LexerStateService } from "./state";
8 |
9 | export class Lexer extends Effect.Service()("Lexer", {
10 | effect: Effect.gen(function* () {
11 | const {
12 | getInput,
13 | setInput,
14 | getPos1,
15 | getPos2,
16 | incPos2,
17 | getChar,
18 | setChar,
19 | setPos1,
20 | getTokens,
21 | saveToken,
22 | getPos1History,
23 | getPos2History,
24 | saveToPos1History,
25 | saveToPos2History,
26 | } = yield* LexerStateService;
27 |
28 | const init = setInput;
29 |
30 | const readChar = Effect.gen(function* () {
31 | const input = yield* getInput;
32 | const pos2 = yield* getPos2;
33 |
34 | if (pos2 >= input.length) {
35 | yield* setChar("\0");
36 | } else {
37 | yield* setChar(input[pos2]);
38 | }
39 | yield* setPos1(pos2);
40 | yield* incPos2;
41 |
42 | yield* saveToPos1History(yield* getPos1);
43 | yield* saveToPos2History(yield* getPos2);
44 | });
45 |
46 | const peekChar = Effect.gen(function* () {
47 | const input = yield* getInput;
48 | const pos2 = yield* getPos2;
49 | return pos2 >= input.length ? 0 : input[pos2];
50 | });
51 |
52 | const newToken = (tokenType: TokenType, ch: string): Token => ({
53 | _tag: tokenType,
54 | literal: ch,
55 | });
56 |
57 | // ---
58 |
59 | const read = (pattern: RegExp, type: string) =>
60 | Effect.gen(function* () {
61 | const position = yield* getPos1;
62 | while (pattern.test(yield* getChar)) {
63 | yield* readChar;
64 | }
65 | const input = yield* getInput;
66 |
67 | const newPosition = yield* getPos1;
68 | const res = input.slice(position, newPosition);
69 | return res;
70 | });
71 |
72 | const readIdentifier = read(/[a-zA-Z_]/, "Identifier");
73 | const readNumber = read(/\d/, "Number");
74 | const eatWhiteSpace = Effect.gen(function* () {
75 | const whitespaceChars = [" ", "\t", "\n", "\r"];
76 | while (whitespaceChars.includes(yield* getChar)) {
77 | yield* readChar;
78 | }
79 | });
80 |
81 | const singleCharLookAhead = (
82 | firstChar: string,
83 | expectedSecond: string,
84 | matchedType: TokenType,
85 | defaultType: TokenType,
86 | ): Effect.Effect =>
87 | Effect.gen(function* () {
88 | const ch = yield* getChar;
89 | yield* readChar;
90 | const ch2 = yield* getChar;
91 | if (ch2 === expectedSecond) {
92 | yield* readChar;
93 | return newToken(matchedType, `${ch}${ch2}`);
94 | }
95 | return newToken(defaultType, firstChar);
96 | });
97 |
98 | const newTokenAndReadChar =
99 | (tokenType: TokenType) =>
100 | (ch: string): Effect.Effect =>
101 | readChar.pipe(
102 | Effect.flatMap(() => Effect.succeed(newToken(tokenType, ch))),
103 | );
104 |
105 | const readString = Effect.gen(function* () {
106 | const position = yield* getPos1;
107 | while (true) {
108 | yield* readChar;
109 | const ch = yield* getChar;
110 | if (ch === '"' || ch === "\0") {
111 | break;
112 | }
113 | }
114 | const input = yield* getInput;
115 | return input.slice(position + 1, yield* getPos1);
116 | });
117 |
118 | const getStory = Effect.gen(function* () {
119 | return {
120 | input: yield* getInput,
121 | tokens: yield* getTokens,
122 | pos1History: yield* getPos1History,
123 | pos2History: yield* getPos2History,
124 | };
125 | });
126 |
127 | const nextToken: Effect.Effect = Effect.gen(
128 | function* () {
129 | yield* eatWhiteSpace;
130 | const ch = yield* getChar;
131 |
132 | const decodedCh = decodeUnknownEither(lexerSymbolSchema)(ch);
133 |
134 | const token = yield* Either.match(decodedCh, {
135 | onLeft: (left) =>
136 | Effect.gen(function* () {
137 | if (/[a-zA-Z_]/.test(ch)) {
138 | const tokenLiteral = yield* readIdentifier;
139 | return newToken(lookupIndent(tokenLiteral), tokenLiteral);
140 | }
141 | if (/\d/.test(ch)) {
142 | return newToken(TokenType.INT, yield* readNumber);
143 | }
144 | return yield* newTokenAndReadChar(TokenType.ILLEGAL)("");
145 | }),
146 | onRight: (right) =>
147 | Match.value(right).pipe(
148 | Match.when(TokenType.ASSIGN, () =>
149 | singleCharLookAhead(
150 | TokenType.ASSIGN,
151 | TokenType.ASSIGN,
152 | TokenType.EQ,
153 | TokenType.ASSIGN,
154 | ),
155 | ),
156 | Match.when(TokenType.BANG, () =>
157 | singleCharLookAhead(
158 | TokenType.BANG,
159 | TokenType.ASSIGN,
160 | TokenType.NOT_EQ,
161 | TokenType.BANG,
162 | ),
163 | ),
164 | Match.when(TokenType.ASTERISK, () =>
165 | singleCharLookAhead(
166 | TokenType.ASTERISK,
167 | TokenType.ASTERISK,
168 | TokenType.EXPONENT,
169 | TokenType.ASTERISK,
170 | ),
171 | ),
172 | Match.when(TokenType.MINUS, newTokenAndReadChar(TokenType.MINUS)),
173 | Match.when(
174 | TokenType.EXPONENT,
175 | newTokenAndReadChar(TokenType.EXPONENT),
176 | ),
177 | Match.when(TokenType.SLASH, newTokenAndReadChar(TokenType.SLASH)),
178 | Match.when(TokenType.LT, newTokenAndReadChar(TokenType.LT)),
179 | Match.when(TokenType.GT, newTokenAndReadChar(TokenType.GT)),
180 | Match.when(
181 | TokenType.SEMICOLON,
182 | newTokenAndReadChar(TokenType.SEMICOLON),
183 | ),
184 | Match.when(
185 | TokenType.LPAREN,
186 | newTokenAndReadChar(TokenType.LPAREN),
187 | ),
188 | Match.when(
189 | TokenType.RPAREN,
190 | newTokenAndReadChar(TokenType.RPAREN),
191 | ),
192 | Match.when(
193 | TokenType.LBRACE,
194 | newTokenAndReadChar(TokenType.LBRACE),
195 | ),
196 | Match.when(
197 | TokenType.RBRACE,
198 | newTokenAndReadChar(TokenType.RBRACE),
199 | ),
200 | Match.when(
201 | TokenType.LBRACKET,
202 | newTokenAndReadChar(TokenType.LBRACKET),
203 | ),
204 | Match.when(
205 | TokenType.RBRACKET,
206 | newTokenAndReadChar(TokenType.RBRACKET),
207 | ),
208 | Match.when(TokenType.COMMA, newTokenAndReadChar(TokenType.COMMA)),
209 | Match.when(TokenType.PLUS, newTokenAndReadChar(TokenType.PLUS)),
210 | Match.when(TokenType.EOF, newTokenAndReadChar(TokenType.EOF)),
211 | Match.when(TokenType.QUOTE, function* () {
212 | return yield* newTokenAndReadChar(TokenType.STRING)(
213 | yield* readString,
214 | );
215 | }),
216 | Match.exhaustive,
217 | ),
218 | });
219 | yield* saveToken(token);
220 | return token;
221 | },
222 | );
223 |
224 | // ---
225 |
226 | return {
227 | init,
228 | readChar,
229 | peekChar,
230 | newToken,
231 | readIdentifier,
232 | readNumber,
233 | eatWhiteSpace,
234 | singleCharLookAhead,
235 | newTokenAndReadChar,
236 | getStory,
237 | nextToken,
238 | };
239 | }),
240 | dependencies: [LexerStateService.Default],
241 | }) {}
242 |
--------------------------------------------------------------------------------
/src/services/lexer/state.ts:
--------------------------------------------------------------------------------
1 | import { Context, Effect, Layer, Ref } from 'effect'
2 | import type { Token } from 'src/schemas/token/unions/all'
3 |
4 | export type LexerStateAll = Effect.Effect.Success
5 |
6 | export class LexerState {
7 | incPos1: Effect.Effect
8 | decPos1: Effect.Effect
9 | getPos1: Effect.Effect
10 | incPos2: Effect.Effect
11 | getInput: Effect.Effect
12 | decPos2: Effect.Effect
13 | getPos2: Effect.Effect
14 | getChar: Effect.Effect
15 | setChar: (newChar: string) => Effect.Effect
16 | setPos1: (newPos: number) => Effect.Effect
17 | setPos2: (newPos2: number) => Effect.Effect
18 | setInput: (newInput: string) => Effect.Effect
19 | getAll: Effect.Effect<{
20 | input: string
21 | pos1: number
22 | pos2: number
23 | char: string
24 | }>
25 | saveToken: (token: Token) => Effect.Effect
26 | getTokens: Effect.Effect
27 | getPos1History: Effect.Effect
28 | saveToPos1History: (pos1: number) => Effect.Effect
29 | getPos2History: Effect.Effect
30 | saveToPos2History: (pos2: number) => Effect.Effect
31 |
32 | constructor(
33 | // execution
34 | private input: Ref.Ref,
35 | private pos1: Ref.Ref,
36 | private pos2: Ref.Ref,
37 | private char: Ref.Ref,
38 | // interpretation
39 | private tokens: Ref.Ref,
40 | private pos1History: Ref.Ref,
41 | private pos2History: Ref.Ref,
42 | ) {
43 | this.getInput = Ref.get(this.input)
44 | this.getChar = Ref.get(this.char)
45 | this.incPos1 = Ref.update(this.pos1, (n) => n + 1)
46 | this.decPos1 = Ref.update(this.pos1, (n) => n - 1)
47 | this.getPos1 = Ref.get(this.pos1)
48 | this.incPos2 = Ref.update(this.pos2, (n) => n + 1)
49 | this.decPos2 = Ref.update(this.pos2, (n) => n - 1)
50 | this.getPos2 = Ref.get(this.pos2)
51 | this.setChar = (newChar: string) => Ref.update(this.char, () => newChar)
52 | this.setPos1 = (newPos1: number) => Ref.update(this.pos1, () => newPos1)
53 | this.setPos2 = (newPos2: number) => Ref.update(this.pos2, () => newPos2)
54 | this.setInput = (newInput: string) =>
55 | Effect.gen(function* () {
56 | yield* Ref.set(input, newInput)
57 | yield* Ref.set(pos1, 0)
58 | yield* Ref.set(pos2, 1)
59 | yield* Ref.set(char, newInput[0])
60 | })
61 | this.getAll = Effect.gen(function* () {
62 | return {
63 | input: yield* Ref.get(input),
64 | pos1: yield* Ref.get(pos1),
65 | pos2: yield* Ref.get(pos2),
66 | char: yield* Ref.get(char),
67 | }
68 | })
69 | // Interpretability. TODO: From a perfomance persepective, this should be separate.
70 | // FROM a performace perspective the only true zero-cost intepretability addition
71 | // that is viable would be a clever wrapper thing. (apart from language level ability to fully turn off code.)
72 | this.getTokens = Ref.get(this.tokens)
73 | this.saveToken = (token: Token) =>
74 | Ref.update(this.tokens, (tokens) => [...tokens, token])
75 | this.getPos1History = Ref.get(this.pos1History)
76 | this.getPos2History = Ref.get(this.pos2History)
77 | this.saveToPos1History = (pos1: number) =>
78 | Effect.gen(function* () {
79 | const tks = yield* Ref.get(tokens)
80 | yield* Ref.update(pos1History, (pos1History) => {
81 | const tokensLength = tks.length
82 | if (!Array.isArray(pos1History[tokensLength])) {
83 | pos1History.push([pos1])
84 | } else {
85 | pos1History[tokensLength].push(pos1)
86 | }
87 | return pos1History
88 | })
89 | })
90 | this.saveToPos2History = (pos2: number) =>
91 | Effect.gen(function* () {
92 | const tks = yield* Ref.get(tokens)
93 | yield* Ref.update(pos2History, (pos2History) => {
94 | const tokensLength = tks.length
95 | if (!Array.isArray(pos2History[tokensLength])) {
96 | pos2History.push([pos2])
97 | } else {
98 | pos2History[tokensLength].push(pos2)
99 | }
100 | return pos2History
101 | })
102 | })
103 |
104 | // Interpretability
105 | }
106 |
107 | static make = (input: Ref.Ref) =>
108 | Effect.gen(function* () {
109 | const pos1 = yield* Ref.make(0)
110 | const pos2 = yield* Ref.make(1)
111 | const char = yield* Ref.make(
112 | yield* Ref.get(input).pipe(Effect.map((str) => str[0])),
113 | )
114 | const tokens = yield* Ref.make([])
115 | const pos1History = yield* Ref.make([])
116 | const pos2History = yield* Ref.make([])
117 |
118 | return new LexerState(
119 | input,
120 | pos1,
121 | pos2,
122 | char,
123 | tokens,
124 | pos1History,
125 | pos2History,
126 | )
127 | })
128 | }
129 |
130 | export class LexerStateService extends Context.Tag('LexerStateService')<
131 | LexerStateService,
132 | LexerState
133 | >() {
134 | static readonly make = (input: string) =>
135 | Layer.effect(
136 | this,
137 | Effect.andThen(Ref.make(input), (inputRef) => LexerState.make(inputRef)),
138 | )
139 | static readonly Default = Layer.effect(
140 | this,
141 | Effect.andThen(Ref.make(''), (inputRef) => LexerState.make(inputRef)),
142 | )
143 | }
144 |
--------------------------------------------------------------------------------
/src/services/math/index.ts:
--------------------------------------------------------------------------------
1 | export const secSquared = (x: number) => 1 / Math.cos(x) ** 2;
2 |
3 | // expression: PrefixExp.make({
4 | // token: {
5 | // _tag: "-",
6 | // literal: "-",
7 | // },
8 | // operator: "-",
9 | // right: CallExp.make({
10 | // token: {
11 | // _tag: "(",
12 | // literal: "(",
13 | // },
14 | // fn: IdentExp.make({
15 | // token: {
16 | // _tag: "IDENT",
17 | // literal: "sin",
18 | // },
19 | // value: "sin",
20 | // }),
21 | // args: [x],
22 | // }),
23 | // });
24 |
--------------------------------------------------------------------------------
/src/services/object/builtins.ts:
--------------------------------------------------------------------------------
1 | import { ArrayObj } from "@/schemas/objs/array";
2 | import { BuiltInObj } from "@/schemas/objs/built-in";
3 | import { ErrorObj } from "@/schemas/objs/error";
4 | import { FunctionObj } from "@/schemas/objs/function";
5 | import { IntegerObj } from "@/schemas/objs/int";
6 | import { StringObj } from "@/schemas/objs/string";
7 | import type { Obj } from "@/schemas/objs/union";
8 | import { Effect, Match, Schema } from "effect";
9 | import { DiffExp } from "src/schemas/nodes/exps/diff";
10 | import { IdentExp } from "src/schemas/nodes/exps/ident";
11 | import { BlockStmt } from "src/schemas/nodes/stmts/block";
12 | import { ExpStmt } from "src/schemas/nodes/stmts/exp";
13 | import type { Environment } from "./environment";
14 |
15 | // Where does this fit.
16 | // it would be here where
17 |
18 | const makeUnaryMathFunction =
19 | (mathFn: (x: number) => number) =>
20 | (...args: Obj[]) =>
21 | Schema.decodeUnknown(Schema.Tuple(IntegerObj))(args).pipe(
22 | Effect.flatMap(([{ value }]) =>
23 | Effect.succeed(IntegerObj.make({ value: mathFn(value) })),
24 | ),
25 | );
26 |
27 | const sin = makeUnaryMathFunction(Math.sin);
28 | const cos = makeUnaryMathFunction(Math.cos);
29 | const tan = makeUnaryMathFunction(Math.tan);
30 | const ln = makeUnaryMathFunction(Math.log);
31 | const exp = makeUnaryMathFunction(Math.exp);
32 |
33 | const makeConstantFunction =
34 | (value: number) =>
35 | (...args: Obj[]) =>
36 | Effect.succeed(IntegerObj.make({ value })); // to get around not having decimals.
37 |
38 | const pi = makeConstantFunction(Math.PI);
39 | const e = makeConstantFunction(Math.E);
40 |
41 | const diff = (...args: Obj[]) =>
42 | Schema.decodeUnknown(
43 | Schema.Tuple(
44 | Schema.Struct({
45 | params: Schema.Tuple(IdentExp), // of a single ident.
46 | body: BlockStmt,
47 | env: Schema.Unknown, // setting this to env blows everything up
48 | }),
49 | ),
50 | )(args).pipe(
51 | Effect.flatMap(
52 | ([
53 | {
54 | params,
55 | body: { token, statements },
56 | env,
57 | },
58 | ]) =>
59 | Effect.gen(function* () {
60 | // suppose instead I
61 |
62 | // ASSUME THERE IS ONLY A SINGLE STATEMENT - FOR NOW
63 | const expStmt = (yield* Schema.decodeUnknown(Schema.Tuple(ExpStmt))(
64 | statements,
65 | ))[0];
66 |
67 | const newBody = BlockStmt.make({
68 | token,
69 | statements: [
70 | ExpStmt.make({
71 | ...expStmt,
72 | expression: DiffExp.make({
73 | token: {
74 | _tag: "diff",
75 | literal: "diff",
76 | },
77 | exp: expStmt.expression,
78 | params,
79 | }),
80 | }),
81 | ],
82 | });
83 |
84 | return FunctionObj.make({
85 | params,
86 | body: newBody,
87 | env: env as Environment,
88 | });
89 | }),
90 | ),
91 | );
92 |
93 | export const builtins = {
94 | len: BuiltInObj.make({ fn: "len" }),
95 | diff: BuiltInObj.make({ fn: "diff" }),
96 | sin: BuiltInObj.make({ fn: "sin" }),
97 | cos: BuiltInObj.make({ fn: "cos" }),
98 | tan: BuiltInObj.make({ fn: "tan" }),
99 | e: BuiltInObj.make({ fn: "e" }),
100 | ln: BuiltInObj.make({ fn: "ln" }),
101 | pi: BuiltInObj.make({ fn: "pi" }),
102 | exp: BuiltInObj.make({ fn: "exp" }),
103 | } as const;
104 |
105 | export const builtInFnMap = {
106 | len: (...args: Obj[]) =>
107 | Schema.decodeUnknown(Schema.Tuple(Schema.Union(StringObj, ArrayObj)))(
108 | args,
109 | ).pipe(
110 | Effect.flatMap(([firstArg]) =>
111 | Match.value(firstArg).pipe(
112 | Match.tag("StringObj", (strObj) =>
113 | Effect.succeed(IntegerObj.make({ value: strObj.value.length })),
114 | ),
115 | Match.tag("ArrayObj", ({ elements }) =>
116 | Effect.succeed(IntegerObj.make({ value: elements.length })),
117 | ),
118 | Match.exhaustive,
119 | ),
120 | ),
121 | ),
122 | diff,
123 | sin,
124 | cos,
125 | tan,
126 | ln,
127 | e,
128 | exp,
129 | pi,
130 | } as const;
131 |
132 | const builtinKeys = Object.keys(builtins) as (keyof typeof builtins)[]; // hack
133 |
134 | export const builtinsKeySchema = Schema.Literal(...builtinKeys);
135 |
--------------------------------------------------------------------------------
/src/services/object/environment.ts:
--------------------------------------------------------------------------------
1 | import { IdentExp } from "@/schemas/nodes/exps/ident";
2 | import { Obj, type ObjEncoded } from "@/schemas/objs/union";
3 | import { Effect, Schema } from "effect";
4 | import type { ParseError } from "effect/ParseResult";
5 | import { objInspect } from ".";
6 | import { builtins, builtinsKeySchema } from "./builtins";
7 |
8 | export const get =
9 | (env: Environment) =>
10 | (key: string): Effect.Effect =>
11 | Effect.gen(function* () {
12 | return (
13 | env.store[key] ??
14 | (env.outer
15 | ? yield* get(env.outer)(key)
16 | : builtins[yield* Schema.decodeUnknown(builtinsKeySchema)(key)])
17 | );
18 | });
19 |
20 | export const set = (env: Environment) => (key: string, value: Obj) => {
21 | env.store[key] = value;
22 | return value;
23 | };
24 |
25 | export interface Environment {
26 | readonly outer: Environment | undefined;
27 | readonly store: Record;
28 | readonly idents: readonly IdentExp[];
29 | }
30 |
31 | export interface EnvironmentEncoded {
32 | readonly outer: EnvironmentEncoded | undefined;
33 | readonly store: Record;
34 | readonly idents: readonly IdentExp[];
35 | }
36 |
37 | export const Environment = Schema.Struct({
38 | store: Schema.Record({
39 | key: Schema.String,
40 | value: Schema.suspend((): Schema.Schema => Obj),
41 | }),
42 | idents: Schema.Array(IdentExp),
43 | outer: Schema.Union(
44 | Schema.suspend(
45 | (): Schema.Schema => Environment,
46 | ),
47 | Schema.Undefined,
48 | ),
49 | });
50 |
51 | export const createEnvironment = (outer?: Environment | undefined) =>
52 | Environment.make({
53 | store: {},
54 | outer,
55 | idents: [],
56 | });
57 |
58 | export const printStore = (env: Environment): string => {
59 | let output = "Environment Store:\n";
60 | for (const key in env.store) {
61 | if (key in env.store) {
62 | const value = env.store[key];
63 | output += `${key}: ${objInspect(value)}\n`;
64 | }
65 | }
66 |
67 | if (env.outer) {
68 | output += "\nOuter Environment:\n";
69 | output += printStore(env.outer);
70 | }
71 |
72 | return output;
73 | };
74 |
--------------------------------------------------------------------------------
/src/services/object/index.ts:
--------------------------------------------------------------------------------
1 | import { nodeString } from "@/schemas/nodes/union";
2 | import type { Obj } from "@/schemas/objs/union";
3 | import { Data, Match } from "effect";
4 |
5 | const { $is, $match } = Data.taggedEnum();
6 |
7 | export const isIntegerObj = $is("IntegerObj");
8 | export const isBooleanObj = $is("BooleanObj");
9 | export const isNullObj = $is("NullObj");
10 | export const isReturnObj = $is("ReturnObj");
11 | export const isErrorObj = $is("ErrorObj");
12 | export const isFunctionObj = $is("FunctionObj");
13 | export const isStringObj = $is("StringObj");
14 | export const isBuiltInObj = $is("BuiltInObj");
15 | export const isIdentObj = $is("IdentObj");
16 | export const isInfixObj = $is("InfixObj");
17 |
18 | export const objMatch = $match;
19 |
20 | // always safer to handle type by type instead of orElse, because if value changes type
21 | // compiler won't scream at you to remind you to handle that specific case.
22 | export const objInspect = (obj: Obj): string =>
23 | Match.value(obj).pipe(
24 | Match.tag("InfixObj", () => "infix obj"),
25 | Match.tag("IdentObj", ({ identExp: { value } }) => value),
26 | Match.tag("BuiltInObj", () => "builtin function"),
27 | Match.tag(
28 | "FunctionObj",
29 | ({ params, body }) => `
30 | fn (${params.map((p) => nodeString(p)).join(", ")}) {
31 | ${nodeString(body)}
32 | }
33 | `,
34 | ),
35 | Match.tag("ErrorObj", (errorObj) => `ERROR: ${errorObj.message}`),
36 | Match.tag("ReturnObj", ({ value }) => objInspect(value)),
37 | Match.tag("NullObj", () => "null"),
38 | Match.tag("BooleanObj", ({ value }) => `${value}`),
39 | Match.tag("IntegerObj", ({ value }) => `${value}`),
40 | Match.tag("StringObj", ({ value }) => value),
41 | Match.tag(
42 | "ArrayObj",
43 | ({ elements }) => `[${elements.map(objInspect).join(", ")}]`,
44 | ),
45 | Match.exhaustive,
46 | );
47 |
--------------------------------------------------------------------------------
/src/services/parser/constant-folding.ts:
--------------------------------------------------------------------------------
1 | import { Effect, Either, Match, Schema } from "effect";
2 | import { decodeUnknownEither } from "effect/ParseResult";
3 | import { InfixExp } from "../../schemas/nodes/exps/infix";
4 | import { IntExp } from "../../schemas/nodes/exps/int";
5 | import { StrExp } from "../../schemas/nodes/exps/str";
6 | import { type Exp, nativeToExp } from "../../schemas/nodes/exps/union";
7 | import { BlockStmt } from "../../schemas/nodes/stmts/block";
8 | import { ExpStmt } from "../../schemas/nodes/stmts/exp";
9 | import { LetStmt } from "../../schemas/nodes/stmts/let";
10 | import { ReturnStmt } from "../../schemas/nodes/stmts/return";
11 | import type { Stmt } from "../../schemas/nodes/stmts/union";
12 | import { TokenType } from "../../schemas/token-types/union";
13 | import {
14 | OPERATOR_TO_FUNCTION_MAP,
15 | STRING_OPERATOR_TO_FUNCTION_MAP,
16 | } from "../evaluator/constants";
17 |
18 | export const constantFoldingOverStmt = (
19 | stmt: Stmt,
20 | ): Effect.Effect =>
21 | Match.value(stmt).pipe(
22 | Match.tag("BlockStmt", ({ token, statements }) =>
23 | Effect.gen(function* () {
24 | return BlockStmt.make({
25 | token,
26 | statements: yield* Effect.all(
27 | statements.map(constantFoldingOverStmt),
28 | ),
29 | });
30 | }),
31 | ),
32 | Match.tag("ExpStmt", ({ token, expression }) =>
33 | Effect.gen(function* () {
34 | return ExpStmt.make({
35 | token,
36 | expression: yield* constantFoldingOverExp(expression),
37 | });
38 | }),
39 | ),
40 | Match.tag("ReturnStmt", ({ token, value }) =>
41 | Effect.gen(function* () {
42 | return ReturnStmt.make({
43 | token,
44 | value: yield* constantFoldingOverExp(value),
45 | });
46 | }),
47 | ),
48 | Match.tag("LetStmt", ({ token, name, value }) =>
49 | Effect.gen(function* () {
50 | return LetStmt.make({
51 | token,
52 | name,
53 | value: yield* constantFoldingOverExp(value),
54 | });
55 | }),
56 | ),
57 | Match.exhaustive,
58 | );
59 |
60 | const constantFoldingOverExp = (exp: Exp): Effect.Effect =>
61 | Match.value(exp).pipe(
62 | Match.tag("InfixExp", ({ token, left, operator, right }) =>
63 | Effect.gen(function* () {
64 | const foldedLeft = yield* constantFoldingOverExp(left);
65 | const foldedRight = yield* constantFoldingOverExp(right);
66 |
67 | const intResult = Schema.decodeUnknownEither(
68 | Schema.Struct({ left: IntExp, right: IntExp }),
69 | )({
70 | left: foldedLeft,
71 | right: foldedRight,
72 | });
73 |
74 | if (Either.isRight(intResult)) {
75 | return nativeToExp(
76 | OPERATOR_TO_FUNCTION_MAP[operator](
77 | intResult.right.left.value,
78 | intResult.right.right.value,
79 | ),
80 | );
81 | }
82 |
83 | const strResult = decodeUnknownEither(
84 | Schema.Struct({
85 | left: StrExp,
86 | operator: Schema.Literal(TokenType.PLUS),
87 | right: StrExp,
88 | }),
89 | )({ left: foldedLeft, operator, right: foldedRight });
90 |
91 | if (Either.isRight(strResult)) {
92 | return nativeToExp(
93 | STRING_OPERATOR_TO_FUNCTION_MAP[strResult.right.operator](
94 | strResult.right.left.value,
95 | strResult.right.right.value,
96 | ),
97 | );
98 | }
99 | return InfixExp.make({
100 | token,
101 | left: foldedLeft,
102 | operator,
103 | right: foldedRight,
104 | });
105 | }),
106 | ),
107 | Match.orElse(() => Effect.succeed(exp)),
108 | );
109 |
--------------------------------------------------------------------------------
/src/services/parser/index.ts:
--------------------------------------------------------------------------------
1 | import { IndexExp } from "@/schemas/nodes/exps";
2 | import { ArrayExp } from "@/schemas/nodes/exps/array";
3 | import { Effect, Match, Schema } from "effect";
4 | import type { ParseError } from "effect/ParseResult";
5 | import { BoolExp } from "src/schemas/nodes/exps/boolean";
6 | import { CallExp } from "src/schemas/nodes/exps/call";
7 | import { FuncExp } from "src/schemas/nodes/exps/function";
8 | import { IdentExp } from "src/schemas/nodes/exps/ident";
9 | import { IfExp } from "src/schemas/nodes/exps/if";
10 | import { InfixExp } from "src/schemas/nodes/exps/infix";
11 | import { IntExp } from "src/schemas/nodes/exps/int";
12 | import { PrefixExp } from "src/schemas/nodes/exps/prefix";
13 | import { StrExp } from "src/schemas/nodes/exps/str";
14 | import type { Exp } from "src/schemas/nodes/exps/union";
15 | import { Program } from "src/schemas/nodes/program";
16 | import { BlockStmt } from "src/schemas/nodes/stmts/block";
17 | import { ExpStmt } from "src/schemas/nodes/stmts/exp";
18 | import { LetStmt } from "src/schemas/nodes/stmts/let";
19 | import { ReturnStmt } from "src/schemas/nodes/stmts/return";
20 | import type { Stmt } from "src/schemas/nodes/stmts/union";
21 | import { TokenType } from "src/schemas/token-types/union";
22 | import type { Token } from "src/schemas/token/unions/all";
23 | import type { KennethParseError } from "../../errors/kenneth/parse";
24 | import { infixOperatorSchema } from "../../schemas/infix-operator";
25 | import type { FnToken } from "../../schemas/token/function-literal";
26 | import { type IdentToken, identTokenSchema } from "../../schemas/token/ident";
27 | import type { IfToken } from "../../schemas/token/if";
28 | import type { IntToken } from "../../schemas/token/int";
29 | import { letTokenSchema } from "../../schemas/token/let";
30 | import { returnTokenSchema } from "../../schemas/token/return";
31 | import type { StringToken } from "../../schemas/token/string";
32 | import type { BoolToken } from "../../schemas/token/unions/boolean";
33 | import {
34 | type InfixParseFnToken,
35 | infixParseFnTokenSchema,
36 | } from "../../schemas/token/unions/parse-fn/infix";
37 | import {
38 | type PrefixParseFnToken,
39 | prefixParseFnTokenSchema,
40 | } from "../../schemas/token/unions/parse-fn/prefix";
41 | import type { PrefixToken } from "../../schemas/token/unions/prefix";
42 | import { Lexer } from "../lexer";
43 | import { constantFoldingOverStmt } from "./constant-folding";
44 | import {
45 | LOWEST,
46 | PREFIX,
47 | type Precedence,
48 | tokenTypeToPrecedenceMap,
49 | } from "./precedence";
50 | import { ParserStateService } from "./state";
51 |
52 | export class Parser extends Effect.Service()("Parser", {
53 | effect: Effect.gen(function* () {
54 | const {
55 | getCurToken,
56 | getPeekToken,
57 | setCurTokenAndSave,
58 | setPeekTokenAndSave,
59 | getCurTokenHistory,
60 | getPeekTokenHistory,
61 | } = yield* ParserStateService;
62 | const lexer = yield* Lexer;
63 |
64 | const nextToken = Effect.gen(function* () {
65 | yield* setCurTokenAndSave(yield* getPeekToken);
66 | yield* setPeekTokenAndSave(yield* lexer.nextToken);
67 | }).pipe(Effect.withSpan("parser.nextToken"));
68 |
69 | const init = (input: string) =>
70 | Effect.gen(function* () {
71 | yield* lexer.init(input);
72 | yield* nextToken;
73 | yield* nextToken;
74 | }).pipe(Effect.withSpan("parser.init"));
75 |
76 | const tokenIs = (token: Token, tokenType: TokenType) =>
77 | token._tag === tokenType;
78 |
79 | const peekTokenIs = (tokenType: TokenType) =>
80 | getPeekToken
81 | .pipe(
82 | Effect.flatMap((peekToken) =>
83 | Effect.succeed(tokenIs(peekToken, tokenType)),
84 | ),
85 | )
86 | .pipe(Effect.withSpan("parser.peekTokenIs"));
87 |
88 | const curTokenIs = (tokenType: TokenType) =>
89 | getCurToken
90 | .pipe(
91 | Effect.flatMap((curToken) =>
92 | Effect.succeed(tokenIs(curToken, tokenType)),
93 | ),
94 | )
95 | .pipe(Effect.withSpan("parser.curTokenIs"));
96 |
97 | const parseIdentifier = (curToken: IdentToken) =>
98 | Schema.decodeUnknown(identTokenSchema)(curToken)
99 | .pipe(
100 | Effect.flatMap((identToken) =>
101 | Effect.succeed(
102 | IdentExp.make({ token: identToken, value: identToken.literal }),
103 | ),
104 | ),
105 | )
106 | .pipe(Effect.withSpan("parser.parseIdentifier"));
107 |
108 | const parseIntegerLiteral = (curToken: IntToken) =>
109 | Effect.succeed(
110 | IntExp.make({ token: curToken, value: Number(curToken.literal) }),
111 | ).pipe(Effect.withSpan("parser.parseIntegerLiteral"));
112 |
113 | const parseBooleanLiteral = (curToken: BoolToken) =>
114 | curTokenIs(TokenType.TRUE).pipe(
115 | Effect.flatMap((value) =>
116 | Effect.succeed(
117 | BoolExp.make({
118 | token: curToken,
119 | value,
120 | }),
121 | ),
122 | ),
123 | );
124 |
125 | const parseGroupedExpression = (curToken: Token) =>
126 | Effect.gen(function* () {
127 | yield* nextToken;
128 |
129 | const expression = yield* parseExpression(LOWEST);
130 |
131 | // framework handles errors
132 | yield* expectPeek(TokenType.RPAREN);
133 |
134 | return expression;
135 | });
136 |
137 | const parseBlockStatement = Effect.gen(function* () {
138 | const curToken = yield* getCurToken;
139 |
140 | yield* nextToken;
141 |
142 | const stmts: Stmt[] = [];
143 |
144 | while (
145 | !(yield* curTokenIs(TokenType.RBRACE)) &&
146 | !(yield* curTokenIs(TokenType.EOF))
147 | ) {
148 | stmts.push(yield* parseStatement(yield* getCurToken));
149 | yield* nextToken;
150 | }
151 |
152 | return BlockStmt.make({ token: curToken, statements: stmts });
153 | });
154 |
155 | const parseIfExpression = (curToken: IfToken) =>
156 | Effect.gen(function* () {
157 | yield* expectPeek(TokenType.LPAREN);
158 |
159 | yield* nextToken;
160 |
161 | const condition = yield* parseExpression(LOWEST);
162 | yield* expectPeek(TokenType.RPAREN);
163 | yield* expectPeek(TokenType.LBRACE);
164 |
165 | const consequence = yield* parseBlockStatement;
166 |
167 | let alternative: BlockStmt | undefined = undefined;
168 |
169 | if (yield* peekTokenIs(TokenType.ELSE)) {
170 | yield* nextToken;
171 |
172 | yield* expectPeek(TokenType.LBRACE);
173 |
174 | alternative = yield* parseBlockStatement;
175 | }
176 | return IfExp.make({
177 | token: curToken,
178 | condition,
179 | consequence,
180 | alternative,
181 | });
182 | });
183 |
184 | const parseFunctionParameters = Effect.gen(function* () {
185 | const identifiers: IdentExp[] = [];
186 | if (yield* peekTokenIs(TokenType.RPAREN)) {
187 | yield* nextToken;
188 | return identifiers;
189 | }
190 |
191 | yield* nextToken;
192 | const identToken = yield* Schema.decodeUnknown(identTokenSchema)(
193 | yield* getCurToken,
194 | );
195 | const identExp = IdentExp.make({
196 | token: identToken,
197 | value: identToken.literal,
198 | });
199 | identifiers.push(identExp);
200 |
201 | while (yield* peekTokenIs(TokenType.COMMA)) {
202 | yield* nextToken;
203 | yield* nextToken;
204 | const identToken = yield* Schema.decodeUnknown(identTokenSchema)(
205 | yield* getCurToken,
206 | );
207 | const identExp = IdentExp.make({
208 | token: identToken,
209 | value: identToken.literal,
210 | });
211 | identifiers.push(identExp);
212 | }
213 |
214 | yield* expectPeek(TokenType.RPAREN);
215 |
216 | return identifiers;
217 | });
218 |
219 | const parseFunctionLiteral = (curToken: FnToken) =>
220 | Effect.gen(function* () {
221 | yield* expectPeek(TokenType.LPAREN);
222 | const parameters = yield* parseFunctionParameters;
223 | yield* expectPeek(TokenType.LBRACE);
224 |
225 | return FuncExp.make({
226 | token: curToken,
227 | parameters,
228 | body: yield* parseBlockStatement,
229 | });
230 | });
231 |
232 | const parseStringLiteral = (curToken: StringToken) =>
233 | Effect.succeed(StrExp.make({ token: curToken, value: curToken.literal }));
234 |
235 | const parseExpressionList = (end: TokenType) =>
236 | Effect.gen(function* () {
237 | if (yield* peekTokenIs(end)) {
238 | yield* nextToken;
239 | return [];
240 | }
241 |
242 | yield* nextToken;
243 | const list = [yield* parseExpression(LOWEST)];
244 |
245 | while (yield* peekTokenIs(TokenType.COMMA)) {
246 | yield* nextToken;
247 | yield* nextToken;
248 | list.push(yield* parseExpression(LOWEST));
249 | }
250 |
251 | yield* expectPeek(end);
252 |
253 | return list;
254 | });
255 |
256 | const parseArrayExp = () =>
257 | Effect.all([getCurToken, parseExpressionList(TokenType.RBRACKET)]).pipe(
258 | Effect.flatMap(([token, elements]) =>
259 | Effect.succeed(ArrayExp.make({ token, elements })),
260 | ),
261 | );
262 |
263 | const parseIndexExp = (left: Exp) =>
264 | Effect.gen(function* () {
265 | const token = yield* getCurToken;
266 | yield* nextToken;
267 | const index = yield* parseExpression(LOWEST);
268 |
269 | yield* expectPeek(TokenType.RBRACKET);
270 |
271 | return IndexExp.make({ left, token, index });
272 | });
273 |
274 | // from the perpective of match _tags but this still I still need to hand make.
275 | // normalize with current naming conventions
276 | // I feel like I can go stricter with types but
277 | // I don't want to mess up the whole exp that will turn into something that you will be ok with false negatives
278 | const getPrefixParseFunction = (token: PrefixParseFnToken) =>
279 | Match.value(token)
280 | .pipe(
281 | Match.tag(TokenType.IDENT, parseIdentifier),
282 | Match.tag(TokenType.INT, parseIntegerLiteral),
283 | Match.tag(TokenType.IF, parseIfExpression),
284 | Match.tag(TokenType.FUNCTION, parseFunctionLiteral),
285 | Match.tag(TokenType.TRUE, parseBooleanLiteral),
286 | Match.tag(TokenType.FALSE, parseBooleanLiteral),
287 | Match.tag(TokenType.BANG, parsePrefixExpression),
288 | Match.tag(TokenType.MINUS, parsePrefixExpression),
289 | Match.tag(TokenType.STRING, parseStringLiteral),
290 | Match.tag(TokenType.LPAREN, parseGroupedExpression),
291 | Match.tag(TokenType.LBRACKET, parseArrayExp),
292 | Match.exhaustive,
293 | )
294 | .pipe(Effect.withSpan("parser.getPrefixParseFunction"));
295 |
296 | const parseExpression = (
297 | precendence: Precedence,
298 | ): Effect.Effect =>
299 | Effect.gen(function* () {
300 | const curToken = yield* getCurToken;
301 | const prefixParseFnToken = yield* Schema.decodeUnknown(
302 | prefixParseFnTokenSchema,
303 | )(curToken);
304 | const prefix = yield* getPrefixParseFunction(prefixParseFnToken);
305 | let leftExp = prefix;
306 |
307 | while (
308 | !(yield* peekTokenIs(TokenType.SEMICOLON)) &&
309 | precendence < (yield* peekPrecedence)
310 | ) {
311 | const peekToken = yield* getPeekToken;
312 |
313 | const infixParseFnToken = yield* Schema.decodeUnknown(
314 | infixParseFnTokenSchema,
315 | )(peekToken);
316 |
317 | const infix = yield* getInfixParseFunction(infixParseFnToken);
318 |
319 | yield* nextToken;
320 |
321 | leftExp = yield* infix(leftExp);
322 | }
323 |
324 | return leftExp;
325 | }).pipe(Effect.withSpan("parser.parseExpression"));
326 |
327 | // the correct way, would be for curToken to be provided as an argument, that way the types would FLOW.
328 | // in the current setup, I believe I am getting curToken *twice*.
329 | const parsePrefixExpression = (curToken: PrefixToken) =>
330 | Effect.gen(function* () {
331 | yield* nextToken;
332 |
333 | const prefixExpression = PrefixExp.make({
334 | token: curToken,
335 | operator: curToken.literal,
336 | right: yield* parseExpression(PREFIX),
337 | });
338 |
339 | return prefixExpression;
340 | }).pipe(Effect.withSpan("parser.parsePrefixExpression"));
341 |
342 | const expectPeek = (tokenType: TokenType) =>
343 | Effect.gen(function* () {
344 | const peekToken = yield* getPeekToken;
345 |
346 | yield* Schema.decodeUnknown(Schema.Literal(tokenType))(peekToken._tag);
347 |
348 | yield* nextToken;
349 | }).pipe(Effect.withSpan("parser.expectPeek"));
350 |
351 | const parseCallExpression = (fn: Exp) =>
352 | Effect.gen(function* () {
353 | return CallExp.make({
354 | token: yield* getCurToken,
355 | fn,
356 | args: yield* parseExpressionList(TokenType.RPAREN),
357 | });
358 | });
359 |
360 | const getInfixParseFunction = (token: InfixParseFnToken) =>
361 | Effect.succeed(
362 | Match.value(token).pipe(
363 | Match.tag(TokenType.PLUS, () => parseInfixExpressions),
364 | Match.tag(TokenType.MINUS, () => parseInfixExpressions),
365 | Match.tag(TokenType.SLASH, () => parseInfixExpressions),
366 | Match.tag(TokenType.ASTERISK, () => parseInfixExpressions),
367 | Match.tag(TokenType.EQ, () => parseInfixExpressions),
368 | Match.tag(TokenType.NOT_EQ, () => parseInfixExpressions),
369 | Match.tag(TokenType.LT, () => parseInfixExpressions),
370 | Match.tag(TokenType.GT, () => parseInfixExpressions),
371 | Match.tag(TokenType.EXPONENT, () => parseInfixExpressions),
372 | Match.tag(TokenType.LPAREN, () => parseCallExpression),
373 | Match.tag(TokenType.LBRACKET, () => parseIndexExp),
374 | Match.exhaustive,
375 | ),
376 | ).pipe(Effect.withSpan("parser.getInfixParseFunction"));
377 |
378 | const parseLetStatement = Effect.gen(function* () {
379 | const curToken = yield* getCurToken;
380 | const letToken = yield* Schema.decodeUnknown(letTokenSchema)(curToken);
381 | yield* nextToken;
382 |
383 | const identToken = yield* Schema.decodeUnknown(identTokenSchema)(
384 | yield* getCurToken,
385 | ); // grosss
386 |
387 | const identifier = IdentExp.make({
388 | token: identToken as IdentToken,
389 | value: identToken.literal,
390 | });
391 |
392 | yield* expectPeek(TokenType.ASSIGN);
393 |
394 | yield* nextToken;
395 |
396 | const value = yield* parseExpression(LOWEST);
397 |
398 | if (yield* peekTokenIs(TokenType.SEMICOLON)) {
399 | yield* nextToken;
400 | }
401 |
402 | return LetStmt.make({ name: identifier, token: letToken, value });
403 | }).pipe(Effect.withSpan("parser.parseLetStatement"));
404 |
405 | const parseReturnStatement = Effect.gen(function* () {
406 | const curToken = yield* getCurToken;
407 | const returnToken =
408 | yield* Schema.decodeUnknown(returnTokenSchema)(curToken);
409 | yield* nextToken;
410 |
411 | const returnValue = yield* parseExpression(LOWEST);
412 |
413 | if (yield* peekTokenIs(TokenType.SEMICOLON)) {
414 | yield* nextToken;
415 | }
416 |
417 | return ReturnStmt.make({ token: returnToken, value: returnValue });
418 | }).pipe(Effect.withSpan("parser.parseReturnStatement"));
419 |
420 | const parseExpressionStatement = Effect.gen(function* () {
421 | const curToken = yield* getCurToken;
422 | const expStmt = ExpStmt.make({
423 | token: curToken,
424 | expression: yield* parseExpression(LOWEST),
425 | });
426 |
427 | if (yield* peekTokenIs(TokenType.SEMICOLON)) {
428 | yield* nextToken;
429 | }
430 |
431 | return expStmt;
432 | }).pipe(Effect.withSpan("parser.parseExpressionStatement"));
433 |
434 | const parseStatement = (curToken: Token) =>
435 | Match.value(curToken)
436 | .pipe(
437 | Match.when({ _tag: TokenType.LET }, () => parseLetStatement),
438 | Match.when({ _tag: TokenType.RETURN }, () => parseReturnStatement),
439 | Match.orElse(() => parseExpressionStatement),
440 | )
441 | .pipe(Effect.withSpan("parser.parseStatement"));
442 |
443 | const parseProgram = Effect.gen(function* () {
444 | const statements: Stmt[] = [];
445 |
446 | let curToken = yield* getCurToken;
447 | while (curToken._tag !== TokenType.EOF) {
448 | const stmt = yield* parseStatement(curToken);
449 | statements.push(stmt);
450 | yield* nextToken;
451 | curToken = yield* getCurToken;
452 | }
453 | const program = Program.make({ token: curToken, statements });
454 | return program;
455 | }).pipe(Effect.withSpan("parser.parseProgram"));
456 |
457 | const parseProgramOptimized = Effect.gen(function* () {
458 | const program = yield* parseProgram;
459 | const statements = yield* Effect.all(
460 | program.statements.map(constantFoldingOverStmt),
461 | );
462 | return Program.make({ token: program.token, statements });
463 | }).pipe(Effect.withSpan("parser.parseProgramOptimized"));
464 |
465 | const getPrecedence = (token: Token) =>
466 | tokenTypeToPrecedenceMap.get(token._tag) ?? LOWEST;
467 |
468 | const curPrecedence = Effect.gen(function* () {
469 | const curToken = yield* getCurToken;
470 | return getPrecedence(curToken);
471 | });
472 |
473 | const peekPrecedence = Effect.gen(function* () {
474 | const peekToken = yield* getPeekToken;
475 | return getPrecedence(peekToken);
476 | });
477 |
478 | const parseInfixExpressions = (left: Exp) =>
479 | Effect.gen(function* () {
480 | const curToken = yield* getCurToken;
481 | const precedence = yield* curPrecedence;
482 | yield* nextToken;
483 |
484 | const right = yield* parseExpression(precedence);
485 |
486 | const operator = yield* Schema.decodeUnknown(infixOperatorSchema)(
487 | curToken.literal,
488 | );
489 |
490 | return InfixExp.make({ token: curToken, left, operator, right });
491 | });
492 |
493 | const getParserStory = Effect.gen(function* () {
494 | return {
495 | curTokenHistory: yield* getCurTokenHistory,
496 | peekTokenHistory: yield* getPeekTokenHistory,
497 | };
498 | });
499 |
500 | return {
501 | init,
502 | parseProgram,
503 | parseProgramOptimized,
504 | getLexerStory: lexer.getStory,
505 | getParserStory,
506 | };
507 | }),
508 | dependencies: [Lexer.Default, ParserStateService.Default],
509 | }) {}
510 |
--------------------------------------------------------------------------------
/src/services/parser/parsing-functions.ts:
--------------------------------------------------------------------------------
1 | import type { Exp } from 'src/schemas/nodes/exps/union'
2 |
3 | export type PrefixParseFunction = () => Exp
4 | export type InfixParseFunction = (exp: Exp) => Exp
5 |
--------------------------------------------------------------------------------
/src/services/parser/precedence.ts:
--------------------------------------------------------------------------------
1 | import { Brand } from "effect";
2 | import { TokenType } from "../../schemas/token-types/union";
3 |
4 | export const LOWEST = Brand.nominal()(0);
5 | export const EQUALS = Brand.nominal()(1);
6 | export const LESSGREATER = Brand.nominal()(2);
7 | export const SUM = Brand.nominal()(3);
8 | export const PRODUCT = Brand.nominal()(4);
9 | export const EXPONENT = Brand.nominal()(5);
10 | export const PREFIX = Brand.nominal()(6);
11 | export const CALL = Brand.nominal()(7);
12 | export const INDEX = Brand.nominal()(8);
13 |
14 | export type LOWEST = 0 & Brand.Brand<"LOWEST">;
15 | export type EQUALS = 1 & Brand.Brand<"EQUALS">;
16 | export type LESSGREATER = 2 & Brand.Brand<"LESSGREATER">;
17 | export type SUM = 3 & Brand.Brand<"SUM">;
18 | export type PRODUCT = 4 & Brand.Brand<"PRODUCT">;
19 | export type EXPONENT = 5 & Brand.Brand<"EXPONENT">;
20 | export type PREFIX = 6 & Brand.Brand<"PREFIX">;
21 | export type CALL = 7 & Brand.Brand<"CALL">;
22 | export type INDEX = 8 & Brand.Brand<"INDEX">;
23 |
24 | export type Precedence =
25 | | LOWEST
26 | | EQUALS
27 | | LESSGREATER
28 | | SUM
29 | | PRODUCT
30 | | EXPONENT
31 | | PREFIX
32 | | CALL
33 | | INDEX;
34 |
35 | export const tokenTypeToPrecedenceMap: ReadonlyMap =
36 | new Map([
37 | [TokenType.EQ, EQUALS],
38 | [TokenType.NOT_EQ, EQUALS],
39 | [TokenType.LT, LESSGREATER],
40 | [TokenType.GT, LESSGREATER],
41 | [TokenType.PLUS, SUM],
42 | [TokenType.MINUS, SUM],
43 | [TokenType.ASTERISK, PRODUCT],
44 | [TokenType.EXPONENT, EXPONENT],
45 | [TokenType.SLASH, PRODUCT],
46 | [TokenType.LPAREN, CALL],
47 | [TokenType.LBRACKET, INDEX],
48 | ]);
49 |
--------------------------------------------------------------------------------
/src/services/parser/state.ts:
--------------------------------------------------------------------------------
1 | import { Context, Effect, Layer, Ref } from 'effect'
2 | import { TokenType } from '../../schemas/token-types/union'
3 | import type { Token } from 'src/schemas/token/unions/all'
4 |
5 | export class ParserState {
6 | getCurToken: Effect.Effect
7 | getPeekToken: Effect.Effect
8 | setCurToken: (newCurToken: Token) => Effect.Effect
9 | setPeekToken: (newPeekToken: Token) => Effect.Effect
10 | getCurTokenHistory: Effect.Effect
11 | saveToCurTokenHistory: (token: Token) => Effect.Effect
12 | getPeekTokenHistory: Effect.Effect
13 | saveToPeekTokenHistory: (token: Token) => Effect.Effect
14 | setCurTokenAndSave: (newCurToken: Token) => Effect.Effect
15 | setPeekTokenAndSave: (newPeekToken: Token) => Effect.Effect
16 |
17 | constructor(
18 | private curToken: Ref.Ref,
19 | private peekToken: Ref.Ref,
20 | private curTokenHistory: Ref.Ref,
21 | private peekTokenHistory: Ref.Ref,
22 | ) {
23 | this.getCurToken = Ref.get(this.curToken)
24 | this.getPeekToken = Ref.get(this.peekToken)
25 | this.setCurToken = (newCurToken) =>
26 | Ref.update(this.curToken, (_) => newCurToken)
27 | this.setPeekToken = (newPeekToken) =>
28 | Ref.update(this.peekToken, (_) => newPeekToken)
29 | this.getCurTokenHistory = Ref.get(this.curTokenHistory)
30 | this.getPeekTokenHistory = Ref.get(this.peekTokenHistory)
31 | this.saveToCurTokenHistory = (curToken) =>
32 | Effect.gen(function* () {
33 | yield* Ref.update(curTokenHistory, (curTokenHistory) => {
34 | return [...curTokenHistory, curToken]
35 | })
36 | })
37 | this.saveToPeekTokenHistory = (peekToken) =>
38 | Effect.gen(function* () {
39 | yield* Ref.update(peekTokenHistory, (peekTokenHistory) => {
40 | return [...peekTokenHistory, peekToken]
41 | })
42 | })
43 | this.setCurTokenAndSave = (newCurToken) =>
44 | Effect.gen(function* () {
45 | yield* Ref.update(curToken, (_) => newCurToken)
46 | yield* Ref.update(curTokenHistory, (curTokenHistory) => {
47 | return [...curTokenHistory, newCurToken]
48 | })
49 | })
50 | this.setPeekTokenAndSave = (newPeekToken) =>
51 | Effect.gen(function* () {
52 | yield* Ref.update(peekToken, (_) => newPeekToken)
53 | yield* Ref.update(curTokenHistory, (curTokenHistory) => {
54 | return [...curTokenHistory, newPeekToken]
55 | })
56 | })
57 | }
58 |
59 | static make = () =>
60 | Effect.gen(function* (_) {
61 | const curToken = yield* Ref.make({
62 | _tag: TokenType.ILLEGAL,
63 | literal: '',
64 | } as Token)
65 |
66 | const peekToken = yield* Ref.make({
67 | _tag: TokenType.ILLEGAL,
68 | literal: '',
69 | } as Token)
70 |
71 | const curTokenHistory = yield* Ref.make([])
72 | const peekTokenHistory = yield* Ref.make([])
73 | return new ParserState(
74 | curToken,
75 | peekToken,
76 | curTokenHistory,
77 | peekTokenHistory,
78 | )
79 | })
80 | }
81 |
82 | export class ParserStateService extends Context.Tag('ParserStateService')<
83 | ParserStateService,
84 | ParserState
85 | >() {
86 | static readonly Default = Layer.effect(this, ParserState.make())
87 | }
88 |
--------------------------------------------------------------------------------
/src/services/repl/constants.ts:
--------------------------------------------------------------------------------
1 | export const PROMPT = '>> '
2 |
--------------------------------------------------------------------------------
/src/services/repl/index.ts:
--------------------------------------------------------------------------------
1 | import { Effect } from "effect"
2 | import { LexerStateService } from "../lexer/state"
3 |
4 | export class REPL extends Effect.Service()("REPL", {
5 | effect: Effect.gen(function*() {
6 | const {} = yield* LexerStateService
7 | return {}
8 | }),
9 | dependencies: []
10 | }) {}
11 |
--------------------------------------------------------------------------------
/src/services/test.ts:
--------------------------------------------------------------------------------
1 | import { Terminal } from "@effect/platform"
2 | import { NodeRuntime, NodeTerminal } from "@effect/platform-node"
3 | import { Effect } from "effect"
4 | import { PROMPT } from "./repl/constants"
5 |
6 | const program = Effect.gen(function*() {
7 | const terminal = yield* Terminal.Terminal
8 | while (true) {
9 | yield* terminal.display(`\n${PROMPT}`)
10 | const input = yield* terminal.readLine
11 | yield* terminal.display(input)
12 | }
13 | })
14 |
15 | NodeRuntime.runMain(program.pipe(Effect.provide(NodeTerminal.layer)))
16 | // Input: "hello"
17 | // Output: "input: hello"
18 |
--------------------------------------------------------------------------------
/src/services/tokens.ts:
--------------------------------------------------------------------------------
1 | import { TokenType } from '../schemas/token-types/union'
2 |
3 | export const keywords = {
4 | fn: TokenType.FUNCTION,
5 | let: TokenType.LET,
6 | true: TokenType.TRUE,
7 | false: TokenType.FALSE,
8 | if: TokenType.IF,
9 | else: TokenType.ELSE,
10 | return: TokenType.RETURN,
11 | } as const
12 |
13 | export function lookupIndent(ident: string) {
14 | return ident in keywords
15 | ? keywords[ident as keyof typeof keywords]
16 | : TokenType.IDENT
17 | }
18 |
--------------------------------------------------------------------------------
/src/tests/evaluator/utils.ts:
--------------------------------------------------------------------------------
1 | import { NULL } from "@/schemas/objs/null";
2 | import type { Obj } from "@/schemas/objs/union";
3 | import { Effect, Match } from "effect";
4 | import { KennethParseError } from "src/errors/kenneth/parse";
5 | import { isErrorObj, objInspect } from "src/services/object";
6 |
7 | // TODO: this seems quite redundant
8 |
9 | export const testIntegerObject = (obj: Obj, expected: number) =>
10 | Match.value(obj).pipe(
11 | Match.tag("IntegerObj", (intObj) =>
12 | Effect.fail(
13 | new KennethParseError({
14 | message: `expect obj.value to be ${expected}. got ${intObj.value}`,
15 | }),
16 | ).pipe(Effect.when(() => intObj.value !== expected)),
17 | ),
18 | Match.orElse(() =>
19 | Effect.fail(
20 | new KennethParseError({
21 | message: `obj is not an IntegerObj. got ${obj._tag}`,
22 | }),
23 | ),
24 | ),
25 | );
26 |
27 | export const testStringObject = (obj: Obj, expected: string) =>
28 | Match.value(obj).pipe(
29 | Match.tag("StringObj", (stringObj) =>
30 | Effect.fail(
31 | new KennethParseError({
32 | message: `expect obj.value to be ${expected}. got ${stringObj.value}`,
33 | }),
34 | ).pipe(Effect.when(() => stringObj.value !== expected)),
35 | ),
36 | Match.orElse(() =>
37 | Effect.fail(
38 | new KennethParseError({
39 | message: `obj is not a string. got ${obj._tag}`,
40 | }),
41 | ),
42 | ),
43 | );
44 |
45 | export const testBooleanObject = (obj: Obj, expected: boolean) =>
46 | Match.value(obj).pipe(
47 | Match.tag("BooleanObj", (booleanObj) =>
48 | Effect.fail(
49 | new KennethParseError({
50 | message: `expect obj.value to be ${expected}. got ${booleanObj.value}`,
51 | }),
52 | ).pipe(Effect.when(() => booleanObj.value !== expected)),
53 | ),
54 | Match.orElse(() =>
55 | Effect.fail(
56 | new KennethParseError({
57 | message: `obj is not a boolean. got ${obj._tag}`,
58 | }),
59 | ),
60 | ),
61 | );
62 |
63 | export const testNullOject = (obj: Obj) =>
64 | Effect.fail(
65 | new KennethParseError({
66 | message: `obj is not NULL. got ${objInspect(obj)}`,
67 | }),
68 | ).pipe(Effect.when(() => obj !== NULL));
69 |
70 | export const testErrorObject = (obj: Obj, expected: string) =>
71 | Effect.gen(function* () {
72 | if (!isErrorObj(obj)) {
73 | return yield* new KennethParseError({
74 | message: `obj is not an ErrorObj. got ${JSON.stringify(obj)}`,
75 | });
76 | }
77 | if (obj.message !== expected) {
78 | return yield* new KennethParseError({
79 | message: `expect obj.message to be ${expected}. got ${obj.message}`,
80 | });
81 | }
82 | return true;
83 | });
84 |
--------------------------------------------------------------------------------
/src/tests/parser/statements/let.ts:
--------------------------------------------------------------------------------
1 | import { tokenLiteral } from "@/schemas/nodes/union";
2 | import { Effect, Match } from "effect";
3 | import { KennethParseError } from "src/errors/kenneth/parse";
4 | import type { LetStmt } from "src/schemas/nodes/stmts/let";
5 | import type { Stmt } from "src/schemas/nodes/stmts/union";
6 | import { isLetStatement } from "src/services/ast";
7 |
8 | export const testLetStatement = (statement: Stmt, name: string) =>
9 | Effect.gen(function* () {
10 | return yield* Match.value(statement).pipe(
11 | Match.when(
12 | (statement) => tokenLiteral(statement) !== "let",
13 | function* () {
14 | return yield* new KennethParseError({
15 | message: `expected statement.tokenLiteral() to be let instead got ${tokenLiteral(statement)}`,
16 | });
17 | },
18 | ),
19 | Match.when(
20 | (statement) => isLetStatement(statement),
21 | function* () {
22 | const letStmt = statement as LetStmt;
23 | return yield* Match.value(letStmt).pipe(
24 | Match.when(
25 | {
26 | name: {
27 | value: (value) => value !== name,
28 | },
29 | },
30 | function* () {
31 | return yield* new KennethParseError({
32 | message: `expected statement.name.value to be ${name} instead got ${letStmt.name.value}`,
33 | });
34 | },
35 | ),
36 | Match.when(
37 | (letStmt) => tokenLiteral(letStmt.name) !== name,
38 | function* () {
39 | return yield* new KennethParseError({
40 | message: `expected letStmt.name.tokenLiteral() to be name instead got ${tokenLiteral(letStmt.name)}`,
41 | });
42 | },
43 | ),
44 | Match.orElse(function* () {
45 | return yield* Effect.succeed(true);
46 | }),
47 | );
48 | },
49 | ),
50 | Match.orElse(function* () {
51 | return yield* new KennethParseError({
52 | message: "expected statement to be a let statement",
53 | });
54 | }),
55 | );
56 | });
57 |
--------------------------------------------------------------------------------
/src/tests/parser/statements/return.ts:
--------------------------------------------------------------------------------
1 | import { KennethParseError } from "@/errors/kenneth/parse";
2 | import type { Stmt } from "@/schemas/nodes/stmts/union";
3 | import { tokenLiteral } from "@/schemas/nodes/union";
4 | import { isReturnStatement } from "@/services/ast";
5 | import { Effect, Match } from "effect";
6 |
7 | export const testReturnStatement = (statement: Stmt, name: string) =>
8 | Effect.gen(function* () {
9 | return yield* Match.value(statement).pipe(
10 | Match.when(
11 | (statement) => tokenLiteral(statement) !== "return",
12 | function* () {
13 | return yield* new KennethParseError({
14 | message: `expected tokenLiteral(statement) to be return instead got ${tokenLiteral(statement)}`,
15 | });
16 | },
17 | ),
18 | Match.when(
19 | (statement) => !isReturnStatement(statement),
20 | function* () {
21 | return yield* new KennethParseError({
22 | message: "expected statement to be return statement",
23 | });
24 | },
25 | ),
26 | Match.orElse(function* () {
27 | return yield* Effect.succeed(true);
28 | }),
29 | );
30 | });
31 |
--------------------------------------------------------------------------------
/src/tests/parser/utils/test-bool-exp.ts:
--------------------------------------------------------------------------------
1 | import { Effect } from 'effect'
2 | import { KennethParseError } from 'src/errors/kenneth/parse'
3 | import { BoolExpEq, type BoolExp } from 'src/schemas/nodes/exps/boolean'
4 | import { nativeToExp } from 'src/schemas/nodes/exps/union'
5 |
6 | export const testBoolExp = (boolExp: BoolExp, value: boolean) =>
7 | Effect.gen(function* () {
8 | return !BoolExpEq(boolExp, nativeToExp(value))
9 | ? yield* new KennethParseError({
10 | message: `boolExp not equal ${boolExp.value} !== ${value}`,
11 | })
12 | : undefined
13 | })
14 |
--------------------------------------------------------------------------------
/src/tests/parser/utils/test-identifier.ts:
--------------------------------------------------------------------------------
1 | import { nodeString, tokenLiteral } from "@/schemas/nodes/union";
2 | import { Effect } from "effect";
3 | import { KennethParseError } from "src/errors/kenneth/parse";
4 | import type { Exp } from "src/schemas/nodes/exps/union";
5 | import { isIdentExpression } from "src/services/ast";
6 |
7 | export const testIdentExp = (expression: Exp, value: string) =>
8 | Effect.gen(function* () {
9 | return yield* !isIdentExpression(expression)
10 | ? new KennethParseError({
11 | message: `Expected expression to be IdentExpression got ${nodeString(expression)}`,
12 | })
13 | : expression.value !== value
14 | ? new KennethParseError({
15 | message: `Expected identExpression.Value to be ${value}, got ${expression.value}`,
16 | })
17 | : tokenLiteral(expression) !== value
18 | ? new KennethParseError({
19 | message: `Expected ident.TokenLiteral to be ${value}, got ${tokenLiteral(expression)}`,
20 | })
21 | : Effect.succeed(true);
22 | });
23 |
--------------------------------------------------------------------------------
/src/tests/parser/utils/test-infix-expression.ts:
--------------------------------------------------------------------------------
1 | import { ConstantExp } from "@/schemas/nodes/exps/unions/constant";
2 | import { Effect, Schema } from "effect";
3 | import { KennethParseError } from "src/errors/kenneth/parse";
4 | import { InfixExp } from "src/schemas/nodes/exps/infix";
5 | import type { Exp } from "src/schemas/nodes/exps/union";
6 | import { testLiteralExpression } from "./test-literal-expression";
7 |
8 | export const testInfixExp = (
9 | exp: Exp,
10 | left: string | number | boolean,
11 | operator: string,
12 | right: string | number | boolean,
13 | ) =>
14 | Effect.gen(function* () {
15 | const infixExp = yield* Schema.decodeUnknown(InfixExp)(exp);
16 | yield* testLiteralExpression(infixExp.left, left);
17 |
18 | if (infixExp.operator !== operator) {
19 | return yield* new KennethParseError({
20 | message: `Expected exp.operator to be ${operator}, got ${infixExp.operator} instead.`,
21 | });
22 | }
23 | yield* testLiteralExpression(infixExp.right, right);
24 |
25 | return true;
26 | });
27 |
--------------------------------------------------------------------------------
/src/tests/parser/utils/test-int-exp.ts:
--------------------------------------------------------------------------------
1 | import { Effect } from 'effect'
2 | import { KennethParseError } from 'src/errors/kenneth/parse'
3 | import {
4 | IntExpEq,
5 | nativeToIntExp,
6 | type IntExp,
7 | } from 'src/schemas/nodes/exps/int'
8 |
9 | export const testIntExp = (intExp: IntExp, value: number) =>
10 | Effect.gen(function* () {
11 | return !IntExpEq(intExp, nativeToIntExp(value))
12 | ? yield* new KennethParseError({
13 | message: `intExp not equal ${intExp.value} !== ${value}`,
14 | })
15 | : undefined
16 | })
17 |
--------------------------------------------------------------------------------
/src/tests/parser/utils/test-literal-expression.ts:
--------------------------------------------------------------------------------
1 | import { Effect, Match } from 'effect'
2 | import { KennethParseError } from 'src/errors/kenneth/parse'
3 | import { BoolExpEq, type BoolExp } from 'src/schemas/nodes/exps/boolean'
4 | import { IntExpEq, type IntExp } from 'src/schemas/nodes/exps/int'
5 | import { StrExpEq, type StrExp } from 'src/schemas/nodes/exps/str'
6 | import { nativeToExp } from 'src/schemas/nodes/exps/union'
7 |
8 | export const testLiteralExpression = (
9 | exp: IntExp | StrExp | BoolExp,
10 | value: string | number | boolean,
11 | ) =>
12 | Effect.gen(function* () {
13 | return !Match.value(exp).pipe(
14 | Match.tag('StrExp', (strExp) => StrExpEq(strExp, nativeToExp(value))),
15 | Match.tag('IntExp', (intExp) => IntExpEq(intExp, nativeToExp(value))),
16 | Match.tag('BoolExp', (boolExp) => BoolExpEq(boolExp, nativeToExp(value))),
17 | )
18 | ? yield* new KennethParseError({
19 | message: `${exp._tag} not equal ${exp.value} !== ${value}`,
20 | })
21 | : undefined
22 | })
23 |
--------------------------------------------------------------------------------
/src/tests/parser/utils/test-str-exp.ts:
--------------------------------------------------------------------------------
1 | import { Effect } from 'effect'
2 | import { KennethParseError } from 'src/errors/kenneth/parse'
3 | import { StrExpEq, type StrExp } from 'src/schemas/nodes/exps/str'
4 | import { nativeToExp } from 'src/schemas/nodes/exps/union'
5 |
6 | export const testStrExp = (strExp: StrExp, value: string) =>
7 | Effect.gen(function* () {
8 | return !StrExpEq(strExp, nativeToExp(value))
9 | ? yield* new KennethParseError({
10 | message: `strExp not equal ${strExp.value} !== ${value}`,
11 | })
12 | : undefined
13 | })
14 |
--------------------------------------------------------------------------------
/src/tests/vitest/eval/eval.test.ts:
--------------------------------------------------------------------------------
1 | import { KennethEvalError } from "@/errors/kenneth/eval";
2 | import { defaultLayer } from "@/layers/default";
3 | import { nodeString } from "@/schemas/nodes/union";
4 | import { ArrayObj } from "@/schemas/objs/array";
5 | import { FunctionObj } from "@/schemas/objs/function";
6 | import { Evaluator } from "@/services/evaluator";
7 | import {
8 | expectBooleanObjEq,
9 | expectIntObjEq,
10 | expectStrObjEq,
11 | } from "@/services/expectations/obj/eq";
12 | import { secSquared } from "@/services/math";
13 | import { testIntegerObject, testNullOject } from "@/tests/evaluator/utils";
14 | import { describe, expect, it } from "@effect/vitest";
15 | import { Cause, Effect, Exit, Match, Schema } from "effect";
16 |
17 | const evalP = (input: string) =>
18 | Effect.gen(function* () {
19 | const evaluator = yield* Evaluator;
20 | return yield* evaluator.run(input);
21 | }).pipe(Effect.provide(defaultLayer));
22 |
23 | describe("eval", () => {
24 | describe("IntExp", () => {
25 | const tests = [
26 | ["5", 5],
27 | ["10", 10],
28 | ["-5", -5],
29 | ["-10", -10],
30 | ["5 + 5 + 5 + 5 - 10", 10],
31 | ["2 * 2 * 2 * 2 * 2", 32],
32 | ["-50 + 100 + -50", 0],
33 | ["5 * 2 + 10", 20],
34 | ["5 + 2 * 10", 25],
35 | ["20 + 2 * -10", 0],
36 | ["50 / 2 * 2 + 10", 60],
37 | ["2 * (5 + 10)", 30],
38 | ["3 * 3 * 3 + 10", 37],
39 | ["3 * (3 * 3) + 10", 37],
40 | ["(5 + 10 * 2 + 15 / 3) * 2 + -10", 50],
41 | ["2 ** 2", 4],
42 | ] as const;
43 | for (const [input, expected] of tests) {
44 | it.effect(input, () =>
45 | evalP(input).pipe(
46 | Effect.flatMap((evaluated) => expectIntObjEq(evaluated, expected)),
47 | ),
48 | );
49 | }
50 | });
51 | describe("BoolExp", () => {
52 | const tests = [
53 | ["true", true],
54 | ["false", false],
55 | ["1 < 2", true],
56 | ["1 > 2", false],
57 | ["1 < 1", false],
58 | ["1 > 1", false],
59 | ["1 == 1", true],
60 | ["1 != 1", false],
61 | ["1 == 2", false],
62 | ["1 != 2", true],
63 | ["true == true", true],
64 | ["false == false", true],
65 | ["true == false", false],
66 | ["true != false", true],
67 | ["false != true", true],
68 | ["(1 < 2) == true", true],
69 | ["(1 < 2) == false", false],
70 | ["(1 > 2) == true", false],
71 | ["(1 > 2) == false", true],
72 | ] as const;
73 |
74 | for (const [input, expected] of tests) {
75 | it.effect(input, () =>
76 | evalP(input).pipe(
77 | Effect.flatMap((evaluated) =>
78 | expectBooleanObjEq(evaluated, expected),
79 | ),
80 | ),
81 | );
82 | }
83 | });
84 | describe("PrefixExp", () => {
85 | describe("bang operator", () => {
86 | const tests = [
87 | ["!true", false],
88 | ["!false", true],
89 | ["!5", false],
90 | ["!!true", true],
91 | ["!!false", false],
92 | ["!!5", true],
93 | ] as const;
94 | for (const [input, expected] of tests) {
95 | it.effect(input, () =>
96 | evalP(input).pipe(
97 | Effect.flatMap((evaluated) =>
98 | expectBooleanObjEq(evaluated, expected),
99 | ),
100 | ),
101 | );
102 | }
103 | });
104 | });
105 | describe("IfExp", () => {
106 | const tests = [
107 | ["if (true) { 10 }", 10],
108 | ["if (false) { 10 }", null],
109 | ["if (1) { 10 }", 10],
110 | ["if (1 < 2) { 10 }", 10],
111 | ["if (1 > 2) { 10 }", null],
112 | ["if (1 > 2) { 10 } else { 20 }", 20],
113 | ["if (1 < 2) { 10 } else { 20 }", 10],
114 | ] as const;
115 | for (const [input, expected] of tests) {
116 | it.effect(input, () =>
117 | evalP(input).pipe(
118 | Effect.flatMap((evaluated) =>
119 | Match.value(expected).pipe(
120 | Match.when(Match.number, (expected) =>
121 | expectIntObjEq(evaluated, expected),
122 | ),
123 | Match.when(Match.null, () => testNullOject(evaluated)),
124 | Match.exhaustive,
125 | ),
126 | ),
127 | ),
128 | );
129 | }
130 | });
131 | describe("ReturnExp", () => {
132 | const tests = [
133 | ["return 10;", 10],
134 | ["return 10; 9;", 10],
135 | ["return 2 * 5; 9;", 10],
136 | ["9; return 2 * 5; 9;", 10],
137 | [
138 | `
139 | if (10 > 1) {
140 | if (10 > 1) {
141 | return 10;
142 | }
143 |
144 | return 1;
145 | }
146 | `,
147 | 10,
148 | ],
149 | ] as const;
150 |
151 | for (const [input, expected] of tests) {
152 | it.effect(input, () =>
153 | evalP(input).pipe(
154 | Effect.flatMap((evaluated) => expectIntObjEq(evaluated, expected)),
155 | ),
156 | );
157 | }
158 | });
159 | describe("Error Handling", () => {
160 | // more or less, not checking the ParseError message.
161 | describe("Parse Error", () => {
162 | const tests = [
163 | ["-true", "unknown operator: -BOOLEAN"],
164 | ["foobar", "identifier not found: foobar"],
165 | ] as const;
166 |
167 | for (const [input, expected] of tests) {
168 | it.effect(input, () =>
169 | Effect.gen(function* () {
170 | const result = yield* Effect.exit(evalP(input));
171 |
172 | Exit.match(result, {
173 | onFailure: (cause) =>
174 | `Exited with failure state: ${Cause.pretty(cause)}`,
175 | onSuccess: (value) => expect(true).toBe(false),
176 | });
177 | }),
178 | );
179 | }
180 | });
181 | describe("KennethEvalError", () => {
182 | const tests = [
183 | ["5 + true;", "type mismatch: IntegerObj + BooleanObj"],
184 | ["5 + true; 5;", "type mismatch: IntegerObj + BooleanObj"],
185 | ["true + false;", "unknown operator: BOOLEAN + BOOLEAN"],
186 | ["5; true + false; 5", "unknown operator: BOOLEAN + BOOLEAN"],
187 | [
188 | "if (10 > 1) { true + false; }",
189 | "unknown operator: BOOLEAN + BOOLEAN",
190 | ],
191 | [
192 | `
193 | if (10 > 1) {
194 | if (10 > 1) {
195 | return true + false;
196 | }
197 |
198 | return 1;
199 | }
200 | `,
201 | "unknown operator: BOOLEAN + BOOLEAN",
202 | ],
203 | ] as const;
204 |
205 | for (const [input, expected] of tests) {
206 | it.effect(input, () =>
207 | Effect.gen(function* () {
208 | const result = yield* Effect.exit(evalP(input));
209 | expect(result).toStrictEqual(
210 | Exit.fail(new KennethEvalError({ message: expected })),
211 | );
212 | }),
213 | );
214 | }
215 | });
216 | });
217 | describe("LetStmt", () => {
218 | const tests = [
219 | ["let a = 5; a;", 5],
220 | ["let a = 5 * 5; a;", 25],
221 | ["let a = 5; let b = a; b;", 5],
222 | ["let a = 5; let b = a; let c = a + b + 5; c;", 15],
223 | ] as const;
224 |
225 | for (const [input, expected] of tests) {
226 | it.effect(input, () =>
227 | evalP(input).pipe(
228 | Effect.flatMap((evaluated) => expectIntObjEq(evaluated, expected)),
229 | ),
230 | );
231 | }
232 | });
233 | describe("CallExp", () => {
234 | const tests = [
235 | ["let identity = fn(x) { x; }; identity(5);", 5],
236 | ["let identity = fn(x) { return x; }; identity(5);", 5],
237 | ["let double = fn(x) { x * 2; }; double(5);", 10],
238 | ["let add = fn(x, y) { x + y; }; add(5, 5);", 10],
239 | ["let add = fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", 20],
240 | ["fn(x) { x; }(5)", 5],
241 | ["let add = fn(x, y) { fn (x,y) {x + y}; }; add(5,5)(4,4)", 8],
242 | ] as const;
243 |
244 | for (const [input, expected] of tests) {
245 | it.effect(input, () =>
246 | evalP(input).pipe(
247 | Effect.flatMap((evaluated) => expectIntObjEq(evaluated, expected)),
248 | ),
249 | );
250 | }
251 | });
252 | describe("BuiltInFunc", () => {
253 | const tests = [
254 | ['len("")', 0],
255 | ['len("four")', 4],
256 | ['len("hello world")', 11],
257 | ['let hello = fn(x) { "hello" }; len(hello(1))', 5],
258 | ["pi()", Math.PI],
259 | ] as const;
260 |
261 | for (const [input, expected] of tests) {
262 | it.effect(input, () =>
263 | evalP(input).pipe(
264 | Effect.flatMap((evaluated) => expectIntObjEq(evaluated, expected)),
265 | ),
266 | );
267 | }
268 | describe("Error Handling", () => {
269 | const tests = [
270 | ["len(1)", 'argument to "len" not supported, got IntegerObj'],
271 | ['len("one", "two")', "wrong number of arguments. got=2, want=1"],
272 | ] as const;
273 |
274 | for (const [input, expected] of tests) {
275 | it.effect(input, () =>
276 | Effect.gen(function* () {
277 | const result = yield* Effect.exit(evalP(input));
278 |
279 | Exit.match(result, {
280 | onFailure: (cause) =>
281 | `Exited with failure state: ${Cause.pretty(cause)}`,
282 | onSuccess: (value) => expect(true).toBe(false),
283 | });
284 | }),
285 | );
286 | }
287 | });
288 |
289 | describe("math", () => {
290 | describe("trig", () => {
291 | const tests = [
292 | ["sin(0)", Math.sin(0)],
293 | ["sin(pi() / 2)", Math.sin(Math.PI / 2)],
294 | ["cos(0)", Math.cos(0)],
295 | ["cos(pi() / 2)", Math.cos(Math.PI / 2)],
296 | ["tan(0)", Math.tan(0)],
297 | ["tan(pi() / 4)", Math.tan(Math.PI / 4)],
298 | ] as const;
299 |
300 | for (const [input, expected] of tests) {
301 | it.effect(input, () =>
302 | evalP(input).pipe(
303 | Effect.flatMap((evaluated) =>
304 | expectIntObjEq(evaluated, expected),
305 | ),
306 | ),
307 | );
308 | }
309 | });
310 | describe("log and exp", () => {
311 | const tests = [
312 | ["ln(0)", Math.log(0)],
313 | ["ln(1)", Math.log(1)],
314 | ["ln(e())", Math.log(Math.E)],
315 | ["exp(e())", Math.exp(Math.E)],
316 | ["exp(1)", Math.exp(1)],
317 | ["ln(exp(3))", Math.log(Math.exp(3))],
318 | ] as const;
319 |
320 | for (const [input, expected] of tests) {
321 | it.effect(input, () =>
322 | evalP(input).pipe(
323 | Effect.flatMap((evaluated) =>
324 | expectIntObjEq(evaluated, expected),
325 | ),
326 | ),
327 | );
328 | }
329 | });
330 | });
331 | });
332 | describe("Differentiation", () => {
333 | describe("general", () => {
334 | const tests = [
335 | ["diff(fn(x) { x })(3)", 1],
336 | ["diff(fn(x) { 2 })(3)", 0],
337 | ["diff(fn(x) { 2 * x })(3)", 2],
338 | ["diff(fn(x) { (2 + 0) * x })(3)", 2],
339 | ["let second = 2; diff(fn(x) { x ** second })(3)", 6],
340 | ["diff(fn(x) { 3 * x ** 2 })(3)", 18],
341 | ["diff(fn(x) { 2 + 2 })(3)", 0],
342 | ["diff(fn(x) { 2 + x })(3)", 1],
343 | ["diff(fn(x) { 2 * x ** 3 + x + 3 })(3)", 55],
344 | ["diff(fn(x) { 2 * x ** 3 + (x + 3) })(3)", 55],
345 | ["diff(fn(x) { 2 * x ** 3 + x + 3 + 4 * x + 5 * x ** 4 })(3)", 599],
346 | ["let f = fn(y) { y }; diff(fn(x) { x ** 7 + f(2) })(3)", 5103],
347 | ["let f = fn(y) { y }; diff(fn(x) { x ** 7 + f(x) })(3)", 5104],
348 | ["let second = 2; diff(fn(x) { x ** 7 + second })(3)", 5103],
349 | ["diff(fn(x) { 2 * x ** 3 - (x + 3) })(3)", 53],
350 | ] as const;
351 |
352 | for (const [input, expected] of tests) {
353 | it.effect(input, () =>
354 | evalP(input).pipe(
355 | Effect.flatMap((evaluated) => expectIntObjEq(evaluated, expected)),
356 | ),
357 | );
358 | }
359 | });
360 | describe("product rule", () => {
361 | const tests = [
362 | ["diff(fn(x) { (x + 2 * x ** 3) * (x + 1) })(3)", 277],
363 | [
364 | "diff(fn(x) { (x + 2 * x ** 3) * (x + 1) + (x + 3 * x ** 3) * (x + 1) })(3)",
365 | 689,
366 | ],
367 | ] as const;
368 |
369 | for (const [input, expected] of tests) {
370 | it.effect(input, () =>
371 | evalP(input).pipe(
372 | Effect.flatMap((evaluated) => expectIntObjEq(evaluated, expected)),
373 | ),
374 | );
375 | }
376 | });
377 | describe("quotient rule", () => {
378 | const tests = [
379 | ["diff(fn(x) { (x + 2 * x ** 3) / (x + 1) })(3)", 163 / 16],
380 | [
381 | "diff(fn(x) { (x + 2 * x ** 3) / (x + 1) + (x + 3 * x ** 3) / (x + 1) })(3)",
382 | 163 / 16 + 61 / 4,
383 | ],
384 | ] as const;
385 |
386 | for (const [input, expected] of tests) {
387 | it.effect(input, () =>
388 | evalP(input).pipe(
389 | Effect.flatMap((evaluated) => expectIntObjEq(evaluated, expected)),
390 | ),
391 | );
392 | }
393 | });
394 | describe("chain rule", () => {
395 | const tests = [
396 | ["diff(fn (x) { (3 * x ** 2 + 5 * x) ** 4 })(3)", 6816096],
397 | ["diff(fn (x) { 1 / (2 * x + 3) })(3)", -2 / 81],
398 | [
399 | "let f = fn(y) { y ** 4 }; let g = fn(r) { 3 * r ** 2 + 5 }; let h = fn(x) { f(g(x)) }; diff(h)(3)",
400 | 2359296,
401 | ],
402 | [
403 | "let f = fn(x) { x ** 4 }; let g = fn(x) { 3 * x ** 2 + 5 }; let h = fn(x) { f(g(x)) }; diff(h)(3)",
404 | 2359296,
405 | ],
406 | ] as const;
407 |
408 | for (const [input, expected] of tests) {
409 | it.effect(input, () =>
410 | evalP(input).pipe(
411 | Effect.flatMap((evaluated) => expectIntObjEq(evaluated, expected)),
412 | ),
413 | );
414 | }
415 | });
416 | describe("trig", () => {
417 | const tests = [
418 | ["diff(fn(x) {sin(x)})(0)", Math.cos(0)],
419 | ["diff(fn(x) {sin(x)})(pi() / 2)", Math.cos(Math.PI / 2)],
420 | ["diff(fn(x) {cos(x)})(0)", -Math.sin(0)],
421 | ["diff(fn(x) {cos(x)})(pi() / 2)", -Math.sin(Math.PI / 2)],
422 | ["diff(fn(x) {tan(x)})(0)", secSquared(0)],
423 | ["diff(fn(x) {tan(x)})(pi() / 4)", secSquared(Math.PI / 4)],
424 | ] as const;
425 |
426 | for (const [input, expected] of tests) {
427 | it.effect(input, () =>
428 | evalP(input).pipe(
429 | Effect.flatMap((evaluated) => expectIntObjEq(evaluated, expected)),
430 | ),
431 | );
432 | }
433 | });
434 | describe("log and exp", () => {
435 | const tests = [
436 | ["diff(fn(x) {ln(x)})(1)", 1 / 1],
437 | ["diff(fn(x) {exp(x)})(1)", Math.E],
438 | ] as const;
439 |
440 | for (const [input, expected] of tests) {
441 | it.effect(input, () =>
442 | evalP(input).pipe(
443 | Effect.flatMap((evaluated) => expectIntObjEq(evaluated, expected)),
444 | ),
445 | );
446 | }
447 | });
448 | });
449 | describe("string concatenation", () => {
450 | const tests = [['"Hello" + " " + "World!"', "Hello World!"]] as const;
451 | for (const [input, expected] of tests) {
452 | it.effect(input, () =>
453 | evalP(input).pipe(
454 | Effect.flatMap((evaluated) => expectStrObjEq(evaluated, expected)),
455 | ),
456 | );
457 | }
458 | });
459 | describe("IndexExp", () => {
460 | describe("general", () => {
461 | const tests = [
462 | ["[1, 2, 3][0]", 1],
463 | ["[1, 2, 3][1]", 2],
464 | ["[1, 2, 3][2]", 3],
465 | ["let i = 0; [1][i];", 1],
466 | ["[1, 2, 3][1 + 1];", 3],
467 | ["let myArray = [1, 2, 3]; myArray[2];", 3],
468 | ["let myArray = [1, 2, 3]; myArray[0] + myArray[1] + myArray[2];", 6],
469 | ["let myArray = [1, 2, 3]; let i = myArray[0]; myArray[i]", 2],
470 | ] as const;
471 | for (const [input, expected] of tests) {
472 | it.effect(input, () =>
473 | evalP(input).pipe(
474 | Effect.flatMap((evaluated) =>
475 | Match.value(expected).pipe(
476 | Match.when(Match.number, (expected) =>
477 | expectIntObjEq(evaluated, expected),
478 | ),
479 | Match.when(Match.null, () => testNullOject(evaluated)),
480 | Match.exhaustive,
481 | ),
482 | ),
483 | ),
484 | );
485 | }
486 | });
487 | describe("out of range error handling", () => {
488 | const tests = [
489 | ["[1, 2, 3][3]", null],
490 | ["[1, 2, 3][-1]", null],
491 | ] as const;
492 | for (const [input, expected] of tests) {
493 | it.effect(input, () =>
494 | Effect.gen(function* () {
495 | const result = yield* Effect.exit(evalP(input));
496 |
497 | Exit.match(result, {
498 | onFailure: (cause) =>
499 | `Exited with failure state: ${Cause.pretty(cause)}`,
500 | onSuccess: (value) => expect(true).toBe(false),
501 | });
502 | }),
503 | );
504 | }
505 | });
506 | });
507 |
508 | it.effect("FunctionObj", () =>
509 | evalP("fn(x) { x+ 2}").pipe(
510 | Effect.flatMap((evaluated) =>
511 | Effect.gen(function* () {
512 | const { params, body } =
513 | yield* Schema.decodeUnknown(FunctionObj)(evaluated);
514 | expect(params.length).toBe(1);
515 | expect(nodeString(params[0])).toBe("x");
516 | expect(nodeString(body)).toBe("(x + 2)");
517 | }),
518 | ),
519 | ),
520 | );
521 | it.effect("Closures", () =>
522 | evalP(`
523 | let newAdder = fn(x) {
524 | fn(y) { x + y };
525 | };
526 |
527 | let addTwo = newAdder(2);
528 | addTwo(2);
529 | `).pipe(Effect.flatMap((evaluated) => expectIntObjEq(evaluated, 4))),
530 | );
531 | it.effect("StrExp", () =>
532 | evalP('"Hello World!"').pipe(
533 | Effect.flatMap((evaluated) => expectStrObjEq(evaluated, "Hello World!")),
534 | ),
535 | );
536 | it.effect("ArrayExp", () =>
537 | evalP("[1, 2 * 2, 3 + 3]").pipe(
538 | Effect.flatMap((evaluated) =>
539 | Effect.gen(function* () {
540 | const arrayObj = yield* Schema.decodeUnknown(ArrayObj)(evaluated);
541 |
542 | expect(arrayObj.elements.length).toBe(3);
543 |
544 | yield* expectIntObjEq(arrayObj.elements[0], 1);
545 | yield* expectIntObjEq(arrayObj.elements[1], 4);
546 | yield* expectIntObjEq(arrayObj.elements[2], 6);
547 | }),
548 | ),
549 | ),
550 | );
551 | });
552 |
--------------------------------------------------------------------------------
/src/tests/vitest/lex/lex.test.ts:
--------------------------------------------------------------------------------
1 | import { TokenType } from "@/schemas/token-types/union";
2 | import { Lexer } from "@/services/lexer";
3 | import { describe, expect, it } from "@effect/vitest";
4 | import { Effect, LogLevel, Logger } from "effect";
5 |
6 | const tests: [string, string, [TokenType, string][]][] = [
7 | [
8 | "simple",
9 | "=+()",
10 | [
11 | [TokenType.ASSIGN, TokenType.ASSIGN],
12 | [TokenType.PLUS, TokenType.PLUS],
13 | [TokenType.LPAREN, TokenType.LPAREN],
14 | [TokenType.RPAREN, TokenType.RPAREN],
15 | ],
16 | ],
17 | [
18 | "exponent",
19 | "return 2 ** 2;",
20 | [
21 | [TokenType.RETURN, TokenType.RETURN],
22 | [TokenType.INT, "2"],
23 | [TokenType.EXPONENT, TokenType.EXPONENT],
24 | [TokenType.INT, "2"],
25 | [TokenType.SEMICOLON, TokenType.SEMICOLON],
26 | ],
27 | ],
28 | [
29 | "diff",
30 | "diff(fn(x) { x })(3)",
31 | [
32 | [TokenType.IDENT, "diff"],
33 | [TokenType.LPAREN, TokenType.LPAREN],
34 | [TokenType.FUNCTION, TokenType.FUNCTION],
35 | [TokenType.LPAREN, TokenType.LPAREN],
36 | [TokenType.IDENT, "x"],
37 | [TokenType.RPAREN, TokenType.RPAREN],
38 | [TokenType.LBRACE, TokenType.LBRACE],
39 | [TokenType.IDENT, "x"],
40 | [TokenType.RBRACE, TokenType.RBRACE],
41 | [TokenType.RPAREN, TokenType.RPAREN],
42 | [TokenType.LPAREN, TokenType.LPAREN],
43 | [TokenType.INT, "3"],
44 | [TokenType.RPAREN, TokenType.RPAREN],
45 | ],
46 | ],
47 | [
48 | "complicated",
49 | `let five = 5;
50 | let ten = 10;
51 | let add = fn(x, y) {
52 | x + y;
53 | };
54 | let result = add(five, ten);
55 | !-/*5;
56 | 5 < 10 > 5;
57 | if (5 < 10) {
58 | return true;
59 | } else {
60 | return false;
61 | }
62 |
63 | 10 == 10;
64 | 10 != 9;
65 | "foobar"
66 | "foo bar"
67 | [1,2];
68 | `,
69 | [
70 | [TokenType.LET, TokenType.LET],
71 | [TokenType.IDENT, "five"],
72 | [TokenType.ASSIGN, TokenType.ASSIGN],
73 | [TokenType.INT, "5"],
74 | [TokenType.SEMICOLON, TokenType.SEMICOLON],
75 | [TokenType.LET, TokenType.LET],
76 | [TokenType.IDENT, "ten"],
77 | [TokenType.ASSIGN, TokenType.ASSIGN],
78 | [TokenType.INT, "10"],
79 | [TokenType.SEMICOLON, TokenType.SEMICOLON],
80 | [TokenType.LET, TokenType.LET],
81 | [TokenType.IDENT, "add"],
82 | [TokenType.ASSIGN, TokenType.ASSIGN],
83 | [TokenType.FUNCTION, TokenType.FUNCTION],
84 | [TokenType.LPAREN, TokenType.LPAREN],
85 | [TokenType.IDENT, "x"],
86 | [TokenType.COMMA, TokenType.COMMA],
87 | [TokenType.IDENT, "y"],
88 | [TokenType.RPAREN, TokenType.RPAREN],
89 | [TokenType.LBRACE, TokenType.LBRACE],
90 | [TokenType.IDENT, "x"],
91 | [TokenType.PLUS, TokenType.PLUS],
92 | [TokenType.IDENT, "y"],
93 | [TokenType.SEMICOLON, TokenType.SEMICOLON],
94 | [TokenType.RBRACE, TokenType.RBRACE],
95 | [TokenType.SEMICOLON, TokenType.SEMICOLON],
96 | [TokenType.LET, TokenType.LET],
97 | [TokenType.IDENT, "result"],
98 | [TokenType.ASSIGN, TokenType.ASSIGN],
99 | [TokenType.IDENT, "add"],
100 | [TokenType.LPAREN, TokenType.LPAREN],
101 | [TokenType.IDENT, "five"],
102 | [TokenType.COMMA, TokenType.COMMA],
103 | [TokenType.IDENT, "ten"],
104 | [TokenType.RPAREN, TokenType.RPAREN],
105 | [TokenType.SEMICOLON, TokenType.SEMICOLON],
106 | [TokenType.BANG, TokenType.BANG],
107 | [TokenType.MINUS, TokenType.MINUS],
108 | [TokenType.SLASH, TokenType.SLASH],
109 | [TokenType.ASTERISK, TokenType.ASTERISK],
110 | [TokenType.INT, "5"],
111 | [TokenType.SEMICOLON, TokenType.SEMICOLON],
112 | [TokenType.INT, "5"],
113 | [TokenType.LT, TokenType.LT],
114 | [TokenType.INT, "10"],
115 | [TokenType.GT, TokenType.GT],
116 | [TokenType.INT, "5"],
117 | [TokenType.SEMICOLON, TokenType.SEMICOLON],
118 | [TokenType.IF, TokenType.IF],
119 | [TokenType.LPAREN, TokenType.LPAREN],
120 | [TokenType.INT, "5"],
121 | [TokenType.LT, TokenType.LT],
122 | [TokenType.INT, "10"],
123 | [TokenType.RPAREN, TokenType.RPAREN],
124 | [TokenType.LBRACE, TokenType.LBRACE],
125 | [TokenType.RETURN, TokenType.RETURN],
126 | [TokenType.TRUE, TokenType.TRUE],
127 | [TokenType.SEMICOLON, TokenType.SEMICOLON],
128 | [TokenType.RBRACE, TokenType.RBRACE],
129 | [TokenType.ELSE, TokenType.ELSE],
130 | [TokenType.LBRACE, TokenType.LBRACE],
131 | [TokenType.RETURN, TokenType.RETURN],
132 | [TokenType.FALSE, TokenType.FALSE],
133 | [TokenType.SEMICOLON, TokenType.SEMICOLON],
134 | [TokenType.RBRACE, TokenType.RBRACE],
135 | [TokenType.INT, "10"],
136 | [TokenType.EQ, TokenType.EQ],
137 | [TokenType.INT, "10"],
138 | [TokenType.SEMICOLON, TokenType.SEMICOLON],
139 | [TokenType.INT, "10"],
140 | [TokenType.NOT_EQ, TokenType.NOT_EQ],
141 | [TokenType.INT, "9"],
142 | [TokenType.SEMICOLON, TokenType.SEMICOLON],
143 | [TokenType.STRING, "foobar"],
144 | [TokenType.STRING, "foo bar"],
145 | [TokenType.LBRACKET, "["],
146 | [TokenType.INT, "1"],
147 | [TokenType.COMMA, ","],
148 | [TokenType.INT, "2"],
149 | [TokenType.RBRACKET, "]"],
150 | [TokenType.SEMICOLON, TokenType.SEMICOLON],
151 | [TokenType.EOF, TokenType.EOF],
152 | ],
153 | ],
154 | ];
155 |
156 | describe("lex tests", () => {
157 | for (const [desc, input, expected] of tests) {
158 | it.effect(desc, () =>
159 | Effect.gen(function* () {
160 | const lexer = yield* Lexer;
161 | yield* lexer.init(input);
162 |
163 | for (const [expectedType, expectedLiteral] of expected) {
164 | const token = yield* lexer.nextToken;
165 | expect(token._tag).toBe(expectedType);
166 | expect(token.literal).toBe(expectedLiteral);
167 | }
168 | }).pipe(
169 | Effect.provide(Lexer.Default),
170 | Logger.withMinimumLogLevel(LogLevel.Debug),
171 | ),
172 | );
173 | }
174 | });
175 |
--------------------------------------------------------------------------------
/src/tests/vitest/parse/helper.ts:
--------------------------------------------------------------------------------
1 | import { ExpStmt } from "@/schemas/nodes/stmts/exp";
2 | import { Parser } from "@/services/parser";
3 | import { Effect, Schema } from "effect";
4 |
5 | export const getExpFromProgram = (input: string, optimized = false) =>
6 | Effect.gen(function* () {
7 | const parser = yield* Parser;
8 | yield* parser.init(input);
9 |
10 | const program = optimized
11 | ? yield* parser.parseProgramOptimized
12 | : yield* parser.parseProgram;
13 |
14 | const [{ expression }] = yield* Schema.decodeUnknown(Schema.Tuple(ExpStmt))(
15 | program.statements,
16 | );
17 | return expression;
18 | });
19 |
--------------------------------------------------------------------------------
/src/tests/vitest/parse/parse.test.ts:
--------------------------------------------------------------------------------
1 | import { defaultLayer } from "@/layers/default";
2 | import { IndexExp } from "@/schemas/nodes/exps";
3 | import { ArrayExp } from "@/schemas/nodes/exps/array";
4 | import { CallExp } from "@/schemas/nodes/exps/call";
5 | import { FuncExp } from "@/schemas/nodes/exps/function";
6 | import { nativeToIdentExp } from "@/schemas/nodes/exps/ident";
7 | import { IfExp } from "@/schemas/nodes/exps/if";
8 | import { InfixExp } from "@/schemas/nodes/exps/infix";
9 | import { Program } from "@/schemas/nodes/program";
10 | import { ExpStmt } from "@/schemas/nodes/stmts/exp";
11 | import { LetStmt } from "@/schemas/nodes/stmts/let";
12 | import { ReturnStmt } from "@/schemas/nodes/stmts/return";
13 | import { nodeString, tokenLiteral } from "@/schemas/nodes/union";
14 | import { TokenType } from "@/schemas/token-types/union";
15 | import {
16 | expectBooleanExpEq,
17 | expectIdentExpEq,
18 | expectIntExpEq,
19 | expectStrExpEq,
20 | } from "@/services/expectations/exp/eq";
21 | import { Parser } from "@/services/parser";
22 | import { testInfixExp } from "@/tests/parser/utils/test-infix-expression";
23 | import { describe, expect, it } from "@effect/vitest";
24 | import { Effect, Schema } from "effect";
25 | import { getExpFromProgram } from "./helper";
26 |
27 | describe("parse", () => {
28 | it("nodeString program", () => {
29 | const program = Program.make({
30 | token: {
31 | _tag: TokenType.IDENT,
32 | literal: "myVar",
33 | },
34 | statements: [
35 | LetStmt.make({
36 | name: nativeToIdentExp("myVar"),
37 | token: {
38 | _tag: TokenType.LET,
39 | literal: TokenType.LET,
40 | },
41 | value: nativeToIdentExp("anotherVar"),
42 | }),
43 | ],
44 | });
45 | expect(nodeString(program)).toBe(`${TokenType.LET} myVar = anotherVar;`);
46 | });
47 | it.effect("IntExp", () =>
48 | // TODO: generic testing.
49 | Effect.gen(function* () {
50 | const exp = yield* getExpFromProgram("5;");
51 | yield* expectIntExpEq(exp, 5);
52 | }).pipe(Effect.provide(Parser.Default)),
53 | );
54 | it.effect("BoolExp", () =>
55 | Effect.gen(function* () {
56 | const exp = yield* getExpFromProgram("true;");
57 | yield* expectBooleanExpEq(exp, true);
58 | }).pipe(Effect.provide(Parser.Default)),
59 | );
60 | describe("IfExp", () => {
61 | it.effect("if", () =>
62 | Effect.gen(function* () {
63 | const exp = yield* getExpFromProgram(
64 | `${TokenType.IF} ${TokenType.LPAREN}x ${TokenType.LT} y${TokenType.RPAREN} ${TokenType.LBRACE} x ${TokenType.RBRACE}`,
65 | );
66 | const {
67 | condition,
68 | alternative,
69 | consequence: { statements },
70 | } = yield* Schema.decodeUnknown(IfExp)(exp);
71 |
72 | yield* testInfixExp(condition, "x", TokenType.LT, "y");
73 |
74 | const [consequence] = yield* Schema.decodeUnknown(
75 | Schema.Tuple(ExpStmt),
76 | )(statements);
77 |
78 | yield* expectIdentExpEq(consequence.expression, "x");
79 |
80 | expect(alternative).toBeUndefined();
81 | }).pipe(Effect.provide(defaultLayer)),
82 | );
83 | it.effect("if else", () =>
84 | Effect.gen(function* () {
85 | const exp = yield* getExpFromProgram(
86 | `${TokenType.IF} ${TokenType.LPAREN}x ${TokenType.LT} y${TokenType.RPAREN} ${TokenType.LBRACE} x ${TokenType.RBRACE} ${TokenType.ELSE} ${TokenType.LBRACE} y ${TokenType.RBRACE}`,
87 | );
88 | const {
89 | condition,
90 | alternative,
91 | consequence: { statements },
92 | } = yield* Schema.decodeUnknown(IfExp)(exp);
93 |
94 | yield* testInfixExp(condition, "x", TokenType.LT, "y");
95 |
96 | const [{ expression }] = yield* Schema.decodeUnknown(
97 | Schema.Tuple(ExpStmt),
98 | )(statements);
99 |
100 | yield* expectIdentExpEq(expression, "x");
101 |
102 | const [{ expression: altExp }] = yield* Schema.decodeUnknown(
103 | Schema.Tuple(ExpStmt),
104 | )(alternative?.statements);
105 |
106 | yield* expectIdentExpEq(altExp, "y");
107 | }).pipe(Effect.provide(defaultLayer)),
108 | );
109 | it.effect("nested if", () =>
110 | Effect.gen(function* () {
111 | const exp = yield* getExpFromProgram(`
112 | if (11 > 1) {
113 | if (10 > 1) {
114 | return 10;
115 | }
116 |
117 | return 1;
118 | }`);
119 |
120 | const {
121 | condition,
122 | alternative,
123 | consequence: { statements },
124 | } = yield* Schema.decodeUnknown(IfExp)(exp);
125 |
126 | yield* testInfixExp(condition, "10", TokenType.GT, " 1");
127 |
128 | const [{ expression }, returnStmt] = yield* Schema.decodeUnknown(
129 | Schema.Tuple(ExpStmt, ReturnStmt),
130 | )(statements);
131 |
132 | const consequence1IfExp =
133 | yield* Schema.decodeUnknown(IfExp)(expression);
134 |
135 | const consequence1IfExpCondition = yield* Schema.decodeUnknown(
136 | InfixExp,
137 | )(consequence1IfExp.condition);
138 |
139 | yield* testInfixExp(consequence1IfExpCondition, "10", ">", "1");
140 |
141 | const [innerFirstConsequence] = yield* Schema.decodeUnknown(
142 | Schema.Tuple(ReturnStmt),
143 | )(consequence1IfExp.consequence.statements);
144 |
145 | expect(tokenLiteral(innerFirstConsequence)).toBe("return");
146 |
147 | yield* expectIntExpEq(innerFirstConsequence.value, 10);
148 | expect(alternative).toBeUndefined();
149 |
150 | expect(tokenLiteral(returnStmt)).toBe("return");
151 | yield* expectIntExpEq(returnStmt.value, 1);
152 | }).pipe(Effect.provide(defaultLayer)),
153 | );
154 | });
155 | it.effect("FuncExp", () =>
156 | Effect.gen(function* () {
157 | const exp = yield* getExpFromProgram(
158 | `${TokenType.FUNCTION}${TokenType.LPAREN}x,y${TokenType.RPAREN} ${TokenType.LBRACE} x + y${TokenType.RBRACE}${TokenType.SEMICOLON}`,
159 | );
160 | const {
161 | parameters,
162 | body: { statements },
163 | } = yield* Schema.decodeUnknown(FuncExp)(exp);
164 |
165 | expect(parameters.length).toBe(2);
166 |
167 | yield* expectIdentExpEq(parameters[0], "x");
168 | yield* expectIdentExpEq(parameters[1], "y");
169 |
170 | const [{ expression }] = yield* Schema.decodeUnknown(
171 | Schema.Tuple(ExpStmt),
172 | )(statements);
173 |
174 | const infixExp = yield* Schema.decodeUnknown(InfixExp)(expression);
175 |
176 | yield* testInfixExp(infixExp, "x", "+", "y");
177 | }).pipe(Effect.provide(defaultLayer)),
178 | );
179 | it.effect("CallExp", () =>
180 | Effect.gen(function* () {
181 | const exp = yield* getExpFromProgram("add(1, 2 * 3, 4 + 5);");
182 |
183 | const { fn, args } = yield* Schema.decodeUnknown(CallExp)(exp);
184 |
185 | yield* expectIdentExpEq(fn, "add");
186 |
187 | expect(args.length).toBe(3);
188 |
189 | yield* expectIntExpEq(args[0], 1);
190 | yield* testInfixExp(args[1], 2, TokenType.ASTERISK, 3);
191 | yield* testInfixExp(args[2], 4, TokenType.PLUS, 5);
192 | }).pipe(Effect.provide(defaultLayer)),
193 | );
194 | it.effect("StrExp", () =>
195 | Effect.gen(function* () {
196 | const exp = yield* getExpFromProgram('"hello world";');
197 | yield* expectStrExpEq(exp, "hello world");
198 | }).pipe(Effect.provide(defaultLayer)),
199 | );
200 | it.effect("Constant Folding", () =>
201 | Effect.gen(function* () {
202 | const exp = yield* getExpFromProgram("1 + 2 + 3 + (4 + 5);", true);
203 | yield* expectIntExpEq(exp, 15);
204 | }).pipe(Effect.provide(defaultLayer)),
205 | );
206 | it.effect("IdentExp", () =>
207 | Effect.gen(function* () {
208 | const exp = yield* getExpFromProgram("foobar;");
209 |
210 | yield* expectIdentExpEq(exp, "foobar");
211 | }).pipe(Effect.provide(defaultLayer)),
212 | );
213 | it.effect("ArrayExp", () =>
214 | Effect.gen(function* () {
215 | const exp = yield* getExpFromProgram("[1, 2 * 2, 3 + 3]");
216 |
217 | const { elements } = yield* Schema.decodeUnknown(ArrayExp)(exp);
218 |
219 | expect(elements.length).toBe(3);
220 |
221 | yield* expectIntExpEq(elements[0], 1);
222 | yield* testInfixExp(elements[1], 2, TokenType.ASTERISK, 2);
223 | yield* testInfixExp(elements[2], 3, TokenType.PLUS, 3);
224 | }).pipe(Effect.provide(defaultLayer)),
225 | );
226 | it.effect("IndexExp", () =>
227 | Effect.gen(function* () {
228 | const exp = yield* getExpFromProgram("myArray[1 + 1]");
229 |
230 | const { left, index } = yield* Schema.decodeUnknown(IndexExp)(exp);
231 |
232 | yield* expectIdentExpEq(left, "myArray");
233 | yield* testInfixExp(index, 1, TokenType.PLUS, 1);
234 | }).pipe(Effect.provide(defaultLayer)),
235 | );
236 | });
237 |
--------------------------------------------------------------------------------
/src/vite/App.css:
--------------------------------------------------------------------------------
1 | @import "tailwindcss";
2 |
3 | @plugin "tailwindcss-animate";
4 |
5 | @custom-variant dark (&:is(.dark *));
6 |
7 | :root {
8 | --background: hsl(0 0% 100%);
9 | --foreground: hsl(0 0% 3.9%);
10 | --card: hsl(0 0% 100%);
11 | --card-foreground: hsl(0 0% 3.9%);
12 | --popover: hsl(0 0% 100%);
13 | --popover-foreground: hsl(0 0% 3.9%);
14 | --primary: hsl(0 0% 9%);
15 | --primary-foreground: hsl(0 0% 98%);
16 | --secondary: hsl(0 0% 96.1%);
17 | --secondary-foreground: hsl(0 0% 9%);
18 | --muted: hsl(0 0% 96.1%);
19 | --muted-foreground: hsl(0 0% 45.1%);
20 | --accent: hsl(0 0% 96.1%);
21 | --accent-foreground: hsl(0 0% 9%);
22 | --destructive: hsl(0 84.2% 60.2%);
23 | --destructive-foreground: hsl(0 0% 98%);
24 | --border: hsl(0 0% 89.8%);
25 | --input: hsl(0 0% 89.8%);
26 | --ring: hsl(0 0% 3.9%);
27 | --chart-1: hsl(12 76% 61%);
28 | --chart-2: hsl(173 58% 39%);
29 | --chart-3: hsl(197 37% 24%);
30 | --chart-4: hsl(43 74% 66%);
31 | --chart-5: hsl(27 87% 67%);
32 | --radius: 0.6rem;
33 |
34 | /* LAYOUT */
35 | --contentMax: calc(100% - var(--gap) * 2);
36 | --content: min(80ch, var(--contentMax));
37 | }
38 |
39 | .dark {
40 | --background: hsl(0 0% 3.9%);
41 | --foreground: hsl(0 0% 98%);
42 | --card: hsl(0 0% 3.9%);
43 | --card-foreground: hsl(0 0% 98%);
44 | --popover: hsl(0 0% 3.9%);
45 | --popover-foreground: hsl(0 0% 98%);
46 | --primary: hsl(0 0% 98%);
47 | --primary-foreground: hsl(0 0% 9%);
48 | --secondary: hsl(0 0% 14.9%);
49 | --secondary-foreground: hsl(0 0% 98%);
50 | --muted: hsl(0 0% 14.9%);
51 | --muted-foreground: hsl(0 0% 63.9%);
52 | --accent: hsl(0 0% 14.9%);
53 | --accent-foreground: hsl(0 0% 98%);
54 | --destructive: hsl(0 62.8% 30.6%);
55 | --destructive-foreground: hsl(0 0% 98%);
56 | --border: hsl(0 0% 14.9%);
57 | --input: hsl(0 0% 14.9%);
58 | --ring: hsl(0 0% 83.1%);
59 | --chart-1: hsl(220 70% 50%);
60 | --chart-2: hsl(160 60% 45%);
61 | --chart-3: hsl(30 80% 55%);
62 | --chart-4: hsl(280 65% 60%);
63 | --chart-5: hsl(340 75% 55%);
64 | }
65 |
66 | @theme inline {
67 | /* LAYOUT */
68 | --contentMax: calc(100% - var(--gap) * 2);
69 | --content: min(80ch, var(--contentMax));
70 | --color-background: var(--background);
71 | --color-foreground: var(--foreground);
72 | --color-card: var(--card);
73 | --color-card-foreground: var(--card-foreground);
74 | --color-popover: var(--popover);
75 | --color-popover-foreground: var(--popover-foreground);
76 | --color-primary: var(--primary);
77 | --color-primary-foreground: var(--primary-foreground);
78 | --color-secondary: var(--secondary);
79 | --color-secondary-foreground: var(--secondary-foreground);
80 | --color-muted: var(--muted);
81 | --color-muted-foreground: var(--muted-foreground);
82 | --color-accent: var(--accent);
83 | --color-accent-foreground: var(--accent-foreground);
84 | --color-destructive: var(--destructive);
85 | --color-destructive-foreground: var(--destructive-foreground);
86 | --color-border: var(--border);
87 | --color-input: var(--input);
88 | --color-ring: var(--ring);
89 | --color-chart-1: var(--chart-1);
90 | --color-chart-2: var(--chart-2);
91 | --color-chart-3: var(--chart-3);
92 | --color-chart-4: var(--chart-4);
93 | --color-chart-5: var(--chart-5);
94 | --radius-sm: calc(var(--radius) - 4px);
95 | --radius-md: calc(var(--radius) - 2px);
96 | --radius-lg: var(--radius);
97 | --radius-xl: calc(var(--radius) + 4px);
98 | }
99 |
100 | @layer base {
101 | * {
102 | @apply border-border outline-ring/50;
103 | }
104 | body {
105 | @apply bg-background text-foreground;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/vite/App.tsx:
--------------------------------------------------------------------------------
1 | import { objInspect } from "@/services/object";
2 | import { Match } from "effect";
3 | import { useState } from "react";
4 | import { Textarea } from "../components/ui/textarea";
5 | import { runAndInterpret } from "../programs/run-and-interpret";
6 | import { examples } from "./examples";
7 |
8 | const PROMPT = ">>";
9 |
10 | function App() {
11 | const [count, setCount] = useState(0);
12 | const [text, setText] = useState(PROMPT);
13 | const [instructions, setInstructions] = useState("");
14 | const [evaluations, setEvaluations] = useState([]);
15 |
16 | const handleKeyDown = (e: React.KeyboardEvent) => {
17 | if (e.ctrlKey && e.key === "c") {
18 | setText((prevText) => `\n${PROMPT}`);
19 | e.preventDefault();
20 | }
21 | };
22 |
23 | const handleEnter = async (newText: string) => {
24 | console.log("enter");
25 | const userInput = newText
26 | .substring(newText.lastIndexOf(PROMPT) + PROMPT.length)
27 | .trim();
28 | if (userInput.toLowerCase() === "clear") {
29 | setText((prevText) => `${PROMPT}`);
30 | return;
31 | }
32 | const returnValue = await runAndInterpret(instructions + userInput);
33 |
34 | setInstructions(
35 | (prevInstructions) =>
36 | `${prevInstructions}${newText.substring(newText.lastIndexOf(PROMPT) + PROMPT.length).trim()}\n`,
37 | );
38 | console.log("instructions", instructions);
39 | const evaluated = Match.value(returnValue).pipe(
40 | Match.tag("ErrorObj", (errorObj) => {
41 | return errorObj.message;
42 | }),
43 | Match.orElse((r) => {
44 | return objInspect(r.evaluation);
45 | }),
46 | );
47 |
48 | setEvaluations((evals) => [...evals, evaluated]);
49 |
50 | setText((prevText) => `${prevText}${evaluated}\n${PROMPT}`);
51 | };
52 |
53 | const handleChange = async (e: React.ChangeEvent) => {
54 | const newText = e.target.value;
55 | setText(newText);
56 | if (newText.charAt(newText.length - 1) === "\n") {
57 | await handleEnter(newText);
58 | }
59 | };
60 | return (
61 |
62 |
63 |
64 | ts-monkey repl
65 |
66 |
72 |
73 | {examples.map((str, i) => (
74 |
86 | ))}
87 |
88 |
89 | {/*
90 |
91 | {examples.map((str, i) => (
92 |
93 | {str}
94 |
95 | ))}
96 |
*/}
97 |
98 | );
99 | }
100 |
101 | export default App;
102 |
--------------------------------------------------------------------------------
/src/vite/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/vite/examples.ts:
--------------------------------------------------------------------------------
1 | export const examples = [
2 | ["diff(fn(x) { x })(3)"],
3 | ["diff(fn(x) { 2 })(3)"],
4 | ["diff(fn(x) { 2 * x })(3)"],
5 | ["diff(fn(x) { (2 + 0) * x })(3)"],
6 | ["let second = 2; diff(fn(x) { x ** second })(3)"],
7 | ["diff(fn(x) { 3 * x ** 2 })(3)"],
8 | ["diff(fn(x) { 2 + 2 })(3)"],
9 | ["diff(fn(x) { 2 + x })(3)"],
10 | ["diff(fn(x) { 2 * x ** 3 + x + 3 })(3)"],
11 | ["diff(fn(x) { 2 * x ** 3 + (x + 3) })(3)"],
12 | ["diff(fn(x) { 2 * x ** 3 + x + 3 + 4 * x + 5 * x ** 4 })(3)"],
13 | ["let f = fn(y) { y }; diff(fn(x) { x ** 7 + f(2) })(3)"],
14 | ["let f = fn(y) { y }; diff(fn(x) { x ** 7 + f(x) })(3)"],
15 | ["let second = 2; diff(fn(x) { x ** 7 + second })(3)"],
16 | ["diff(fn(x) { 2 * x ** 3 - (x + 3) })(3)"],
17 | ["diff(fn(x) { (x + 2 * x ** 3) * (x + 1) })(3)"],
18 | [
19 | "diff(fn(x) { (x + 2 * x ** 3) * (x + 1) + (x + 3 * x ** 3) * (x + 1) })(3)",
20 | ],
21 | ["diff(fn(x) { (x + 2 * x ** 3) / (x + 1) })(3)"],
22 | [
23 | "diff(fn(x) { (x + 2 * x ** 3) / (x + 1) + (x + 3 * x ** 3) / (x + 1) })(3)",
24 | ],
25 | ["diff(fn (x) { (3 * x ** 2 + 5 * x) ** 4 })(3)"],
26 | ["diff(fn (x) { 1 / (2 * x + 3) })(3)"],
27 | ["sin(0)"],
28 | ["sin(pi() / 2)"],
29 | ["cos(0)"],
30 | ["cos(pi() / 2)"],
31 | ["tan(0)"],
32 | ["tan(pi() / 4)"],
33 | ["ln(0)"],
34 | ["ln(1)"],
35 | ["ln(e())"],
36 | ["exp(e())"],
37 | ["exp(1)"],
38 | ["ln(exp(3))"],
39 | ["diff(fn(x) {sin(x)})(0)"],
40 | ["diff(fn(x) {sin(x)})(pi() / 2)"],
41 | ["diff(fn(x) {cos(x)})(0)"],
42 | ["diff(fn(x) {cos(x)})(pi() / 2)"],
43 | ["diff(fn(x) {tan(x)})(0)"],
44 | ["diff(fn(x) {tan(x)})(pi() / 4)"],
45 | ];
46 |
--------------------------------------------------------------------------------
/src/vite/main.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from "react";
2 | import { createRoot } from "react-dom/client";
3 | import App from "./App";
4 |
5 | createRoot(document.getElementById("root")).render(
6 |
7 |
8 | ,
9 | );
10 |
--------------------------------------------------------------------------------
/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "jsx": "react-jsx",
4 | "strict": true,
5 | "exactOptionalPropertyTypes": true,
6 | "moduleDetection": "force",
7 | "composite": true,
8 | "downlevelIteration": true,
9 | "resolveJsonModule": true,
10 | "esModuleInterop": false,
11 | "declaration": true,
12 | "skipLibCheck": true,
13 | "emitDecoratorMetadata": true,
14 | "experimentalDecorators": true,
15 | "moduleResolution": "bundler",
16 | "lib": [
17 | "ES2022",
18 | "DOM",
19 | "DOM.Iterable"
20 | ],
21 | "types": [],
22 | "isolatedModules": true,
23 | "sourceMap": true,
24 | "declarationMap": true,
25 | "noImplicitReturns": false,
26 | "noUnusedLocals": true,
27 | "noUnusedParameters": false,
28 | "noFallthroughCasesInSwitch": true,
29 | "noEmitOnError": false,
30 | "noErrorTruncation": false,
31 | "allowJs": false,
32 | "checkJs": false,
33 | "forceConsistentCasingInFileNames": true,
34 | "noImplicitAny": true,
35 | "noImplicitThis": true,
36 | "noUncheckedIndexedAccess": false,
37 | "strictNullChecks": true,
38 | "baseUrl": ".",
39 | "paths": {
40 | "@/*": [
41 | "./src/*"
42 | ],
43 | "src/*": [
44 | "src/*"
45 | ]
46 | },
47 | "target": "ES2022",
48 | "incremental": true,
49 | "removeComments": false,
50 | "plugins": [
51 | {
52 | "name": "@effect/language-service"
53 | }
54 | ]
55 | }
56 | }
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.src.json",
3 | "compilerOptions": {
4 | "types": ["node"],
5 | "tsBuildInfoFile": ".tsbuildinfo/build.tsbuildinfo",
6 | "outDir": "build/esm",
7 | "declarationDir": "build/dts",
8 | "stripInternal": true
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.base.json",
3 | "include": [],
4 | "references": [
5 | { "path": "tsconfig.src.json" },
6 | { "path": "tsconfig.test.json" }
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/tsconfig.src.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.base.json",
3 | "include": ["src"],
4 | "compilerOptions": {
5 | "types": ["node"],
6 | "outDir": "build/src",
7 | "tsBuildInfoFile": ".tsbuildinfo/src.tsbuildinfo",
8 | "rootDir": "src"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/tsconfig.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.base.json",
3 | "include": ["test"],
4 | "references": [
5 | { "path": "tsconfig.src.json" }
6 | ],
7 | "compilerOptions": {
8 | "types": ["node"],
9 | "tsBuildInfoFile": ".tsbuildinfo/test.tsbuildinfo",
10 | "rootDir": "test",
11 | "noEmit": true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import tailwindcss from "@tailwindcss/vite";
2 | import react from "@vitejs/plugin-react-swc";
3 | import { defineConfig } from "vite";
4 |
5 | import path from "node:path";
6 |
7 | // https://vite.dev/config/
8 | export default defineConfig({
9 | plugins: [react(), tailwindcss()],
10 | resolve: {
11 | alias: {
12 | src: path.resolve(__dirname, "./src"),
13 | "@": path.resolve(__dirname, "./src"),
14 | },
15 | },
16 | });
17 |
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import path from "node:path";
2 | import { defineConfig } from "vitest/config";
3 |
4 | export default defineConfig({
5 | plugins: [],
6 | test: {
7 | setupFiles: [path.join(__dirname, "setupTests.ts")],
8 | include: ["./src/tests/vitest/**/*.test.ts"],
9 | globals: true,
10 | coverage: {
11 | provider: "v8",
12 | },
13 | },
14 | resolve: {
15 | alias: {
16 | "@template/basic/test": path.join(__dirname, "test"),
17 | "@template/basic": path.join(__dirname, "src"),
18 | src: path.resolve(__dirname, "./src"),
19 | "@": path.resolve(__dirname, "./src"),
20 | },
21 | },
22 | });
23 |
--------------------------------------------------------------------------------