From c846a5549bcc6b15040a9328587e4497f5b8972a Mon Sep 17 00:00:00 2001 From: s-kybound Date: Tue, 9 Apr 2024 17:11:20 +0800 Subject: [PATCH 01/22] add node workflow --- .github/workflows/nodejs.yml | 41 ++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/nodejs.yml diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml new file mode 100644 index 0000000..244d98f --- /dev/null +++ b/.github/workflows/nodejs.yml @@ -0,0 +1,41 @@ +# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions + +name: Node.js CI + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Check for package-lock.json + run: | + if [ -e package-lock.json ]; then + echo "package-lock.json found; please do not use NPM! This project uses Yarn!" + exit 1 + fi + exit 0 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: yarn + - run: yarn install + - run: yarn build + - run: yarn format + - run: yarn eslint + - run: yarn test + - run: yarn test-coverage + env: + CI: true + - name: Coveralls + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file From 1b28b0df757bdde515f27b013100af8438f76216 Mon Sep 17 00:00:00 2001 From: s-kybound Date: Tue, 9 Apr 2024 17:16:35 +0800 Subject: [PATCH 02/22] update to workflow --- .github/workflows/nodejs.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 244d98f..dadf47b 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -22,6 +22,9 @@ jobs: exit 1 fi exit 0 + - name: Install dependencies (apt) + run: | + sudo apt-get update - name: Setup Node uses: actions/setup-node@v4 with: From 44c81bf1aca03b3ed0b1a690270be1d4495993a4 Mon Sep 17 00:00:00 2001 From: s-kybound Date: Tue, 9 Apr 2024 17:18:20 +0800 Subject: [PATCH 03/22] add dependencies --- .github/workflows/nodejs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index dadf47b..f868fd2 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -30,7 +30,7 @@ jobs: with: node-version: 20 cache: yarn - - run: yarn install + - run: yarn install --frozen-lockfile - run: yarn build - run: yarn format - run: yarn eslint From fd5f58e54ae0a7adee4a72801e1ae5ff644c43b2 Mon Sep 17 00:00:00 2001 From: s-kybound Date: Tue, 9 Apr 2024 17:22:21 +0800 Subject: [PATCH 04/22] try to update dependabot --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 9e7408b..fe3de70 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,7 +5,7 @@ version: 2 updates: - - package-ecosystem: npm # See documentation for possible values + - package-ecosystem: "yarn" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "weekly" From fa2e54cd0112cd64bbeb229e3d5d3f40afcfc7b4 Mon Sep 17 00:00:00 2001 From: s-kybound Date: Tue, 9 Apr 2024 17:27:25 +0800 Subject: [PATCH 05/22] hopefully this fixes the problem --- .github/workflows/nodejs.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index f868fd2..e4b533f 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -15,6 +15,8 @@ jobs: build: runs-on: ubuntu-latest steps: + - name: Checkout repository + uses: actions/checkout@v4 - name: Check for package-lock.json run: | if [ -e package-lock.json ]; then @@ -30,7 +32,7 @@ jobs: with: node-version: 20 cache: yarn - - run: yarn install --frozen-lockfile + - run: yarn install - run: yarn build - run: yarn format - run: yarn eslint From dd20509a972919dcd2e6f9f085d549b3c23aaec6 Mon Sep 17 00:00:00 2001 From: s-kybound Date: Tue, 9 Apr 2024 17:29:53 +0800 Subject: [PATCH 06/22] update workflow - remove build --- .github/workflows/nodejs.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index e4b533f..6433ece 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -33,7 +33,6 @@ jobs: node-version: 20 cache: yarn - run: yarn install - - run: yarn build - run: yarn format - run: yarn eslint - run: yarn test From 6b198c547a56683a088fc3ae94bade95754f5932 Mon Sep 17 00:00:00 2001 From: s-kybound Date: Tue, 9 Apr 2024 17:34:27 +0800 Subject: [PATCH 07/22] update yarn commands --- .github/workflows/nodejs.yml | 2 +- package.json | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 6433ece..2ede34a 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -33,7 +33,7 @@ jobs: node-version: 20 cache: yarn - run: yarn install - - run: yarn format + - run: yarn format:ci - run: yarn eslint - run: yarn test - run: yarn test-coverage diff --git a/package.json b/package.json index 098a522..3be85aa 100644 --- a/package.json +++ b/package.json @@ -33,8 +33,9 @@ "scripts": { "build-libs": "npx ts-node ./src/compile-libs.ts", "test": "jest", - "lint": "eslint --ignore-path .eslintignore --ext .ts", - "format": "prettier --ignore-path .gitignore --write \"**/*.+(js|ts|json)\"" + "eslint": "eslint --ignore-path .eslintignore --ext .ts", + "format": "prettier --ignore-path .gitignore --write \"**/*.+(js|ts|json)\"", + "format:ci": "prettier --ignore-path .gitignore --list-different \"**/*.+(js|ts|json)\"" }, "meta": {}, "devDependencies": { From 8e997b53119e04841ac655b795654153a4d6b563 Mon Sep 17 00:00:00 2001 From: s-kybound Date: Tue, 9 Apr 2024 17:36:03 +0800 Subject: [PATCH 08/22] add coverage test --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 3be85aa..0a5f9ff 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "scripts": { "build-libs": "npx ts-node ./src/compile-libs.ts", "test": "jest", + "test-coverage": "jest --coverage", "eslint": "eslint --ignore-path .eslintignore --ext .ts", "format": "prettier --ignore-path .gitignore --write \"**/*.+(js|ts|json)\"", "format:ci": "prettier --ignore-path .gitignore --list-different \"**/*.+(js|ts|json)\"" From 0bcb5fa2a074ab9b7009ff98aa8d1cda2ab9a818 Mon Sep 17 00:00:00 2001 From: Kyriel Abad Date: Tue, 9 Apr 2024 17:43:24 +0800 Subject: [PATCH 09/22] Enable CI and pre push and commit hooks (#20) * add experimental reverse parser * add decoder to scm-slang * export function * resolve typing error * remove redundant import * add reverse parser import path * make pairs distinguishable from arrays * add husky for quality checking * implement equals method for nodes * enhance the tests for the scheme parser --- .husky/pre-commit | 1 + .husky/pre-push | 1 + package.json | 4 +- src/stdlib/base.ts | 3 +- src/transpiler/__tests__/schemeParse.ts | 2 +- src/transpiler/index.ts | 8 +- .../parser/__tests__/scheme-parser.ts | 789 ++++++++++++++---- .../types/nodes/scheme-node-types.ts | 266 ++++++ src/utils/reverse_parser.ts | 18 +- yarn.lock | 5 + 10 files changed, 911 insertions(+), 186 deletions(-) create mode 100644 .husky/pre-commit create mode 100644 .husky/pre-push diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..a2cdad5 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +yarn format && yarn eslint diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100644 index 0000000..899eef8 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1 @@ +yarn format && yarn eslint && yarn test diff --git a/package.json b/package.json index 0a5f9ff..ffed1eb 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,8 @@ "test-coverage": "jest --coverage", "eslint": "eslint --ignore-path .eslintignore --ext .ts", "format": "prettier --ignore-path .gitignore --write \"**/*.+(js|ts|json)\"", - "format:ci": "prettier --ignore-path .gitignore --list-different \"**/*.+(js|ts|json)\"" + "format:ci": "prettier --ignore-path .gitignore --list-different \"**/*.+(js|ts|json)\"", + "prepare": "husky" }, "meta": {}, "devDependencies": { @@ -51,6 +52,7 @@ "eslint-plugin-import": "^2.25.2", "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", "eslint-plugin-promise": "^6.0.0", + "husky": "^9.0.11", "jest": "^29.7.0", "prettier": "^3.2.4", "source-map": "^0.7.4", diff --git a/src/stdlib/base.ts b/src/stdlib/base.ts index df6c170..ff804e4 100644 --- a/src/stdlib/base.ts +++ b/src/stdlib/base.ts @@ -253,7 +253,8 @@ const array_test: Function = (p: any) => { } return Array.isArray(p); }; -export const pair$63$: Function = (p: any) => array_test(p) && p.length === 2 && p.pair === true; +export const pair$63$: Function = (p: any) => + array_test(p) && p.length === 2 && p.pair === true; export const not$45$pair$63$: Function = compose(not, pair$63$); export const set$45$car$33$: Function = (p: core.Pair | core.List, v: any) => { if (pair$63$(p)) { diff --git a/src/transpiler/__tests__/schemeParse.ts b/src/transpiler/__tests__/schemeParse.ts index c67ba08..30559e1 100644 --- a/src/transpiler/__tests__/schemeParse.ts +++ b/src/transpiler/__tests__/schemeParse.ts @@ -9,4 +9,4 @@ test("schemeParse", () => { `; const program = schemeParse(source); expect(program).toMatchSnapshot(); -}); \ No newline at end of file +}); diff --git a/src/transpiler/index.ts b/src/transpiler/index.ts index 07b6f83..0a22fe2 100644 --- a/src/transpiler/index.ts +++ b/src/transpiler/index.ts @@ -20,7 +20,11 @@ export { ParserError } from "./parser"; * If not provided, defaults to the latest version. * @returns */ -export function schemeParse(source: string, chapter?: number, encode?: boolean): Program { +export function schemeParse( + source: string, + chapter?: number, + encode?: boolean, +): Program { // Instantiate the lexer const lexer = new SchemeLexer(source); @@ -51,5 +55,5 @@ export function schemeParse(source: string, chapter?: number, encode?: boolean): // Finally we transpile the AST const program: Program = transpiler.transpile(redefinedAST); - return encode ? estreeEncode(program) as Program: program; + return encode ? (estreeEncode(program) as Program) : program; } diff --git a/src/transpiler/parser/__tests__/scheme-parser.ts b/src/transpiler/parser/__tests__/scheme-parser.ts index 853fc77..e0a7a62 100644 --- a/src/transpiler/parser/__tests__/scheme-parser.ts +++ b/src/transpiler/parser/__tests__/scheme-parser.ts @@ -1,6 +1,11 @@ import { SchemeParser } from "../scheme-parser"; import { SchemeLexer } from "../../lexer"; -import { Expression } from "../../types/nodes/scheme-node-types"; +import { + Atomic, + Extended, + Expression, +} from "../../types/nodes/scheme-node-types"; +import { Location, Position } from "../../types/location"; // Unfortunately, we are currently unable to test the parser in isolation from the // lexer, as the parser depends on the lexer to generate tokens. Generating the tokens @@ -8,206 +13,646 @@ import { Expression } from "../../types/nodes/scheme-node-types"; // As a result, we will have to test the parser in conjunction with the lexer. // We will avoid testing the more advanced features of the lexer here. -function parse(input: string, chapter: number = Infinity): Expression[] { - const lexer = new SchemeLexer(input); - const parser = new SchemeParser(input, lexer.scanTokens(), chapter); - return parser.parse(); -} +const dummyLocation = new Location(new Position(0, 0), new Position(0, 0)); -// Additionally, with the way the parser is currently implemented, it is difficult to -// test for equality of the parsed expressions. Unfortunately, we will currently test for the -// LACK of erroneous behavior, rather than the presence of correct behavior. -test("does not throw on parsing empty program", () => { - expect(() => parse("")).not.toThrow(); -}); - -test("does not throw on parsing literals", () => { - expect(() => parse("1")).not.toThrow(); - expect(() => parse("1.0")).not.toThrow(); - expect(() => parse('"hello"')).not.toThrow(); - expect(() => parse("#t")).not.toThrow(); - expect(() => parse("#f")).not.toThrow(); -}); - -test("does not throw on parsing identifiers", () => { - expect(() => parse("hello")).not.toThrow(); - expect(() => parse("hello-world")).not.toThrow(); - expect(() => parse("hello?")).not.toThrow(); - expect(() => parse("hello-world!")).not.toThrow(); -}); +// helper functions that will make creation of nodes a lot easier +function numericLiteral(value: string) { + return new Atomic.NumericLiteral(dummyLocation, value); +} -test("does not throw on parsing normal lambda functions", () => { - expect(() => parse("(lambda (x) x)")).not.toThrow(); - expect(() => parse("(lambda (x y) x)")).not.toThrow(); - expect(() => parse("(lambda (x y z) x)")).not.toThrow(); -}); +function booleanLiteral(value: boolean) { + return new Atomic.BooleanLiteral(dummyLocation, value); +} -test("does not throw on parsing variadic lambda functions", () => { - expect(() => parse("(lambda x x)")).not.toThrow(); - expect(() => parse("(lambda (x . y) x)")).not.toThrow(); - expect(() => parse("(lambda (x y . z) x)")).not.toThrow(); -}); +function stringLiteral(value: string) { + return new Atomic.StringLiteral(dummyLocation, value); +} -test("does not throw on parsing functions with body", () => { - expect(() => parse("(lambda (x) x x)")).not.toThrow(); - expect(() => parse("(lambda (x y) x y)")).not.toThrow(); - expect(() => parse("(lambda (x y z) x y z)")).not.toThrow(); -}); +function identifier(value: string) { + return new Atomic.Identifier(dummyLocation, value); +} -test("does not throw on parsing definitions", () => { - expect(() => parse("(define x 1)")).not.toThrow(); - expect(() => parse("(define x 1.0)")).not.toThrow(); - expect(() => parse('(define x "hello")')).not.toThrow(); - expect(() => parse("(define x #t)")).not.toThrow(); - expect(() => parse("(define x #f)")).not.toThrow(); - expect(() => parse("(define x (lambda (x) x))")).not.toThrow(); -}); +function lambda( + body: Expression, + params: Atomic.Identifier[], + rest?: Atomic.Identifier, +) { + return new Atomic.Lambda(dummyLocation, body, params, rest); +} -test("does not throw on parsing function definitions", () => { - expect(() => parse("(define (f x) x)")).not.toThrow(); - expect(() => parse("(define (f x y) x y)")).not.toThrow(); - expect(() => parse("(define (f x y z) x y z)")).not.toThrow(); -}); +function sequence(...expressions: Expression[]) { + return new Atomic.Sequence(dummyLocation, expressions); +} -test("does not throw on parsing function applications", () => { - expect(() => parse("(f)")).not.toThrow(); - expect(() => parse("(f x)")).not.toThrow(); - expect(() => parse("(f x y)")).not.toThrow(); - expect(() => parse("(f x y z)")).not.toThrow(); -}); +function definition(name: Atomic.Identifier, value: Expression) { + return new Atomic.Definition(dummyLocation, name, value); +} -test("does not throw on parsing conditionals", () => { - expect(() => parse("(if #t 1 2)")).not.toThrow(); - expect(() => parse("(if #f 1 2)")).not.toThrow(); - expect(() => parse("(if #t 1)")).not.toThrow(); - expect(() => parse("(if #f 1)")).not.toThrow(); -}); +function functionDefinition( + name: Atomic.Identifier, + body: Expression, + params: Atomic.Identifier[], + rest?: Atomic.Identifier, +) { + return new Extended.FunctionDefinition( + dummyLocation, + name, + body, + params, + rest, + ); +} -test("does not throw on parsing quoted literals and identifiers", () => { - expect(() => parse("'1")).not.toThrow(); - expect(() => parse("'1.0")).not.toThrow(); - expect(() => parse('\'"hello"')).not.toThrow(); - expect(() => parse("'hello")).not.toThrow(); -}); +function application(operator: Expression, operands: Expression[]) { + return new Atomic.Application(dummyLocation, operator, operands); +} -test("does not throw on quoted vectors", () => { - expect(() => parse("'#(1 2 3)")).not.toThrow(); - expect(() => parse("'#(1 2 3 4)")).not.toThrow(); - expect(() => parse("'#(1 testy 3 4 5)")).not.toThrow(); -}); +function conditional( + test: Expression, + consequent: Expression, + alternate?: Expression, +) { + return new Atomic.Conditional( + dummyLocation, + test, + consequent, + alternate ? alternate : identifier("undefined"), + ); +} -test("does not throw on parsing quotations in lists", () => { - expect(() => parse("'(1 2 3)")).not.toThrow(); - expect(() => parse('\'("1" (2 3) 4)')).not.toThrow(); - expect(() => parse("'(1 (2 (3 will-this be a symbol)) 5)")).not.toThrow(); -}); +function symbol(value: string) { + return new Atomic.Symbol(dummyLocation, value); +} -test("does not throw when evaluating the empty list", () => { - expect(() => parse("'()")).not.toThrow(); -}); +function vector(...elements: Expression[]) { + return new Atomic.Vector(dummyLocation, elements); +} -test("does not throw on parsing dotted lists", () => { - expect(() => parse("'(1 . 2)")).not.toThrow(); - expect(() => parse("'(1 2 . 3)")).not.toThrow(); - expect(() => parse("'(1 2 3 . 4)")).not.toThrow(); -}); +function list(...elements: Expression[]) { + return new Extended.List(dummyLocation, elements); +} -test("does not throw on parsing nested dotted lists", () => { - expect(() => parse("'(1 . (2 . 3))")).not.toThrow(); - expect(() => parse("'(1 2 . (3 . 4))")).not.toThrow(); - expect(() => parse("'((1 2 . 3) 2 3 . (4 . 5))")).not.toThrow(); -}); +function dottedList(elements: Expression[], rest: Expression) { + return new Extended.List(dummyLocation, elements, rest); +} -test("does not throw on parsing nested lists", () => { - expect(() => parse("'(1 (2 3) 4)")).not.toThrow(); - expect(() => parse("'(1 (2 (3 4)) 5)")).not.toThrow(); - expect(() => parse("'((1 2 (3 4)) 2 3 (4 5))")).not.toThrow(); -}); +function nil() { + return new Atomic.Nil(dummyLocation); +} -test("does not throw on parsing quasiquoted lists", () => { - expect(() => parse("`(1 2 3)")).not.toThrow(); - expect(() => parse("`(1 (2 ,(+ 1 2)) 4)")).not.toThrow(); - expect(() => parse("`(1 (,a (3 4)) 5)")).not.toThrow(); -}); +function reassignment(name: Atomic.Identifier, value: Expression) { + return new Atomic.Reassignment(dummyLocation, name, value); +} -/* -test("does not throw on parsing quasiquoted structures with unquote-splicing", () => { - expect(() => parse("`(1 2 ,@a 4)")).not.toThrow(); - expect(() => parse("`(1 (2 ,@a) 5)")).not.toThrow(); - expect(() => parse("`(1 ,@(list 1 2 3) 3 4)")).not.toThrow(); -}); - */ +function importNode(from: Atomic.StringLiteral, imports: Atomic.Identifier[]) { + return new Atomic.Import(dummyLocation, from, imports); +} -test("does not throw on parsing reassignments", () => { - expect(() => parse("(set! x 1)")).not.toThrow(); - expect(() => parse("(set! x 1.0)")).not.toThrow(); - expect(() => parse('(set! x "hello")')).not.toThrow(); - expect(() => parse("(set! x #t)")).not.toThrow(); - expect(() => parse("(set! x #f)")).not.toThrow(); - expect(() => parse("(set! x (lambda (x) x))")).not.toThrow(); -}); +function exportNode( + definition: Atomic.Definition | Extended.FunctionDefinition, +) { + return new Atomic.Export(dummyLocation, definition); +} -test("does not throw on nested reassignments", () => { - expect(() => parse("(set! x (set! y 1))")).not.toThrow(); - expect(() => parse("(set! x (set! y 1.0))")).not.toThrow(); - expect(() => parse('(set! x (set! y "hello"))')).not.toThrow(); - expect(() => parse("(set! x (set! y #t))")).not.toThrow(); - expect(() => parse("(set! x (set! y #f))")).not.toThrow(); - expect(() => parse("(set! x (set! y (lambda (x) x)))")).not.toThrow(); -}); +function letNode( + identifiers: Atomic.Identifier[], + values: Expression[], + body: Expression, +) { + return new Extended.Let(dummyLocation, identifiers, values, body); +} -test("does not throw on parsing import statements", () => { - expect(() => parse('(import "path/to/file" (a b c d))')).not.toThrow(); -}); +function cond( + predicates: Expression[], + consequents: Expression[], + elseClause?: Expression, +) { + return new Extended.Cond(dummyLocation, predicates, consequents, elseClause); +} -test("does not throw on parsing export statements", () => { - expect(() => parse("(export (define a 1))")).not.toThrow(); -}); +function begin(...expressions: Expression[]) { + return new Extended.Begin(dummyLocation, expressions); +} -test("does not throw on parsing vector literals", () => { - expect(() => parse("#(1 2 3)")).not.toThrow(); - expect(() => parse("#(1 2 3 4)")).not.toThrow(); - expect(() => parse("#(1 testy 3 4 5)")).not.toThrow(); -}); +function delay(body: Expression) { + return new Extended.Delay(dummyLocation, body); +} -test("does not throw on parsing vector literals with nested lists", () => { - expect(() => parse("#(1 (2 3) 4)")).not.toThrow(); - expect(() => parse("#(1 (2 (3 4)) 5)")).not.toThrow(); - expect(() => parse("#((1 2 (3 4)) 2 3 (4 5))")).not.toThrow(); -}); +// helper functions to help make testing the parser easier +function parse(input: string, chapter: number = Infinity): Expression[] { + const lexer = new SchemeLexer(input); + const parser = new SchemeParser(input, lexer.scanTokens(), chapter); + return parser.parse(); +} -test("does not throw on parsing let expressions", () => { - expect(() => parse("(let () (= 1 1))")).not.toThrow(); - expect(() => parse("(let ((x 1)) x)")).not.toThrow(); - expect(() => parse("(let ((x 1) (y 2)) x y)")).not.toThrow(); - expect(() => parse("(let ((x 1) (y 2) (z 3)) x y z)")).not.toThrow(); -}); +function parseFirst(input: string, chapter: number = Infinity): Expression { + return parse(input, chapter)[0]; +} -test("does not throw on parsing cond expressions", () => { - expect(() => parse("(cond (else 1))")).not.toThrow(); - expect(() => parse("(cond ((foo) (bar) (baz)))")).not.toThrow(); - expect(() => - parse("(cond ((= 1 1) 1 ) ((= 2 2) 2 2) (else 3))"), - ).not.toThrow(); +test("parsing empty program returns nothing", () => { + expect(parse("")).toEqual([]); +}); + +test("parses literals", () => { + expect(parseFirst("1").equals(numericLiteral("1"))).toEqual(true); + expect(parseFirst("1.0").equals(numericLiteral("1.0"))).toEqual(true); + expect(parseFirst('"hello"').equals(stringLiteral("hello"))).toEqual(true); + expect(parseFirst("#t").equals(booleanLiteral(true))).toEqual(true); + expect(parseFirst("#f").equals(booleanLiteral(false))).toEqual(true); +}); + +test("parses identifiers", () => { + expect(parseFirst("hello").equals(identifier("hello"))).toEqual(true); + expect(parseFirst("hello-world").equals(identifier("hello-world"))).toEqual( + true, + ); + expect(parseFirst("hello?").equals(identifier("hello?"))).toEqual(true); + expect(parseFirst("hello-world!").equals(identifier("hello-world!"))).toEqual( + true, + ); +}); + +test("parses lambda functions", () => { + expect( + parseFirst("(lambda () 1)").equals(lambda(numericLiteral("1"), [])), + ).toEqual(true); + expect( + parseFirst("(lambda (x) x)").equals( + lambda(identifier("x"), [identifier("x")]), + ), + ).toEqual(true); + expect( + parseFirst("(lambda (x y) x)").equals( + lambda(identifier("x"), [identifier("x"), identifier("y")]), + ), + ).toEqual(true); + expect( + parseFirst("(lambda (x y z) x)").equals( + lambda(identifier("x"), [ + identifier("x"), + identifier("y"), + identifier("z"), + ]), + ), + ).toEqual(true); +}); + +test("parses variadic lambda functions", () => { + expect( + parseFirst("(lambda x x)").equals( + lambda(identifier("x"), [], identifier("x")), + ), + ).toEqual(true); + expect( + parseFirst("(lambda (x . y) x)").equals( + lambda(identifier("x"), [identifier("x")], identifier("y")), + ), + ).toEqual(true); + expect( + parseFirst("(lambda (x y . z) x)").equals( + lambda( + identifier("x"), + [identifier("x"), identifier("y")], + identifier("z"), + ), + ), + ).toEqual(true); +}); + +test("parses functions with body", () => { + expect( + parseFirst("(lambda () x x)").equals( + lambda(sequence(identifier("x"), identifier("x")), []), + ), + ).toEqual(true); +}); + +test("parses definitions", () => { + expect( + parseFirst("(define x 1)").equals( + definition(identifier("x"), numericLiteral("1")), + ), + ).toEqual(true); + expect( + parseFirst("(define x 1.0)").equals( + definition(identifier("x"), numericLiteral("1.0")), + ), + ).toEqual(true); + expect( + parseFirst('(define x "hello")').equals( + definition(identifier("x"), stringLiteral("hello")), + ), + ).toEqual(true); + expect( + parseFirst("(define x #t)").equals( + definition(identifier("x"), booleanLiteral(true)), + ), + ).toEqual(true); + expect( + parseFirst("(define x #f)").equals( + definition(identifier("x"), booleanLiteral(false)), + ), + ).toEqual(true); + expect( + parseFirst("(define x (lambda (x) x))").equals( + definition(identifier("x"), lambda(identifier("x"), [identifier("x")])), + ), + ).toEqual(true); +}); + +test("parses function definitions", () => { + expect( + parseFirst("(define (f x) x)").equals( + functionDefinition(identifier("f"), identifier("x"), [identifier("x")]), + ), + ).toEqual(true); + expect( + parseFirst("(define (f x y) x y)").equals( + functionDefinition( + identifier("f"), + sequence(identifier("x"), identifier("y")), + [identifier("x"), identifier("y")], + ), + ), + ).toEqual(true); +}); + +test("parses applications", () => { + expect(parseFirst("(f)").equals(application(identifier("f"), []))).toEqual( + true, + ); + expect( + parseFirst("(f x)").equals(application(identifier("f"), [identifier("x")])), + ).toEqual(true); + expect( + parseFirst("(f x y)").equals( + application(identifier("f"), [identifier("x"), identifier("y")]), + ), + ).toEqual(true); + expect( + parseFirst("((lambda (x) x) 1)").equals( + application(lambda(identifier("x"), [identifier("x")]), [ + numericLiteral("1"), + ]), + ), + ).toEqual(true); +}); + +test("parses conditionals", () => { + expect( + parseFirst("(if #t 1 2)").equals( + conditional( + booleanLiteral(true), + numericLiteral("1"), + numericLiteral("2"), + ), + ), + ).toEqual(true); + expect( + parseFirst("(if #f 1)").equals( + conditional(booleanLiteral(false), numericLiteral("1")), + ), + ).toEqual(true); +}); + +test("parses quoted literals and identifiers", () => { + expect(parseFirst("'1").equals(numericLiteral("1"))).toEqual(true); + expect(parseFirst("'1.0").equals(numericLiteral("1.0"))).toEqual(true); + expect(parseFirst('\'"hello"').equals(stringLiteral("hello"))).toEqual(true); + expect(parseFirst("'hello").equals(symbol("hello"))).toEqual(true); +}); + +test("parses quoted vectors (everything inside should be quoted)", () => { + expect( + parseFirst("'#(1 2 3)").equals( + vector(numericLiteral("1"), numericLiteral("2"), numericLiteral("3")), + ), + ).toEqual(true); + expect( + parseFirst("'#(1 2 3 4)").equals( + vector( + numericLiteral("1"), + numericLiteral("2"), + numericLiteral("3"), + numericLiteral("4"), + ), + ), + ).toEqual(true); + expect( + parseFirst("'#(1 testy 3 4 5)").equals( + vector( + numericLiteral("1"), + symbol("testy"), + numericLiteral("3"), + numericLiteral("4"), + numericLiteral("5"), + ), + ), + ).toEqual(true); +}); + +test("parses quotations in lists", () => { + expect( + parseFirst("'(1 2 3)").equals( + list(numericLiteral("1"), numericLiteral("2"), numericLiteral("3")), + ), + ).toEqual(true); + expect( + parseFirst('\'("1" (2 3) 4)').equals( + list( + stringLiteral("1"), + list(numericLiteral("2"), numericLiteral("3")), + numericLiteral("4"), + ), + ), + ).toEqual(true); + expect( + parseFirst("'(1 (2 (3 will-this be a symbol)) 5)").equals( + list( + numericLiteral("1"), + list( + numericLiteral("2"), + list( + numericLiteral("3"), + symbol("will-this"), + symbol("be"), + symbol("a"), + symbol("symbol"), + ), + ), + numericLiteral("5"), + ), + ), + ).toEqual(true); +}); + +test("parses the empty list", () => { + expect(parseFirst("'()").equals(nil())).toEqual(true); +}); + +test("parses dotted lists", () => { + expect( + parseFirst("'(1 . 2)").equals( + dottedList([numericLiteral("1")], numericLiteral("2")), + ), + ).toEqual(true); + expect( + parseFirst("'(1 2 . 3)").equals( + dottedList( + [numericLiteral("1"), numericLiteral("2")], + numericLiteral("3"), + ), + ), + ).toEqual(true); + expect( + parseFirst("'((1 2 . 3) 2 3 . 4)").equals( + dottedList( + [ + dottedList( + [numericLiteral("1"), numericLiteral("2")], + numericLiteral("3"), + ), + numericLiteral("2"), + numericLiteral("3"), + ], + numericLiteral("4"), + ), + ), + ).toEqual(true); +}); + +test("parses nested lists", () => { + expect( + parseFirst("'(1 (2 3) 4)").equals( + list( + numericLiteral("1"), + list(numericLiteral("2"), numericLiteral("3")), + numericLiteral("4"), + ), + ), + ).toEqual(true); +}); + +test("parses quasiquoted lists", () => { + expect( + parseFirst("`(1 2 3)").equals( + list(numericLiteral("1"), numericLiteral("2"), numericLiteral("3")), + ), + ).toEqual(true); + // the application is unquoted! + expect( + parseFirst("`(1 (2 ,(+ 1 2)) 4)").equals( + list( + numericLiteral("1"), + list( + numericLiteral("2"), + application(identifier("+"), [ + numericLiteral("1"), + numericLiteral("2"), + ]), + ), + numericLiteral("4"), + ), + ), + ).toEqual(true); }); -test("does not throw on parsing begin expressions", () => { - expect(() => parse("(begin 1)")).not.toThrow(); - expect(() => parse("(begin 1 2)")).not.toThrow(); - expect(() => parse("(begin 1 2 3)")).not.toThrow(); -}); +/* +quasiquotation will be left for semester 1 ay2425 -test("does not throw on parsing delay expressions", () => { - expect(() => parse("(delay 1)")).not.toThrow(); - expect(() => parse("(delay (+ 1 2))")).not.toThrow(); - expect(() => parse("(delay (lambda (x) x))")).not.toThrow(); +test("does not throw on parsing quasiquoted structures with unquote-splicing", () => { + expect(() => parse("`(1 2 ,@a 4)")).not.toThrow(); + expect(() => parse("`(1 (2 ,@a) 5)")).not.toThrow(); + expect(() => parse("`(1 ,@(list 1 2 3) 3 4)")).not.toThrow(); }); - -test("does not throw on parsing datum comments", () => { - expect(() => parse("#; (this-should-be-ignored)")).not.toThrow(); - expect(() => - parse("#; (this-should-be-ignored) (but-this-should-not)"), - ).not.toThrow(); +*/ + +test("should throw on a unquote without an external quote", () => { + expect(() => parse(",1")).toThrow(); +}); + +test("parses reassignments", () => { + expect( + parseFirst("(set! x 1)").equals( + reassignment(identifier("x"), numericLiteral("1")), + ), + ).toEqual(true); +}); + +// the return value of reassignment is UNSPECIFIED in R7RS. +// for scm-slang, we choose to have it emit the value of the assignment, +// similar to javascript. +test("parses nested reassignments", () => { + expect( + parseFirst("(set! x (set! y 1))").equals( + reassignment( + identifier("x"), + reassignment(identifier("y"), numericLiteral("1")), + ), + ), + ).toEqual(true); +}); + +test("parses import statements", () => { + expect( + parseFirst('(import "path" (a b c d))').equals( + importNode(stringLiteral("path"), [ + identifier("a"), + identifier("b"), + identifier("c"), + identifier("d"), + ]), + ), + ).toEqual(true); +}); + +test("parses export statements", () => { + expect( + parseFirst("(export (define a 1))").equals( + exportNode(definition(identifier("a"), numericLiteral("1"))), + ), + ).toEqual(true); +}); + +test("parses vector literals (which are equal to their quoted equivalents - everything is quoted)", () => { + expect(parseFirst("#(1 2 3)").equals(parseFirst("'#(1 2 3)"))).toEqual(true); + expect( + parseFirst("#(1 testy 3 4 5)").equals(parseFirst("'#(1 testy 3 4 5)")), + ).toEqual(true); +}); + +test("parses vector literals with nested lists", () => { + // observe - (testy) is not treated as an application, but a list + expect( + parseFirst("#(1 (testy) 4)").equals( + vector(numericLiteral("1"), list(symbol("testy")), numericLiteral("4")), + ), + ).toEqual(true); +}); + +test("parses let expressions", () => { + expect( + parseFirst("(let () 1)").equals(letNode([], [], numericLiteral("1"))), + ).toEqual(true); + expect( + parseFirst("(let ((x 3)) (= x 1))").equals( + letNode( + [identifier("x")], + [numericLiteral("3")], + application(identifier("="), [identifier("x"), numericLiteral("1")]), + ), + ), + ).toEqual(true); +}); + +test("parses cond expressions", () => { + expect( + parseFirst("(cond ((= 1 1) 1))").equals( + cond( + [ + application(identifier("="), [ + numericLiteral("1"), + numericLiteral("1"), + ]), + ], + [numericLiteral("1")], + ), + ), + ).toEqual(true); + expect( + parseFirst("(cond (else 1))").equals(cond([], [], numericLiteral("1"))), + ).toEqual(true); + + // more than one "return value" becomes a sequence + expect( + parseFirst("(cond ((foo) (bar) (baz)))").equals( + cond( + [application(identifier("foo"), [])], + [ + sequence( + application(identifier("bar"), []), + application(identifier("baz"), []), + ), + ], + ), + ), + ).toEqual(true); + + // testing all features + expect( + parseFirst("(cond ((= 1 1) 1 ) ((= 2 2) 2 2) (else 3))").equals( + cond( + [ + application(identifier("="), [ + numericLiteral("1"), + numericLiteral("1"), + ]), + application(identifier("="), [ + numericLiteral("2"), + numericLiteral("2"), + ]), + ], + [ + numericLiteral("1"), + sequence(numericLiteral("2"), numericLiteral("2")), + ], + numericLiteral("3"), + ), + ), + ).toEqual(true); +}); + +test("parses begin expressions", () => { + expect(parseFirst("(begin 1)").equals(begin(numericLiteral("1")))).toEqual( + true, + ); + expect( + parseFirst("(begin 1 2)").equals( + begin(numericLiteral("1"), numericLiteral("2")), + ), + ).toEqual(true); +}); + +test("parses delay expressions", () => { + expect(parseFirst("(delay 1)").equals(delay(numericLiteral("1")))).toEqual( + true, + ); + expect( + parseFirst("(delay (begin 1 2))").equals( + delay(begin(numericLiteral("1"), numericLiteral("2"))), + ), + ).toEqual(true); +}); + +test("ignores datum comments", () => { + // an empty program + expect(parse("#; (this-should-be-ignored)")).toHaveLength(0); + + // only (but-this-should-not) should be parsed + expect( + parseFirst("#; (this-should-be-ignored) (but-this-should-not)").equals( + application(identifier("but-this-should-not"), []), + ), + ).toEqual(true); +}); + +test("ignores line comments", () => { + expect( + parseFirst("; this is a comment\n1").equals(numericLiteral("1")), + ).toEqual(true); +}); + +test("ignores block comments", () => { + expect( + parseFirst(` + (you-won't-see-2 1 #| 2 |# 3) + `).equals( + application(identifier("you-won't-see-2"), [ + numericLiteral("1"), + numericLiteral("3"), + ]), + ), + ).toEqual(true); }); test("able to parse a program with all features", () => { diff --git a/src/transpiler/types/nodes/scheme-node-types.ts b/src/transpiler/types/nodes/scheme-node-types.ts index d143498..b5a14cd 100644 --- a/src/transpiler/types/nodes/scheme-node-types.ts +++ b/src/transpiler/types/nodes/scheme-node-types.ts @@ -17,6 +17,7 @@ import { Location } from "../location"; export interface Expression { location: Location; accept(visitor: Visitor): any; + equals(other: Expression): boolean; } /** @@ -40,6 +41,20 @@ export namespace Atomic { accept(visitor: Visitor): any { return visitor.visitSequence(this); } + equals(other: Expression): boolean { + if (other instanceof Sequence) { + if (this.expressions.length !== other.expressions.length) { + return false; + } + for (let i = 0; i < this.expressions.length; i++) { + if (!this.expressions[i].equals(other.expressions[i])) { + return false; + } + } + return true; + } + return false; + } } /** @@ -63,6 +78,12 @@ export namespace Atomic { accept(visitor: Visitor): any { return visitor.visitNumericLiteral(this); } + equals(other: Expression): boolean { + if (other instanceof NumericLiteral) { + return this.value === other.value; + } + return false; + } } /** @@ -78,6 +99,12 @@ export namespace Atomic { accept(visitor: Visitor): any { return visitor.visitBooleanLiteral(this); } + equals(other: Expression): boolean { + if (other instanceof BooleanLiteral) { + return this.value === other.value; + } + return false; + } } /** @@ -93,6 +120,12 @@ export namespace Atomic { accept(visitor: Visitor): any { return visitor.visitStringLiteral(this); } + equals(other: Expression): boolean { + if (other instanceof StringLiteral) { + return this.value === other.value; + } + return false; + } } /** @@ -118,6 +151,27 @@ export namespace Atomic { accept(visitor: Visitor): any { return visitor.visitLambda(this); } + equals(other: Expression): boolean { + if (other instanceof Lambda) { + if (this.params.length !== other.params.length) { + return false; + } + for (let i = 0; i < this.params.length; i++) { + if (!this.params[i].equals(other.params[i])) { + return false; + } + } + if (this.rest && other.rest) { + if (!this.rest.equals(other.rest)) { + return false; + } + } else if (this.rest || other.rest) { + return false; + } + return this.body.equals(other.body); + } + return false; + } } /** @@ -133,6 +187,12 @@ export namespace Atomic { accept(visitor: Visitor): any { return visitor.visitIdentifier(this); } + equals(other: Expression): boolean { + if (other instanceof Identifier) { + return this.name === other.name; + } + return false; + } } /** @@ -151,6 +211,12 @@ export namespace Atomic { accept(visitor: Visitor): any { return visitor.visitDefinition(this); } + equals(other: Expression): boolean { + if (other instanceof Definition) { + return this.name.equals(other.name) && this.value.equals(other.value); + } + return false; + } } /** @@ -172,6 +238,23 @@ export namespace Atomic { accept(visitor: Visitor): any { return visitor.visitApplication(this); } + equals(other: Expression): boolean { + if (other instanceof Application) { + if (!this.operator.equals(other.operator)) { + return false; + } + if (this.operands.length !== other.operands.length) { + return false; + } + for (let i = 0; i < this.operands.length; i++) { + if (!this.operands[i].equals(other.operands[i])) { + return false; + } + } + return true; + } + return false; + } } /** @@ -196,6 +279,16 @@ export namespace Atomic { accept(visitor: Visitor): any { return visitor.visitConditional(this); } + equals(other: Expression): boolean { + if (other instanceof Conditional) { + return ( + this.test.equals(other.test) && + this.consequent.equals(other.consequent) && + this.alternate.equals(other.alternate) + ); + } + return false; + } } // Scheme chapter 2 @@ -215,6 +308,12 @@ export namespace Atomic { accept(visitor: Visitor): any { return visitor.visitPair(this); } + equals(other: Expression): boolean { + if (other instanceof Pair) { + return this.car.equals(other.car) && this.cdr.equals(other.cdr); + } + return false; + } } /** @@ -228,6 +327,9 @@ export namespace Atomic { accept(visitor: Visitor): any { return visitor.visitNil(this); } + equals(other: Expression): boolean { + return other instanceof Nil; + } } /** @@ -243,6 +345,12 @@ export namespace Atomic { accept(visitor: Visitor): any { return visitor.visitSymbol(this); } + equals(other: Expression): boolean { + if (other instanceof Symbol) { + return this.value === other.value; + } + return false; + } } /** @@ -259,6 +367,12 @@ export namespace Atomic { accept(visitor: Visitor): any { return visitor.visitSpliceMarker(this); } + equals(other: Expression): boolean { + if (other instanceof SpliceMarker) { + return this.value.equals(other.value); + } + return false; + } } // Scheme chapter 3 @@ -280,6 +394,12 @@ export namespace Atomic { accept(visitor: Visitor): any { return visitor.visitReassignment(this); } + equals(other: Expression): boolean { + if (other instanceof Reassignment) { + return this.name.equals(other.name) && this.value.equals(other.value); + } + return false; + } } // scm-slang specific @@ -305,6 +425,23 @@ export namespace Atomic { accept(visitor: Visitor): any { return visitor.visitImport(this); } + equals(other: Expression): boolean { + if (other instanceof Import) { + if (!this.source.equals(other.source)) { + return false; + } + if (this.identifiers.length !== other.identifiers.length) { + return false; + } + for (let i = 0; i < this.identifiers.length; i++) { + if (!this.identifiers[i].equals(other.identifiers[i])) { + return false; + } + } + return true; + } + return false; + } } /** @@ -325,6 +462,12 @@ export namespace Atomic { accept(visitor: Visitor): any { return visitor.visitExport(this); } + equals(other: Expression): boolean { + if (other instanceof Export) { + return this.definition.equals(other.definition); + } + return false; + } } /** @@ -340,6 +483,20 @@ export namespace Atomic { accept(visitor: Visitor): any { return visitor.visitVector(this); } + equals(other: Expression): boolean { + if (other instanceof Vector) { + if (this.elements.length !== other.elements.length) { + return false; + } + for (let i = 0; i < this.elements.length; i++) { + if (!this.elements[i].equals(other.elements[i])) { + return false; + } + } + return true; + } + return false; + } } } @@ -378,6 +535,27 @@ export namespace Extended { accept(visitor: Visitor): any { return visitor.visitFunctionDefinition(this); } + equals(other: Expression): boolean { + if (other instanceof FunctionDefinition) { + if (this.params.length !== other.params.length) { + return false; + } + for (let i = 0; i < this.params.length; i++) { + if (!this.params[i].equals(other.params[i])) { + return false; + } + } + if (this.rest && other.rest) { + if (!this.rest.equals(other.rest)) { + return false; + } + } else if (this.rest || other.rest) { + return false; + } + return this.body.equals(other.body); + } + return false; + } } /** @@ -402,6 +580,28 @@ export namespace Extended { accept(visitor: Visitor): any { return visitor.visitLet(this); } + equals(other: Expression): boolean { + if (other instanceof Let) { + if (this.identifiers.length !== other.identifiers.length) { + return false; + } + for (let i = 0; i < this.identifiers.length; i++) { + if (!this.identifiers[i].equals(other.identifiers[i])) { + return false; + } + } + if (this.values.length !== other.values.length) { + return false; + } + for (let i = 0; i < this.values.length; i++) { + if (!this.values[i].equals(other.values[i])) { + return false; + } + } + return this.body.equals(other.body); + } + return false; + } } /** @@ -427,6 +627,33 @@ export namespace Extended { accept(visitor: Visitor): any { return visitor.visitCond(this); } + equals(other: Expression): boolean { + if (other instanceof Cond) { + if (this.predicates.length !== other.predicates.length) { + return false; + } + for (let i = 0; i < this.predicates.length; i++) { + if (!this.predicates[i].equals(other.predicates[i])) { + return false; + } + } + if (this.consequents.length !== other.consequents.length) { + return false; + } + for (let i = 0; i < this.consequents.length; i++) { + if (!this.consequents[i].equals(other.consequents[i])) { + return false; + } + } + if (this.catchall && other.catchall) { + return this.catchall.equals(other.catchall); + } else if (this.catchall || other.catchall) { + return false; + } + return true; + } + return false; + } } // Scheme chapter 2 @@ -450,6 +677,25 @@ export namespace Extended { accept(visitor: Visitor): any { return visitor.visitList(this); } + equals(other: Expression): boolean { + if (other instanceof List) { + if (this.elements.length !== other.elements.length) { + return false; + } + for (let i = 0; i < this.elements.length; i++) { + if (!this.elements[i].equals(other.elements[i])) { + return false; + } + } + if (this.terminator && other.terminator) { + return this.terminator.equals(other.terminator); + } else if (this.terminator || other.terminator) { + return false; + } + return true; + } + return false; + } } // Scheme chapter 3 @@ -469,6 +715,20 @@ export namespace Extended { accept(visitor: Visitor): any { return visitor.visitBegin(this); } + equals(other: Expression): boolean { + if (other instanceof Begin) { + if (this.expressions.length !== other.expressions.length) { + return false; + } + for (let i = 0; i < this.expressions.length; i++) { + if (!this.expressions[i].equals(other.expressions[i])) { + return false; + } + } + return true; + } + return false; + } } /** @@ -486,5 +746,11 @@ export namespace Extended { accept(visitor: Visitor): any { return visitor.visitDelay(this); } + equals(other: Expression): boolean { + if (other instanceof Delay) { + return this.expression.equals(other.expression); + } + return false; + } } } diff --git a/src/utils/reverse_parser.ts b/src/utils/reverse_parser.ts index eec2b01..6047745 100644 --- a/src/utils/reverse_parser.ts +++ b/src/utils/reverse_parser.ts @@ -5,7 +5,7 @@ export function unparse(node: es.Node): string { switch (node.type) { case "Identifier": return node.name; - + case "Literal": return node.raw!; @@ -13,11 +13,11 @@ export function unparse(node: es.Node): string { const callee = unparse(node.callee); const args = node.arguments.map(unparse).join(" "); return `(${callee} ${args})`; - + case "ArrayExpression": - const elements = node.elements.map(s => unparse(s as any)).join(" "); + const elements = node.elements.map((s) => unparse(s as any)).join(" "); return `(vector ${elements})`; - + case "ArrowFunctionExpression": const params = node.params.map(unparse).join(" "); const body = unparse(node.body); @@ -49,7 +49,7 @@ export function unparse(node: es.Node): string { case "ConditionalExpression": const test = unparse(node.test); - const consequent = unparse(node.consequent); + const consequent = unparse(node.consequent); const alternate = unparse(node.alternate); return `(if ${test} ${consequent} ${alternate})`; @@ -60,12 +60,12 @@ export function unparse(node: es.Node): string { const identifiers = node.specifiers.map(unparse).join(" "); const source = unparse(node.source); return `(import (${source} ${identifiers}))`; - + case "ExportNamedDeclaration": - const definition = unparse(node.declaration!) + const definition = unparse(node.declaration!); return `(export ${definition})`; - + default: throw new Error(`Unparsing for node type ${node.type} not implemented`); } -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index 71dde72..11c0c89 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2688,6 +2688,11 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== +husky@^9.0.11: + version "9.0.11" + resolved "https://registry.yarnpkg.com/husky/-/husky-9.0.11.tgz#fc91df4c756050de41b3e478b2158b87c1e79af9" + integrity sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw== + ignore@^5.2.0, ignore@^5.2.4: version "5.3.1" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" From 4b971d9eb23ebceb33a349dd2ac513bb1854f514 Mon Sep 17 00:00:00 2001 From: henz Date: Wed, 10 Apr 2024 08:42:57 +0800 Subject: [PATCH 10/22] updating snapshots --- .../__snapshots__/schemeParse.ts.snap | 134 +++++++++--------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/src/transpiler/__tests__/__snapshots__/schemeParse.ts.snap b/src/transpiler/__tests__/__snapshots__/schemeParse.ts.snap index d92d3c4..32c8aa8 100644 --- a/src/transpiler/__tests__/__snapshots__/schemeParse.ts.snap +++ b/src/transpiler/__tests__/__snapshots__/schemeParse.ts.snap @@ -1,12 +1,12 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`schemeParse 1`] = ` -{ - "body": [ - { - "declarations": [ - { - "id": { +Object { + "body": Array [ + Object { + "declarations": Array [ + Object { + "id": Object { "loc": Location { "end": Position { "column": 22, @@ -20,11 +20,11 @@ exports[`schemeParse 1`] = ` "name": "square", "type": "Identifier", }, - "init": { + "init": Object { "async": false, - "body": { - "arguments": [ - { + "body": Object { + "arguments": Array [ + Object { "loc": Location { "end": Position { "column": 25, @@ -38,7 +38,7 @@ exports[`schemeParse 1`] = ` "name": "x", "type": "Identifier", }, - { + Object { "loc": Location { "end": Position { "column": 27, @@ -53,7 +53,7 @@ exports[`schemeParse 1`] = ` "type": "Identifier", }, ], - "callee": { + "callee": Object { "loc": Location { "end": Position { "column": 23, @@ -91,8 +91,8 @@ exports[`schemeParse 1`] = ` "line": 2, }, }, - "params": [ - { + "params": Array [ + Object { "loc": Location { "end": Position { "column": 19, @@ -125,12 +125,12 @@ exports[`schemeParse 1`] = ` }, "type": "VariableDeclaration", }, - { - "expression": { - "arguments": [ - { - "arguments": [ - { + Object { + "expression": Object { + "arguments": Array [ + Object { + "arguments": Array [ + Object { "loc": Location { "end": Position { "column": 15, @@ -141,12 +141,12 @@ exports[`schemeParse 1`] = ` "line": 3, }, }, - "raw": ""5/8"", + "raw": "\\"5/8\\"", "type": "Literal", "value": "5/8", }, ], - "callee": { + "callee": Object { "loc": Location { "end": Position { "column": 15, @@ -174,7 +174,7 @@ exports[`schemeParse 1`] = ` "type": "CallExpression", }, ], - "callee": { + "callee": Object { "loc": Location { "end": Position { "column": 14, @@ -213,10 +213,10 @@ exports[`schemeParse 1`] = ` }, "type": "ExpressionStatement", }, - { - "declarations": [ - { - "id": { + Object { + "declarations": Array [ + Object { + "id": Object { "loc": Location { "end": Position { "column": 18, @@ -230,9 +230,9 @@ exports[`schemeParse 1`] = ` "name": "x", "type": "Identifier", }, - "init": { - "arguments": [ - { + "init": Object { + "arguments": Array [ + Object { "loc": Location { "end": Position { "column": 20, @@ -243,12 +243,12 @@ exports[`schemeParse 1`] = ` "line": 4, }, }, - "raw": ""5"", + "raw": "\\"5\\"", "type": "Literal", "value": "5", }, ], - "callee": { + "callee": Object { "loc": Location { "end": Position { "column": 20, @@ -291,9 +291,9 @@ exports[`schemeParse 1`] = ` }, "type": "VariableDeclaration", }, - { - "expression": { - "left": { + Object { + "expression": Object { + "left": Object { "loc": Location { "end": Position { "column": 29, @@ -318,9 +318,9 @@ exports[`schemeParse 1`] = ` }, }, "operator": "=", - "right": { - "arguments": [ - { + "right": Object { + "arguments": Array [ + Object { "loc": Location { "end": Position { "column": 33, @@ -331,12 +331,12 @@ exports[`schemeParse 1`] = ` "line": 4, }, }, - "raw": ""10"", + "raw": "\\"10\\"", "type": "Literal", "value": "10", }, ], - "callee": { + "callee": Object { "loc": Location { "end": Position { "column": 33, @@ -377,8 +377,8 @@ exports[`schemeParse 1`] = ` }, "type": "ExpressionStatement", }, - { - "expression": { + Object { + "expression": Object { "loc": Location { "end": Position { "column": 35, @@ -404,9 +404,9 @@ exports[`schemeParse 1`] = ` }, "type": "ExpressionStatement", }, - { - "expression": { - "left": { + Object { + "expression": Object { + "left": Object { "loc": Location { "end": Position { "column": 22, @@ -431,14 +431,14 @@ exports[`schemeParse 1`] = ` }, }, "operator": "=", - "right": { + "right": Object { "async": false, - "body": { - "body": [ - { - "declarations": [ - { - "id": { + "body": Object { + "body": Array [ + Object { + "declarations": Array [ + Object { + "id": Object { "loc": Location { "end": Position { "column": 37, @@ -452,9 +452,9 @@ exports[`schemeParse 1`] = ` "name": "x", "type": "Identifier", }, - "init": { - "arguments": [ - { + "init": Object { + "arguments": Array [ + Object { "loc": Location { "end": Position { "column": 39, @@ -465,12 +465,12 @@ exports[`schemeParse 1`] = ` "line": 5, }, }, - "raw": ""5"", + "raw": "\\"5\\"", "type": "Literal", "value": "5", }, ], - "callee": { + "callee": Object { "loc": Location { "end": Position { "column": 39, @@ -513,10 +513,10 @@ exports[`schemeParse 1`] = ` }, "type": "VariableDeclaration", }, - { - "argument": { - "arguments": [ - { + Object { + "argument": Object { + "arguments": Array [ + Object { "loc": Location { "end": Position { "column": 45, @@ -530,7 +530,7 @@ exports[`schemeParse 1`] = ` "name": "x", "type": "Identifier", }, - { + Object { "loc": Location { "end": Position { "column": 47, @@ -545,7 +545,7 @@ exports[`schemeParse 1`] = ` "type": "Identifier", }, ], - "callee": { + "callee": Object { "loc": Location { "end": Position { "column": 43, @@ -585,7 +585,7 @@ exports[`schemeParse 1`] = ` "type": "ReturnStatement", }, ], - "loc": { + "loc": Object { "end": Position { "column": 48, "line": 5, @@ -608,8 +608,8 @@ exports[`schemeParse 1`] = ` "line": 5, }, }, - "params": [ - { + "params": Array [ + Object { "loc": Location { "end": Position { "column": 19, @@ -641,7 +641,7 @@ exports[`schemeParse 1`] = ` "type": "ExpressionStatement", }, ], - "loc": { + "loc": Object { "end": Position { "column": 50, "line": 5, From a6737e6f67e31224e207109dc8890c70ddd6500e Mon Sep 17 00:00:00 2001 From: s-kybound Date: Sat, 13 Apr 2024 16:19:09 +0800 Subject: [PATCH 11/22] update scheme complex numbers to force --- src/stdlib/core-math.ts | 48 ++++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/src/stdlib/core-math.ts b/src/stdlib/core-math.ts index e36e546..333bb11 100644 --- a/src/stdlib/core-math.ts +++ b/src/stdlib/core-math.ts @@ -110,7 +110,7 @@ class ComplexMatch extends Match { ) { super(result); } - build(): SchemeComplex { + build(): SchemeNumber { const real = this.real ? (this.real.build() as SchemeInteger | SchemeRational | SchemeReal) : SchemeInteger.EXACT_ZERO; @@ -717,9 +717,9 @@ export class SchemeComplex { static build( real: SchemeReal | SchemeRational | SchemeInteger, imaginary: SchemeReal | SchemeRational | SchemeInteger, - _force: boolean = false, - ): SchemeComplex { - return new SchemeComplex(real, imaginary); + force: boolean = false, + ): SchemeNumber { + return SchemeComplex.simplify(new SchemeComplex(real, imaginary), force); } private constructor( @@ -730,6 +730,16 @@ export class SchemeComplex { this.imaginary = imaginary; } + private static simplify( + complex: SchemeComplex, + force: boolean, + ): SchemeNumber { + if (!force && atomic_equals(complex.imaginary, SchemeInteger.EXACT_ZERO)) { + return complex.real; + } + return complex; + } + promote(nType: NumberType): SchemeNumber { switch (nType) { case NumberType.COMPLEX: @@ -739,7 +749,7 @@ export class SchemeComplex { } } - negate(): SchemeComplex { + negate(): SchemeNumber { return SchemeComplex.build(this.real.negate(), this.imaginary.negate()); } @@ -757,7 +767,7 @@ export class SchemeComplex { ); } - multiplicativeInverse(): SchemeComplex { + multiplicativeInverse(): SchemeNumber { // inverse of a + bi = a - bi / a^2 + b^2 // in this case, we use a / a^2 + b^2 and -b / a^2 + b^2 as the new values required const denominator = atomic_add( @@ -776,7 +786,7 @@ export class SchemeComplex { ); } - add(other: SchemeComplex): SchemeComplex { + add(other: SchemeComplex): SchemeNumber { return SchemeComplex.build( atomic_add(this.real, other.real) as | SchemeInteger @@ -789,7 +799,7 @@ export class SchemeComplex { ); } - multiply(other: SchemeComplex): SchemeComplex { + multiply(other: SchemeComplex): SchemeNumber { // (a + bi) * (c + di) = (ac - bd) + (ad + bc)i const realPart = atomic_subtract( atomic_multiply(this.real, other.real), @@ -845,11 +855,17 @@ export function is_complex(a: any): boolean { } export function is_exact(a: any): boolean { - return is_number(a) && a.numberType <= 2; + // if the number is a complex number, we need to check both the real and imaginary parts + return is_number(a) + ? a.numberType === 4 + ? is_exact(a.real) && is_exact(a.imaginary) + : a.numberType <= 2 + : false; } export function is_inexact(a: any): boolean { - return is_number(a) && a.numberType > 3; + // defined in terms of is_exact + return is_number(a) && !is_exact(a); } // the functions below are used to perform operations on numbers @@ -932,3 +948,15 @@ export function atomic_subtract( export function atomic_divide(a: SchemeNumber, b: SchemeNumber): SchemeNumber { return atomic_multiply(a, atomic_inverse(b)); } + +/** + * Important constants + */ +export const PI = SchemeReal.build(Math.PI); +export const E = SchemeReal.build(Math.E); +export const SQRT2 = SchemeReal.build(Math.SQRT2); +export const LN2 = SchemeReal.build(Math.LN2); +export const LN10 = SchemeReal.build(Math.LN10); +export const LOG2E = SchemeReal.build(Math.LOG2E); +export const LOG10E = SchemeReal.build(Math.LOG10E); +export const SQRT1_2 = SchemeReal.build(Math.SQRT1_2); From de68ac058eadaaf633a6f48caf912646b3775792 Mon Sep 17 00:00:00 2001 From: s-kybound Date: Sat, 13 Apr 2024 17:34:41 +0800 Subject: [PATCH 12/22] reimplement string operations in scm-slang --- src/stdlib/base.ts | 86 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/src/stdlib/base.ts b/src/stdlib/base.ts index ff804e4..9afe37d 100644 --- a/src/stdlib/base.ts +++ b/src/stdlib/base.ts @@ -760,3 +760,89 @@ export const equal$63$ = function (x: any, y: any): boolean { return false; } }; + +// string operations + +export const string$63$: Function = (s: any) => typeof s === "string"; + +export const make$45$string: Function = ( + n: core.SchemeNumber, + ch: string = " ", +) => { + // make sure that the character is a single character + if (ch.length !== 1) { + error("make-string: expected single character"); + } + + let result = ""; + + for (let i = 0; i < core.coerce_to_number(n); i++) { + result += ch; + } + + return result; +}; + +export const string = (...args: string[]) => args.join(""); + +export const string$45$length: Function = (s: string) => + make_number(String(s.length)); + +export const string$45$ref: Function = (s: string, i: core.SchemeNumber) => { + const index = core.coerce_to_number(i); + if (index < 0 || index >= s.length) { + error("string-ref: index out of bounds"); + } + return s[index]; +}; + +export const string$61$$63$: Function = (s1: string, s2: string) => s1 === s2; + +export const string$60$$63$: Function = (s1: string, s2: string) => s1 < s2; + +export const string$62$$63$: Function = (s1: string, s2: string) => s1 > s2; + +export const string$60$$61$$63$: Function = (s1: string, s2: string) => + s1 <= s2; + +export const string$62$$61$$63$: Function = (s1: string, s2: string) => + s1 >= s2; + +export const substring: Function = ( + s: string, + start: core.SchemeNumber, + end?: core.SchemeNumber, +) => { + const s_start = core.coerce_to_number(start); + const s_end = end === undefined ? s.length : core.coerce_to_number(end); + if (s_start < 0 || s_end > s.length || s_start > s_end) { + error("substring: index out of bounds"); + } + return s.substring(s_start, s_end); +}; + +export const string$45$append: Function = string; + +export const string$45$copy: Function = (s: string) => s; + +export const string$45$map: Function = (f: Function, s: string) => { + let result = ""; + for (let i = 0; i < s.length; i++) { + result += f(s[i]); + } + return result; +}; + +export const string$45$for$45$each: Function = (f: Function, s: string) => { + for (let i = 0; i < s.length; i++) { + f(s[i]); + } +}; + +export const string$45$$62$number: Function = (s: string) => { + try { + return make_number(s); + } catch (e) { + error("string->number: invalid number"); + } +}; From 51d434d97f27734ce2da97c13a712169805c2958 Mon Sep 17 00:00:00 2001 From: s-kybound Date: Sat, 13 Apr 2024 17:35:35 +0800 Subject: [PATCH 13/22] implement basic polar form for complex numbers --- src/stdlib/core-math.ts | 53 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/stdlib/core-math.ts b/src/stdlib/core-math.ts index 333bb11..fbeca05 100644 --- a/src/stdlib/core-math.ts +++ b/src/stdlib/core-math.ts @@ -816,11 +816,62 @@ export class SchemeComplex { throw new Error("Cannot coerce a complex number to a javascript number"); } + toPolar(): SchemePolar { + // force both the real and imaginary parts to be inexact + const real = this.real.promote(NumberType.REAL) as SchemeReal; + const imaginary = this.imaginary.promote(NumberType.REAL) as SchemeReal; + + // schemeReals can be reasoned with using the same logic as javascript numbers + // r = sqrt(a^2 + b^2) + const magnitude = SchemeReal.build( + Math.sqrt( + real.coerce() * real.coerce() + imaginary.coerce() * imaginary.coerce(), + ), + ); + // theta = atan(b / a) + const angle = SchemeReal.build( + Math.atan2(imaginary.coerce(), real.coerce()), + ); + return SchemePolar.build(magnitude, angle); + } + toString(): string { return `${this.real}+${this.imaginary}i`; } } +// an alternative form of the complex number. +// only used in intermediate steps, will be converted back at the end of the operation. +// current scm-slang will force any polar complex numbers to be made +// inexact, hence we opt to limit the use of polar form as much as possible. +class SchemePolar { + private readonly magnitude: SchemeReal; + private readonly angle: SchemeReal; + + constructor(magnitude: SchemeReal, angle: SchemeReal) { + this.magnitude = magnitude; + this.angle = angle; + } + + static build(magnitude: SchemeReal, angle: SchemeReal): SchemePolar { + return new SchemePolar(magnitude, angle); + } + + // converts the polar number back to a cartesian complex number + toCartesian(): SchemeNumber { + // a + bi = r * cos(theta) + r * sin(theta)i + // a = r * cos(theta) + // b = r * sin(theta) + const real = SchemeReal.build( + this.magnitude.coerce() * Math.cos(this.angle.coerce()), + ); + const imaginary = SchemeReal.build( + this.magnitude.coerce() * Math.sin(this.angle.coerce()), + ); + return SchemeComplex.build(real, imaginary); + } +} + export const infinity = SchemeReal.INFINITY; export const nan = SchemeReal.NAN; @@ -960,3 +1011,5 @@ export const LN10 = SchemeReal.build(Math.LN10); export const LOG2E = SchemeReal.build(Math.LOG2E); export const LOG10E = SchemeReal.build(Math.LOG10E); export const SQRT1_2 = SchemeReal.build(Math.SQRT1_2); + +// other important functions From 6b9bb762737d2a7bbcba8986db29a4bea03cd793 Mon Sep 17 00:00:00 2001 From: s-kybound Date: Sat, 13 Apr 2024 22:08:09 +0800 Subject: [PATCH 14/22] implement modulo, quotient, remainder --- jest.config.js | 1 + src/stdlib/__tests__/base.ts | 58 ++++++++ src/stdlib/base.ts | 124 ++++++++++++++++ src/stdlib/core-math.ts | 31 +++- .../__snapshots__/schemeParse.ts.snap | 134 +++++++++--------- 5 files changed, 279 insertions(+), 69 deletions(-) create mode 100644 src/stdlib/__tests__/base.ts diff --git a/jest.config.js b/jest.config.js index b11705b..2cbbbe0 100644 --- a/jest.config.js +++ b/jest.config.js @@ -4,4 +4,5 @@ module.exports = { "^.+\\.(ts|tsx)?$": "ts-jest", "^.+\\.(js|jsx)$": "babel-jest", }, + maxWorkers: 1, }; diff --git a/src/stdlib/__tests__/base.ts b/src/stdlib/__tests__/base.ts new file mode 100644 index 0000000..503189a --- /dev/null +++ b/src/stdlib/__tests__/base.ts @@ -0,0 +1,58 @@ +import * as base from '../base'; +import { SchemeInteger } from '../core-math'; + +function makeInteger(n: number): SchemeInteger { + return SchemeInteger.build(n); +} + +test("modulo works with positive numbers", () => { + expect(base.modulo(makeInteger(5), makeInteger(3))).toEqual(makeInteger(2)); + expect(base.modulo(makeInteger(5), makeInteger(5))).toEqual(makeInteger(0)); + expect(base.modulo(makeInteger(5), makeInteger(6))).toEqual(makeInteger(5)); + expect(base.modulo(makeInteger(5), makeInteger(7))).toEqual(makeInteger(5)); + expect(base.modulo(makeInteger(5), makeInteger(8))).toEqual(makeInteger(5)); + expect(base.modulo(makeInteger(5), makeInteger(9))).toEqual(makeInteger(5)); + expect(base.modulo(makeInteger(5), makeInteger(-4))).toEqual(makeInteger(-3)); +}); + +test("modulo works with negative numbers", () => { + expect(base.modulo(makeInteger(-5), makeInteger(3))).toEqual(makeInteger(1)); + expect(base.modulo(makeInteger(-5), makeInteger(5))).toEqual(makeInteger(0)); + expect(base.modulo(makeInteger(-5), makeInteger(6))).toEqual(makeInteger(1)); + expect(base.modulo(makeInteger(-5), makeInteger(7))).toEqual(makeInteger(2)); + expect(base.modulo(makeInteger(-5), makeInteger(8))).toEqual(makeInteger(3)); + expect(base.modulo(makeInteger(-5), makeInteger(9))).toEqual(makeInteger(4)); + expect(base.modulo(makeInteger(-5), makeInteger(-4))).toEqual(makeInteger(-1)); +}); + +test("quotient works with positive numbers", () => { + expect(base.quotient(makeInteger(5), makeInteger(3))).toEqual(makeInteger(1)); + expect(base.quotient(makeInteger(5), makeInteger(5))).toEqual(makeInteger(1)); + expect(base.quotient(makeInteger(5), makeInteger(6))).toEqual(makeInteger(0)); + expect(base.quotient(makeInteger(5), makeInteger(7))).toEqual(makeInteger(0)); + expect(base.quotient(makeInteger(5), makeInteger(8))).toEqual(makeInteger(0)); + expect(base.quotient(makeInteger(5), makeInteger(-4))).toEqual(makeInteger(-1)); +}); + +test("quotient works with negative numbers", () => { + expect(base.quotient(makeInteger(-5), makeInteger(3))).toEqual(makeInteger(-1)); + expect(base.quotient(makeInteger(-5), makeInteger(5))).toEqual(makeInteger(-1)); + expect(base.quotient(makeInteger(-5), makeInteger(6))).toEqual(makeInteger(0)); + expect(base.quotient(makeInteger(-5), makeInteger(7))).toEqual(makeInteger(0)); + expect(base.quotient(makeInteger(-5), makeInteger(8))).toEqual(makeInteger(0)); + expect(base.quotient(makeInteger(-5), makeInteger(-4))).toEqual(makeInteger(1)); +}); + +test("remainder works with positive numbers", () => { + expect(base.remainder(makeInteger(5), makeInteger(3))).toEqual(makeInteger(2)); + expect(base.remainder(makeInteger(5), makeInteger(5))).toEqual(makeInteger(0)); + expect(base.remainder(makeInteger(5), makeInteger(6))).toEqual(makeInteger(5)); + expect(base.remainder(makeInteger(5), makeInteger(7))).toEqual(makeInteger(5)); +}); + +test("remainder works with negative numbers", () => { + expect(base.remainder(makeInteger(-5), makeInteger(3))).toEqual(makeInteger(-2)); + expect(base.remainder(makeInteger(-5), makeInteger(5))).toEqual(makeInteger(0)); + expect(base.remainder(makeInteger(-5), makeInteger(6))).toEqual(makeInteger(-5)); + expect(base.remainder(makeInteger(-5), makeInteger(7))).toEqual(makeInteger(-5)); +}); \ No newline at end of file diff --git a/src/stdlib/base.ts b/src/stdlib/base.ts index 9afe37d..5927650 100644 --- a/src/stdlib/base.ts +++ b/src/stdlib/base.ts @@ -240,6 +240,112 @@ export const $47$: Function = ( export const abs: Function = (n: core.SchemeNumber[]) => negative$63$(n) ? atomic_negate(n) : n; +export const quotient: Function = (a: core.SchemeInteger, b: core.SchemeInteger) => { + if (!integer$63$(a) || !integer$63$(b)) { + error("quotient: expected integers"); + } + if (atomic_equals(b, make_number(0))) { + error("quotient: division by zero"); + } + + let remainder = modulo(a, b); + + let quotient = atomic_divide(atomic_subtract(a, remainder), b) as core.SchemeInteger; + + if (atomic_equals(remainder, make_number(0))) { + return quotient; + } + + // if both a and b are same-signed, we are done + if (atomic_less_than(a, make_number(0)) && atomic_less_than(b, make_number(0))) { + return quotient; + } + + if (atomic_greater_than(a, make_number(0)) && atomic_greater_than(b, make_number(0))) { + return quotient; + } + + // if a is negative, we need to increment the quotient + // to account for the remainder + if (atomic_less_than(a, make_number(0))) { + quotient = atomic_add(quotient, make_number(1)) as core.SchemeInteger; + } + + // if b is negative, we need to decrement the quotient + // to account for the remainder + if (atomic_less_than(b, make_number(0))) { + quotient = atomic_add(quotient, make_number(1)) as core.SchemeInteger; + } + + return quotient; +} + +export const remainder: Function = (a: core.SchemeInteger, b: core.SchemeInteger) => { + if (!integer$63$(a) || !integer$63$(b)) { + error("remainder: expected integers"); + } + if (atomic_equals(b, make_number(0))) { + error("remainder: division by zero"); + } + + let q = quotient(a, b); + + let remainder = atomic_subtract(a, atomic_multiply(q, b)) as core.SchemeInteger; + + return remainder; +} + +export const modulo: Function = (a: core.SchemeInteger, b: core.SchemeInteger) => { + if (!integer$63$(a) || !integer$63$(b)) { + error("modulo: expected integers"); + } + if (atomic_equals(b, make_number(0))) { + error("modulo: division by zero"); + } + + let working = a; + + while (atomic_greater_than_or_equals(abs(working), abs(b))) { + if (atomic_less_than(working, make_number(0))) { + if (atomic_less_than(b, make_number(0))) { + working = atomic_subtract(working, b) as core.SchemeInteger; + } else { + working = atomic_add(working, b) as core.SchemeInteger; + } + } else { + if (atomic_less_than(b, make_number(0))) { + working = atomic_add(working, b) as core.SchemeInteger; + } else { + working = atomic_subtract(working, b) as core.SchemeInteger; + } + } + } + + // we need to deal with the sign of the result + // in 4 separate cases + if (atomic_less_than(working, make_number(0))) { + if (atomic_less_than(b, make_number(0))) { + // both are negative + // do nothing + return working; + } else { + // b is positive + // result needs to be positive - add b to the working + return atomic_add(working, b) as core.SchemeInteger; + } + } else { + if (atomic_less_than(b, make_number(0))) { + // b is negative + // result needs to be negative - add b to the working + return atomic_add(working, b) as core.SchemeInteger; + } else { + // both are positive + // do nothing + return working; + } + } +} + // pair operations export const cons: Function = core.pair; @@ -846,3 +952,21 @@ export const string$45$$62$number: Function = (s: string) => { error("string->number: invalid number"); } }; + +export const string$45$$62$list: Function = (s: string) => { + let result = null; + for (let i = s.length - 1; i >= 0; i--) { + result = cons(s[i], result); + } + return result; +} + +export const list$45$$62$string: Function = (l: core.List) => { + let result = ""; + let current = l; + while (current !== null) { + result += String(car(current)); + current = cdr(current); + } + return result; +} \ No newline at end of file diff --git a/src/stdlib/core-math.ts b/src/stdlib/core-math.ts index fbeca05..6392d08 100644 --- a/src/stdlib/core-math.ts +++ b/src/stdlib/core-math.ts @@ -812,6 +812,14 @@ export class SchemeComplex { return SchemeComplex.build(realPart, imaginaryPart); } + getReal(): SchemeInteger | SchemeRational | SchemeReal { + return this.real; + } + + getImaginary(): SchemeInteger | SchemeRational | SchemeReal { + return this.imaginary; + } + coerce(): number { throw new Error("Cannot coerce a complex number to a javascript number"); } @@ -921,6 +929,25 @@ export function is_inexact(a: any): boolean { // the functions below are used to perform operations on numbers +function simplify(a: SchemeNumber): SchemeNumber { + switch (a.numberType) { + case NumberType.INTEGER: + return a; + case NumberType.RATIONAL: + return (a as SchemeRational).getDenominator() === 1n + ? SchemeInteger.build(a.getNumerator()) + : a; + case NumberType.REAL: + return a; + case NumberType.COMPLEX: + // safe to cast as simplify never promotes a number + return SchemeComplex.build( + simplify((a as SchemeComplex).getReal()) as SchemeInteger | SchemeRational | SchemeReal, + simplify((a as SchemeComplex).getImaginary()) as SchemeInteger | SchemeRational | SchemeReal, + ); + } +} + /** * This function takes two numbers and brings them to the same level. */ @@ -977,7 +1004,7 @@ export function atomic_greater_than_or_equals( export function atomic_add(a: SchemeNumber, b: SchemeNumber): SchemeNumber { const [newA, newB] = equalify(a, b); // safe to cast as we are assured they are of the same type - return newA.add(newB as any); + return simplify(newA.add(newB as any)); } export function atomic_multiply( @@ -986,7 +1013,7 @@ export function atomic_multiply( ): SchemeNumber { const [newA, newB] = equalify(a, b); // safe to cast as we are assured they are of the same type - return newA.multiply(newB as any); + return simplify(newA.multiply(newB as any)); } export function atomic_subtract( diff --git a/src/transpiler/__tests__/__snapshots__/schemeParse.ts.snap b/src/transpiler/__tests__/__snapshots__/schemeParse.ts.snap index 32c8aa8..d92d3c4 100644 --- a/src/transpiler/__tests__/__snapshots__/schemeParse.ts.snap +++ b/src/transpiler/__tests__/__snapshots__/schemeParse.ts.snap @@ -1,12 +1,12 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`schemeParse 1`] = ` -Object { - "body": Array [ - Object { - "declarations": Array [ - Object { - "id": Object { +{ + "body": [ + { + "declarations": [ + { + "id": { "loc": Location { "end": Position { "column": 22, @@ -20,11 +20,11 @@ Object { "name": "square", "type": "Identifier", }, - "init": Object { + "init": { "async": false, - "body": Object { - "arguments": Array [ - Object { + "body": { + "arguments": [ + { "loc": Location { "end": Position { "column": 25, @@ -38,7 +38,7 @@ Object { "name": "x", "type": "Identifier", }, - Object { + { "loc": Location { "end": Position { "column": 27, @@ -53,7 +53,7 @@ Object { "type": "Identifier", }, ], - "callee": Object { + "callee": { "loc": Location { "end": Position { "column": 23, @@ -91,8 +91,8 @@ Object { "line": 2, }, }, - "params": Array [ - Object { + "params": [ + { "loc": Location { "end": Position { "column": 19, @@ -125,12 +125,12 @@ Object { }, "type": "VariableDeclaration", }, - Object { - "expression": Object { - "arguments": Array [ - Object { - "arguments": Array [ - Object { + { + "expression": { + "arguments": [ + { + "arguments": [ + { "loc": Location { "end": Position { "column": 15, @@ -141,12 +141,12 @@ Object { "line": 3, }, }, - "raw": "\\"5/8\\"", + "raw": ""5/8"", "type": "Literal", "value": "5/8", }, ], - "callee": Object { + "callee": { "loc": Location { "end": Position { "column": 15, @@ -174,7 +174,7 @@ Object { "type": "CallExpression", }, ], - "callee": Object { + "callee": { "loc": Location { "end": Position { "column": 14, @@ -213,10 +213,10 @@ Object { }, "type": "ExpressionStatement", }, - Object { - "declarations": Array [ - Object { - "id": Object { + { + "declarations": [ + { + "id": { "loc": Location { "end": Position { "column": 18, @@ -230,9 +230,9 @@ Object { "name": "x", "type": "Identifier", }, - "init": Object { - "arguments": Array [ - Object { + "init": { + "arguments": [ + { "loc": Location { "end": Position { "column": 20, @@ -243,12 +243,12 @@ Object { "line": 4, }, }, - "raw": "\\"5\\"", + "raw": ""5"", "type": "Literal", "value": "5", }, ], - "callee": Object { + "callee": { "loc": Location { "end": Position { "column": 20, @@ -291,9 +291,9 @@ Object { }, "type": "VariableDeclaration", }, - Object { - "expression": Object { - "left": Object { + { + "expression": { + "left": { "loc": Location { "end": Position { "column": 29, @@ -318,9 +318,9 @@ Object { }, }, "operator": "=", - "right": Object { - "arguments": Array [ - Object { + "right": { + "arguments": [ + { "loc": Location { "end": Position { "column": 33, @@ -331,12 +331,12 @@ Object { "line": 4, }, }, - "raw": "\\"10\\"", + "raw": ""10"", "type": "Literal", "value": "10", }, ], - "callee": Object { + "callee": { "loc": Location { "end": Position { "column": 33, @@ -377,8 +377,8 @@ Object { }, "type": "ExpressionStatement", }, - Object { - "expression": Object { + { + "expression": { "loc": Location { "end": Position { "column": 35, @@ -404,9 +404,9 @@ Object { }, "type": "ExpressionStatement", }, - Object { - "expression": Object { - "left": Object { + { + "expression": { + "left": { "loc": Location { "end": Position { "column": 22, @@ -431,14 +431,14 @@ Object { }, }, "operator": "=", - "right": Object { + "right": { "async": false, - "body": Object { - "body": Array [ - Object { - "declarations": Array [ - Object { - "id": Object { + "body": { + "body": [ + { + "declarations": [ + { + "id": { "loc": Location { "end": Position { "column": 37, @@ -452,9 +452,9 @@ Object { "name": "x", "type": "Identifier", }, - "init": Object { - "arguments": Array [ - Object { + "init": { + "arguments": [ + { "loc": Location { "end": Position { "column": 39, @@ -465,12 +465,12 @@ Object { "line": 5, }, }, - "raw": "\\"5\\"", + "raw": ""5"", "type": "Literal", "value": "5", }, ], - "callee": Object { + "callee": { "loc": Location { "end": Position { "column": 39, @@ -513,10 +513,10 @@ Object { }, "type": "VariableDeclaration", }, - Object { - "argument": Object { - "arguments": Array [ - Object { + { + "argument": { + "arguments": [ + { "loc": Location { "end": Position { "column": 45, @@ -530,7 +530,7 @@ Object { "name": "x", "type": "Identifier", }, - Object { + { "loc": Location { "end": Position { "column": 47, @@ -545,7 +545,7 @@ Object { "type": "Identifier", }, ], - "callee": Object { + "callee": { "loc": Location { "end": Position { "column": 43, @@ -585,7 +585,7 @@ Object { "type": "ReturnStatement", }, ], - "loc": Object { + "loc": { "end": Position { "column": 48, "line": 5, @@ -608,8 +608,8 @@ Object { "line": 5, }, }, - "params": Array [ - Object { + "params": [ + { "loc": Location { "end": Position { "column": 19, @@ -641,7 +641,7 @@ Object { "type": "ExpressionStatement", }, ], - "loc": Object { + "loc": { "end": Position { "column": 50, "line": 5, From 966f4c2794f4511e0bb3e4c991557ca0439073db Mon Sep 17 00:00:00 2001 From: s-kybound Date: Sat, 13 Apr 2024 22:16:37 +0800 Subject: [PATCH 15/22] implement gcd and lcm --- src/stdlib/__tests__/base.ts | 85 ++++++++++++++++++++++++++++-------- src/stdlib/base.ts | 84 ++++++++++++++++++++++++++++++----- src/stdlib/core-math.ts | 12 +++-- 3 files changed, 148 insertions(+), 33 deletions(-) diff --git a/src/stdlib/__tests__/base.ts b/src/stdlib/__tests__/base.ts index 503189a..70a9500 100644 --- a/src/stdlib/__tests__/base.ts +++ b/src/stdlib/__tests__/base.ts @@ -1,5 +1,5 @@ -import * as base from '../base'; -import { SchemeInteger } from '../core-math'; +import * as base from "../base"; +import { SchemeInteger } from "../core-math"; function makeInteger(n: number): SchemeInteger { return SchemeInteger.build(n); @@ -22,7 +22,9 @@ test("modulo works with negative numbers", () => { expect(base.modulo(makeInteger(-5), makeInteger(7))).toEqual(makeInteger(2)); expect(base.modulo(makeInteger(-5), makeInteger(8))).toEqual(makeInteger(3)); expect(base.modulo(makeInteger(-5), makeInteger(9))).toEqual(makeInteger(4)); - expect(base.modulo(makeInteger(-5), makeInteger(-4))).toEqual(makeInteger(-1)); + expect(base.modulo(makeInteger(-5), makeInteger(-4))).toEqual( + makeInteger(-1), + ); }); test("quotient works with positive numbers", () => { @@ -31,28 +33,73 @@ test("quotient works with positive numbers", () => { expect(base.quotient(makeInteger(5), makeInteger(6))).toEqual(makeInteger(0)); expect(base.quotient(makeInteger(5), makeInteger(7))).toEqual(makeInteger(0)); expect(base.quotient(makeInteger(5), makeInteger(8))).toEqual(makeInteger(0)); - expect(base.quotient(makeInteger(5), makeInteger(-4))).toEqual(makeInteger(-1)); + expect(base.quotient(makeInteger(5), makeInteger(-4))).toEqual( + makeInteger(-1), + ); }); test("quotient works with negative numbers", () => { - expect(base.quotient(makeInteger(-5), makeInteger(3))).toEqual(makeInteger(-1)); - expect(base.quotient(makeInteger(-5), makeInteger(5))).toEqual(makeInteger(-1)); - expect(base.quotient(makeInteger(-5), makeInteger(6))).toEqual(makeInteger(0)); - expect(base.quotient(makeInteger(-5), makeInteger(7))).toEqual(makeInteger(0)); - expect(base.quotient(makeInteger(-5), makeInteger(8))).toEqual(makeInteger(0)); - expect(base.quotient(makeInteger(-5), makeInteger(-4))).toEqual(makeInteger(1)); + expect(base.quotient(makeInteger(-5), makeInteger(3))).toEqual( + makeInteger(-1), + ); + expect(base.quotient(makeInteger(-5), makeInteger(5))).toEqual( + makeInteger(-1), + ); + expect(base.quotient(makeInteger(-5), makeInteger(6))).toEqual( + makeInteger(0), + ); + expect(base.quotient(makeInteger(-5), makeInteger(7))).toEqual( + makeInteger(0), + ); + expect(base.quotient(makeInteger(-5), makeInteger(8))).toEqual( + makeInteger(0), + ); + expect(base.quotient(makeInteger(-5), makeInteger(-4))).toEqual( + makeInteger(1), + ); }); test("remainder works with positive numbers", () => { - expect(base.remainder(makeInteger(5), makeInteger(3))).toEqual(makeInteger(2)); - expect(base.remainder(makeInteger(5), makeInteger(5))).toEqual(makeInteger(0)); - expect(base.remainder(makeInteger(5), makeInteger(6))).toEqual(makeInteger(5)); - expect(base.remainder(makeInteger(5), makeInteger(7))).toEqual(makeInteger(5)); + expect(base.remainder(makeInteger(5), makeInteger(3))).toEqual( + makeInteger(2), + ); + expect(base.remainder(makeInteger(5), makeInteger(5))).toEqual( + makeInteger(0), + ); + expect(base.remainder(makeInteger(5), makeInteger(6))).toEqual( + makeInteger(5), + ); + expect(base.remainder(makeInteger(5), makeInteger(7))).toEqual( + makeInteger(5), + ); }); test("remainder works with negative numbers", () => { - expect(base.remainder(makeInteger(-5), makeInteger(3))).toEqual(makeInteger(-2)); - expect(base.remainder(makeInteger(-5), makeInteger(5))).toEqual(makeInteger(0)); - expect(base.remainder(makeInteger(-5), makeInteger(6))).toEqual(makeInteger(-5)); - expect(base.remainder(makeInteger(-5), makeInteger(7))).toEqual(makeInteger(-5)); -}); \ No newline at end of file + expect(base.remainder(makeInteger(-5), makeInteger(3))).toEqual( + makeInteger(-2), + ); + expect(base.remainder(makeInteger(-5), makeInteger(5))).toEqual( + makeInteger(0), + ); + expect(base.remainder(makeInteger(-5), makeInteger(6))).toEqual( + makeInteger(-5), + ); + expect(base.remainder(makeInteger(-5), makeInteger(7))).toEqual( + makeInteger(-5), + ); +}); + +test("gcd", () => { + expect(base.gcd(makeInteger(8), makeInteger(12))).toEqual(makeInteger(4)); + expect(base.gcd(makeInteger(8), makeInteger(13))).toEqual(makeInteger(1)); + expect(base.gcd(makeInteger(0), makeInteger(13))).toEqual(makeInteger(13)); + expect(base.gcd(makeInteger(0), makeInteger(0))).toEqual(makeInteger(0)); + expect(base.gcd(makeInteger(4), makeInteger(-2))).toEqual(makeInteger(2)); +}); + +test("lcm", () => { + expect(base.lcm(makeInteger(8), makeInteger(12))).toEqual(makeInteger(24)); + expect(base.lcm(makeInteger(8), makeInteger(13))).toEqual(makeInteger(104)); + expect(base.lcm(makeInteger(0), makeInteger(13))).toEqual(makeInteger(0)); + expect(base.lcm(makeInteger(4), makeInteger(-2))).toEqual(makeInteger(4)); +}); diff --git a/src/stdlib/base.ts b/src/stdlib/base.ts index 5927650..40a76c3 100644 --- a/src/stdlib/base.ts +++ b/src/stdlib/base.ts @@ -240,7 +240,10 @@ export const $47$: Function = ( export const abs: Function = (n: core.SchemeNumber[]) => negative$63$(n) ? atomic_negate(n) : n; -export const quotient: Function = (a: core.SchemeInteger, b: core.SchemeInteger) => { +export const quotient: Function = ( + a: core.SchemeInteger, + b: core.SchemeInteger, +) => { if (!integer$63$(a) || !integer$63$(b)) { error("quotient: expected integers"); } @@ -250,18 +253,27 @@ export const quotient: Function = (a: core.SchemeInteger, b: core.SchemeInteger) let remainder = modulo(a, b); - let quotient = atomic_divide(atomic_subtract(a, remainder), b) as core.SchemeInteger; + let quotient = atomic_divide( + atomic_subtract(a, remainder), + b, + ) as core.SchemeInteger; if (atomic_equals(remainder, make_number(0))) { return quotient; } // if both a and b are same-signed, we are done - if (atomic_less_than(a, make_number(0)) && atomic_less_than(b, make_number(0))) { + if ( + atomic_less_than(a, make_number(0)) && + atomic_less_than(b, make_number(0)) + ) { return quotient; } - if (atomic_greater_than(a, make_number(0)) && atomic_greater_than(b, make_number(0))) { + if ( + atomic_greater_than(a, make_number(0)) && + atomic_greater_than(b, make_number(0)) + ) { return quotient; } @@ -278,9 +290,12 @@ export const quotient: Function = (a: core.SchemeInteger, b: core.SchemeInteger) } return quotient; -} +}; -export const remainder: Function = (a: core.SchemeInteger, b: core.SchemeInteger) => { +export const remainder: Function = ( + a: core.SchemeInteger, + b: core.SchemeInteger, +) => { if (!integer$63$(a) || !integer$63$(b)) { error("remainder: expected integers"); } @@ -290,12 +305,18 @@ export const remainder: Function = (a: core.SchemeInteger, b: core.SchemeInteger let q = quotient(a, b); - let remainder = atomic_subtract(a, atomic_multiply(q, b)) as core.SchemeInteger; + let remainder = atomic_subtract( + a, + atomic_multiply(q, b), + ) as core.SchemeInteger; return remainder; -} +}; -export const modulo: Function = (a: core.SchemeInteger, b: core.SchemeInteger) => { +export const modulo: Function = ( + a: core.SchemeInteger, + b: core.SchemeInteger, +) => { if (!integer$63$(a) || !integer$63$(b)) { error("modulo: expected integers"); } @@ -344,8 +365,49 @@ export const modulo: Function = (a: core.SchemeInteger, b: core.SchemeInteger) = return working; } } +}; + +function atomic_gcd( + a: core.SchemeInteger, + b: core.SchemeInteger, +): core.SchemeInteger { + if (atomic_equals(b, make_number(0))) { + return abs(a); + } + return abs(atomic_gcd(b, remainder(a, b))); +} + +export const gcd: Function = (...vals: core.SchemeInteger[]) => { + if (vals.length === 0) { + return core.SchemeInteger.EXACT_ZERO; + } + + if (vals.length === 1) { + return vals[0]; + } + + return vals.reduce(atomic_gcd); +}; + +function atomic_lcm( + a: core.SchemeInteger, + b: core.SchemeInteger, +): core.SchemeInteger { + return abs(atomic_multiply(quotient(a, gcd(a, b)), b)) as core.SchemeInteger; } +export const lcm: Function = (...vals: core.SchemeInteger[]) => { + if (vals.length === 0) { + return core.SchemeInteger.build(1); + } + + if (vals.length === 1) { + return vals[0]; + } + + return vals.reduce(atomic_lcm); +}; + // pair operations export const cons: Function = core.pair; @@ -959,7 +1021,7 @@ export const string$45$$62$list: Function = (s: string) => { result = cons(s[i], result); } return result; -} +}; export const list$45$$62$string: Function = (l: core.List) => { let result = ""; @@ -969,4 +1031,4 @@ export const list$45$$62$string: Function = (l: core.List) => { current = cdr(current); } return result; -} \ No newline at end of file +}; diff --git a/src/stdlib/core-math.ts b/src/stdlib/core-math.ts index 6392d08..b13f79c 100644 --- a/src/stdlib/core-math.ts +++ b/src/stdlib/core-math.ts @@ -942,10 +942,16 @@ function simplify(a: SchemeNumber): SchemeNumber { case NumberType.COMPLEX: // safe to cast as simplify never promotes a number return SchemeComplex.build( - simplify((a as SchemeComplex).getReal()) as SchemeInteger | SchemeRational | SchemeReal, - simplify((a as SchemeComplex).getImaginary()) as SchemeInteger | SchemeRational | SchemeReal, + simplify((a as SchemeComplex).getReal()) as + | SchemeInteger + | SchemeRational + | SchemeReal, + simplify((a as SchemeComplex).getImaginary()) as + | SchemeInteger + | SchemeRational + | SchemeReal, ); - } + } } /** From 9c5f57fc3c282322440b761056dc9db92e19725e Mon Sep 17 00:00:00 2001 From: s-kybound Date: Sun, 14 Apr 2024 00:23:43 +0800 Subject: [PATCH 16/22] Extend base math functions --- src/stdlib/base.ts | 2 + src/stdlib/core-math.ts | 275 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 274 insertions(+), 3 deletions(-) diff --git a/src/stdlib/base.ts b/src/stdlib/base.ts index 40a76c3..23f8e7c 100644 --- a/src/stdlib/base.ts +++ b/src/stdlib/base.ts @@ -408,6 +408,8 @@ export const lcm: Function = (...vals: core.SchemeInteger[]) => { return vals.reduce(atomic_lcm); }; +export const square: Function = (n: core.SchemeNumber) => $42$(n, n); + // pair operations export const cons: Function = core.pair; diff --git a/src/stdlib/core-math.ts b/src/stdlib/core-math.ts index b13f79c..6918d55 100644 --- a/src/stdlib/core-math.ts +++ b/src/stdlib/core-math.ts @@ -853,10 +853,10 @@ export class SchemeComplex { // current scm-slang will force any polar complex numbers to be made // inexact, hence we opt to limit the use of polar form as much as possible. class SchemePolar { - private readonly magnitude: SchemeReal; - private readonly angle: SchemeReal; + readonly magnitude: SchemeReal; + readonly angle: SchemeReal; - constructor(magnitude: SchemeReal, angle: SchemeReal) { + private constructor(magnitude: SchemeReal, angle: SchemeReal) { this.magnitude = magnitude; this.angle = angle; } @@ -1046,3 +1046,272 @@ export const LOG10E = SchemeReal.build(Math.LOG10E); export const SQRT1_2 = SchemeReal.build(Math.SQRT1_2); // other important functions +// for now, exponentials, square roots and the like will be treated as +// inexact functions, and will return inexact results. this allows us to +// leverage on the inbuilt javascript Math library. +// additional logic is required to handle complex numbers, which we can do with +// our polar form representation. + +export const expt = (n: SchemeNumber, e: SchemeNumber): SchemeNumber => { + if (!is_number(n) || !is_number(e)) { + throw new Error("expt: expected numbers"); + } + if (!is_real(n) || !is_real(e)) { + // complex number case + // we can convert both parts to polar form and use the + // polar form exponentiation formula. + + // given a * e^(bi) and c * e^(di), + // (a * e^(bi)) ^ (c * e^(di)) can be represented by + // the general formula for complex exponentiation: + // (a^c * e^(-bd)) * e^(i(bc * ln(a) + ad)) + + // convert both numbers to polar form + const nPolar = (n.promote(NumberType.COMPLEX) as SchemeComplex).toPolar(); + const ePolar = (e.promote(NumberType.COMPLEX) as SchemeComplex).toPolar(); + + const a = nPolar.magnitude.coerce(); + const b = nPolar.angle.coerce(); + const c = ePolar.magnitude.coerce(); + const d = ePolar.angle.coerce(); + + // we can construct a new polar form following the formula above + const mag = SchemeReal.build(a ** c * Math.E ** (-b * d)); + const angle = SchemeReal.build(b * c * Math.log(a) + a * d); + + return SchemePolar.build(mag, angle).toCartesian(); + } + // coerce both numbers to javascript numbers + const base = n.coerce(); + const exponent = e.coerce(); + + // there are probably cases here i am not considering yet. + // for now, we will just use the javascript Math library and hope for the best. + return SchemeReal.build(Math.pow(base, exponent)); +}; + +export const exp = (n: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("exp: expected number"); + } + if (!is_real(n)) { + // complex number case + throw new Error("exp: expected real number"); + } + return SchemeReal.build(Math.exp(n.coerce())); +}; + +export const log = (n: SchemeNumber, base: SchemeNumber = E): SchemeNumber => { + if (!is_number(n) || !is_number(base)) { + throw new Error("log: expected numbers"); + } + if (!is_real(n) || !is_real(base)) { + // complex number case + // we can convert both parts to polar form and use the + // polar form logarithm formula. + // where log(a * e^(bi)) = log(a) + bi + // and log(c * e^(di)) = log(c) + di + // and so result is log(a) + bi / log(c) + di + // which is just (log(a) - log(c)) + (b / d) i + + // convert both numbers to polar form + const nPolar = (n.promote(NumberType.COMPLEX) as SchemeComplex).toPolar(); + const basePolar = ( + base.promote(NumberType.COMPLEX) as SchemeComplex + ).toPolar(); + const a = nPolar.magnitude.coerce(); + const b = nPolar.angle.coerce(); + const c = basePolar.magnitude.coerce(); + const d = basePolar.angle.coerce(); + + return SchemeComplex.build( + SchemeReal.build(Math.log(a) - Math.log(c)), + SchemeReal.build(b / d), + ); + } + return SchemeReal.build(Math.log(n.coerce()) / Math.log(base.coerce())); +}; + +export const sqrt = (n: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("sqrt: expected number"); + } + if (!is_real(n)) { + // complex number case + const polar = (n.promote(NumberType.COMPLEX) as SchemeComplex).toPolar(); + const mag = polar.magnitude; + const angle = polar.angle; + + // the square root of a complex number is given by + // the square root of the magnitude and half the angle + const newMag = sqrt(mag) as SchemeReal; + const newAngle = SchemeReal.build(angle.coerce() / 2); + + return SchemePolar.build(newMag, newAngle).toCartesian(); + } + let value = n.coerce(); + + if (value < 0) { + return SchemeComplex.build( + SchemeReal.INEXACT_ZERO, + SchemeReal.build(Math.sqrt(-value)), + ); + } + + return SchemeReal.build(Math.sqrt(n.coerce())); +}; + +export const sin = (n: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("sin: expected number"); + } + if (!is_real(n)) { + // complex number case + + // we can use euler's formula to find sin(x) for a complex number x = a + bi + // e^(ix) = cos(x) + i * sin(x) + // that can be rearranged into + // sin(x) = (e^(ix) - e^(-ix)) / 2i + // and finally into + // sin(x) = (sin(a) * (e^(-b) + e^(b)) / 2) + i * (cos(a) * (e^(-b) - e^(b)) / 2) + const complex = n.promote(NumberType.COMPLEX) as SchemeComplex; + const real = complex.getReal(); + const imaginary = complex.getImaginary(); + const a = real.coerce(); + const b = imaginary.coerce(); + return SchemeComplex.build( + SchemeReal.build((Math.sin(a) * (Math.exp(-b) + Math.exp(b))) / 2), + SchemeReal.build((Math.cos(a) * (Math.exp(-b) - Math.exp(b))) / 2), + ); + } + return SchemeReal.build(Math.sin(n.coerce())); +}; + +export const cos = (n: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("cos: expected number"); + } + if (!is_real(n)) { + // complex number case + + // we can use euler's formula to find cos(x) for a complex number x = a + bi + // e^(ix) = cos(x) + i * sin(x) + // that can be rearranged into + // cos(x) = (e^(ix) + e^(-ix)) / 2 + // and finally into + // cos(x) = (cos(a) * (e^(-b) + e^(b)) / 2) - i * (sin(a) * (e^(-b) - e^(b)) / 2) + const complex = n.promote(NumberType.COMPLEX) as SchemeComplex; + const real = complex.getReal(); + const imaginary = complex.getImaginary(); + const a = real.coerce(); + const b = imaginary.coerce(); + return SchemeComplex.build( + SchemeReal.build((Math.cos(a) * (Math.exp(-b) + Math.exp(b))) / 2), + SchemeReal.build((-Math.sin(a) * (Math.exp(-b) - Math.exp(b))) / 2), + ); + } + return SchemeReal.build(Math.cos(n.coerce())); +}; + +export const tan = (n: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("tan: expected number"); + } + if (!is_real(n)) { + // complex number case + const sinValue = sin(n); + const cosValue = cos(n); + return atomic_divide(sinValue, cosValue); + } + return SchemeReal.build(Math.tan(n.coerce())); +}; + +export const asin = (n: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("asin: expected number"); + } + if (!is_real(n)) { + // complex number case + // asin(n) = -i * ln(i * n + sqrt(1 - n^2)) + // we already have the building blocks needed to compute this + const i = SchemeComplex.build( + SchemeInteger.EXACT_ZERO, + SchemeInteger.build(1), + ); + return atomic_multiply( + atomic_negate(i), + log( + atomic_add( + atomic_multiply(i, n), + sqrt(atomic_subtract(SchemeInteger.build(1), atomic_multiply(n, n))), + ), + ), + ); + } + return SchemeReal.build(Math.asin(n.coerce())); +}; + +export const acos = (n: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("acos: expected number"); + } + if (!is_real(n)) { + // complex number case + // acos(n) = -i * ln(n + sqrt(n^2 - 1)) + // again, we have the building blocks needed to compute this + const i = SchemeComplex.build( + SchemeInteger.EXACT_ZERO, + SchemeInteger.build(1), + ); + return atomic_multiply( + atomic_negate(i), + log( + atomic_add( + n, + sqrt(atomic_subtract(atomic_multiply(n, n), SchemeInteger.build(1))), + ), + ), + ); + } + return SchemeReal.build(Math.acos(n.coerce())); +}; + +export const atan = (n: SchemeNumber, m?: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("atan: expected number"); + } + + if (m !== undefined) { + // two argument case, we construct a complex number with n + mi + // if neither n nor m are real, it's an error + if (!is_real(n) || !is_real(m)) { + throw new Error("atan: expected real numbers"); + } + return atan( + SchemeComplex.build( + n as SchemeInteger | SchemeRational | SchemeReal, + m as SchemeInteger | SchemeRational | SchemeReal, + ), + ); + } + + if (!is_real(n)) { + // complex number case + // atan(n) = 1/2 * i * ln((1 - i * n) / (1 + i * n)) + const i = SchemeComplex.build( + SchemeInteger.EXACT_ZERO, + SchemeInteger.build(1), + ); + return atomic_multiply( + // multiply is associative so the order here doesn't matter + atomic_multiply(SchemeRational.build(1, 2), i), + log( + atomic_divide( + atomic_subtract(SchemeInteger.build(1), atomic_multiply(i, n)), + atomic_add(SchemeInteger.build(1), atomic_multiply(i, n)), + ), + ), + ); + } + return SchemeReal.build(Math.atan(n.coerce())); +}; From 906141b20b3e3d64e92deb099d027ca2bee35cf5 Mon Sep 17 00:00:00 2001 From: s-kybound Date: Sun, 14 Apr 2024 00:28:18 +0800 Subject: [PATCH 17/22] make the new math functions exportable --- src/stdlib/base.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/stdlib/base.ts b/src/stdlib/base.ts index 23f8e7c..5a47688 100644 --- a/src/stdlib/base.ts +++ b/src/stdlib/base.ts @@ -409,6 +409,25 @@ export const lcm: Function = (...vals: core.SchemeInteger[]) => { }; export const square: Function = (n: core.SchemeNumber) => $42$(n, n); +export const expt: Function = core.expt; +export const exp: Function = core.exp; +export const log: Function = core.log; +export const sqrt: Function = core.sqrt; +export const sin: Function = core.sin; +export const cos: Function = core.cos; +export const tan: Function = core.tan; +export const asin: Function = core.asin; +export const acos: Function = core.acos; +export const atan: Function = core.atan; + +export const PI = core.PI; +export const E = core.E; +export const SQRT2 = core.SQRT2; +export const SQRT1$47$2 = core.SQRT1_2; +export const LN2 = core.LN2; +export const LN10 = core.LN10; +export const LOG2E = core.LOG2E; +export const LOG10E = core.LOG10E; // pair operations From 64a2d1860714de6c5fb6bb9148e1e3f45085c214 Mon Sep 17 00:00:00 2001 From: s-kybound Date: Sun, 14 Apr 2024 00:29:57 +0800 Subject: [PATCH 18/22] implement number->string --- src/stdlib/base.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/stdlib/base.ts b/src/stdlib/base.ts index 5a47688..19fd3f6 100644 --- a/src/stdlib/base.ts +++ b/src/stdlib/base.ts @@ -1036,6 +1036,9 @@ export const string$45$$62$number: Function = (s: string) => { } }; +export const number$45$$62$string: Function = (n: core.SchemeNumber) => + n.toString(); + export const string$45$$62$list: Function = (s: string) => { let result = null; for (let i = s.length - 1; i >= 0; i--) { From 11aaf86a19867b1ed1aa8fa96a00a3b020787416 Mon Sep 17 00:00:00 2001 From: s-kybound Date: Sun, 14 Apr 2024 22:13:09 +0800 Subject: [PATCH 19/22] implement rounding functions and complex number functions --- src/stdlib/base.ts | 10 +++ src/stdlib/core-math.ts | 170 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 180 insertions(+) diff --git a/src/stdlib/base.ts b/src/stdlib/base.ts index 19fd3f6..156f973 100644 --- a/src/stdlib/base.ts +++ b/src/stdlib/base.ts @@ -419,6 +419,16 @@ export const tan: Function = core.tan; export const asin: Function = core.asin; export const acos: Function = core.acos; export const atan: Function = core.atan; +export const floor: Function = core.floor; +export const ceiling: Function = core.ceiling; +export const truncate: Function = core.truncate; +export const round: Function = core.round; +export const make$45$rectangular: Function = core.make$45$rectangular; +export const make$45$polar: Function = core.make$45$polar; +export const real$45$part: Function = core.real$45$part; +export const imag$45$part: Function = core.imag$45$part; +export const magnitude: Function = core.magnitude; +export const angle: Function = core.angle; export const PI = core.PI; export const E = core.E; diff --git a/src/stdlib/core-math.ts b/src/stdlib/core-math.ts index 6918d55..c954f2b 100644 --- a/src/stdlib/core-math.ts +++ b/src/stdlib/core-math.ts @@ -1315,3 +1315,173 @@ export const atan = (n: SchemeNumber, m?: SchemeNumber): SchemeNumber => { } return SchemeReal.build(Math.atan(n.coerce())); }; + +export const floor = (n: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("floor: expected number"); + } + if (!is_real(n)) { + // complex number case + throw new Error("floor: expected real number"); + } + if (n.numberType === NumberType.INTEGER) { + return n; + } + if (n.numberType === NumberType.RATIONAL) { + // floor is numerator // denominator + const rational = n as SchemeRational; + const numerator = rational.getNumerator(); + const denominator = rational.getDenominator(); + return SchemeInteger.build(numerator / denominator); + } + return SchemeReal.build(Math.floor(n.coerce())); +}; + +export const ceiling = (n: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("ceiling: expected number"); + } + if (!is_real(n)) { + // complex number case + throw new Error("ceiling: expected real number"); + } + if (n.numberType === NumberType.INTEGER) { + return n; + } + if (n.numberType === NumberType.RATIONAL) { + // ceiling is (numerator + denominator - 1) // denominator + const rational = n as SchemeRational; + const numerator = rational.getNumerator(); + const denominator = rational.getDenominator(); + return SchemeInteger.build((numerator + denominator - 1n) / denominator); + } + return SchemeReal.build(Math.ceil(n.coerce())); +}; + +export const truncate = (n: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("truncate: expected number"); + } + if (!is_real(n)) { + // complex number case + throw new Error("truncate: expected real number"); + } + if (n.numberType === NumberType.INTEGER) { + return n; + } + if (n.numberType === NumberType.RATIONAL) { + // truncate is also just numerator // denominator + // exactly like floor + const rational = n as SchemeRational; + const numerator = rational.getNumerator(); + const denominator = rational.getDenominator(); + return SchemeInteger.build(numerator / denominator); + } + return SchemeReal.build(Math.trunc(n.coerce())); +}; + +export const round = (n: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("round: expected number"); + } + if (!is_real(n)) { + // complex number case + throw new Error("round: expected real number"); + } + if (n.numberType === NumberType.INTEGER) { + return n; + } + if (n.numberType === NumberType.RATIONAL) { + // round is numerator + denominator // 2 * denominator + const rational = n as SchemeRational; + const numerator = rational.getNumerator(); + const denominator = rational.getDenominator(); + return SchemeInteger.build((numerator + denominator / 2n) / denominator); + } + return SchemeReal.build(Math.round(n.coerce())); +}; + +export const make$45$rectangular = ( + a: SchemeNumber, + b: SchemeNumber, +): SchemeNumber => { + if (!is_number(a) || !is_number(b)) { + throw new Error("make-rectangular: expected numbers"); + } + if (!is_real(a) || !is_real(b)) { + // complex number case + throw new Error("make-rectangular: expected real numbers"); + } + return SchemeComplex.build( + a as SchemeReal | SchemeRational | SchemeInteger, + b as SchemeReal | SchemeRational | SchemeInteger, + ); +}; + +export const make$45$polar = ( + a: SchemeNumber, + b: SchemeNumber, +): SchemeNumber => { + if (!is_number(a) || !is_number(b)) { + throw new Error("make-polar: expected numbers"); + } + if (!is_real(a) || !is_real(b)) { + // complex number case + throw new Error("make-polar: expected real numbers"); + } + return SchemePolar.build( + a.promote(NumberType.REAL) as SchemeReal, + b.promote(NumberType.REAL) as SchemeReal, + ).toCartesian(); +}; + +export const real$45$part = (n: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("real-part: expected number"); + } + if (!is_real(n)) { + // complex number case + return (n as SchemeComplex).getReal(); + } + return n; +}; + +export const imag$45$part = (n: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("imag-part: expected number"); + } + if (!is_real(n)) { + // complex number case + return (n as SchemeComplex).getImaginary(); + } + return SchemeInteger.EXACT_ZERO; +}; + +export const magnitude = (n: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("magnitude: expected number"); + } + if (!is_real(n)) { + // complex number case + return (n as SchemeComplex).toPolar().magnitude; + } + // abs is not defined here so we should just use direct comparison + if (atomic_less_than(n, SchemeInteger.EXACT_ZERO)) { + return atomic_negate(n); + } + return n; +}; + +export const angle = (n: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("angle: expected number"); + } + if (!is_real(n)) { + // complex number case + return (n as SchemeComplex).toPolar().angle; + } + if (atomic_less_than(n, SchemeInteger.EXACT_ZERO)) { + return PI; + } + return SchemeInteger.EXACT_ZERO; +}; From 4b85ae175107b6fb175424a9cd752195fa63faf9 Mon Sep 17 00:00:00 2001 From: s-kybound Date: Sun, 14 Apr 2024 22:48:13 +0800 Subject: [PATCH 20/22] implement the rest of the stdlib --- src/stdlib/base.ts | 12 +++ src/stdlib/core-math.ts | 178 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+) diff --git a/src/stdlib/base.ts b/src/stdlib/base.ts index 156f973..5efe5da 100644 --- a/src/stdlib/base.ts +++ b/src/stdlib/base.ts @@ -197,6 +197,10 @@ export const $62$$61$: Function = ( export const zero$63$: Function = (n: core.SchemeNumber) => $61$(n, make_number(0)); +export const infinity$63$: Function = (n: core.SchemeNumber) => + $61$(n, core.SchemeReal.INFINITY) || $61$(n, core.SchemeReal.NEG_INFINITY); +export const nan$63$: Function = (n: core.SchemeNumber) => + n === core.SchemeReal.NAN; export const positive$63$: Function = (n: core.SchemeNumber) => $62$(n, make_number(0)); export const negative$63$: Function = (n: core.SchemeNumber) => @@ -408,6 +412,14 @@ export const lcm: Function = (...vals: core.SchemeInteger[]) => { return vals.reduce(atomic_lcm); }; +export const odd$63$: Function = core.odd$63$; +export const even$63$: Function = core.even$63$; + +export const numerator: Function = core.numerator; +export const denominator: Function = core.denominator; +export const exact: Function = core.exact; +export const inexact: Function = core.inexact; + export const square: Function = (n: core.SchemeNumber) => $42$(n, n); export const expt: Function = core.expt; export const exp: Function = core.exp; diff --git a/src/stdlib/core-math.ts b/src/stdlib/core-math.ts index c954f2b..1e48429 100644 --- a/src/stdlib/core-math.ts +++ b/src/stdlib/core-math.ts @@ -449,6 +449,10 @@ export class SchemeInteger { return SchemeInteger.build(this.value * other.value); } + getBigInt(): bigint { + return this.value; + } + coerce(): number { if (this.value > Number.MAX_SAFE_INTEGER) { return Infinity; @@ -1046,6 +1050,156 @@ export const LOG10E = SchemeReal.build(Math.LOG10E); export const SQRT1_2 = SchemeReal.build(Math.SQRT1_2); // other important functions + +export const numerator = (n: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("numerator: expected number"); + } + if (!is_real(n)) { + // complex number case + // always return an integer + return is_exact(n) ? SchemeInteger.build(1) : SchemeReal.build(1); + } + if (!is_rational(n)) { + // is real number + // get the value of the number + const val = n.coerce(); + // if the value is a defined special case, return accordingly + if (val === Infinity) { + return SchemeReal.build(1); + } + if (val === -Infinity) { + return SchemeReal.build(1); + } + if (isNaN(val)) { + return SchemeReal.NAN; + } + // if the value is an integer, return it + if (Number.isInteger(val)) { + return SchemeReal.build(val); + } + // else if the value is a float, + // multiply it till it becomes an integer + let multiplier = 1; + while (!Number.isInteger(val * multiplier)) { + multiplier *= 10; + } + let numerator = val * multiplier; + let denominator = multiplier; + // simplify the fraction + const gcd = (a: number, b: number): number => { + if (b === 0) { + return a; + } + return gcd(b, a % b); + }; + const divisor = gcd(numerator, denominator); + numerator = numerator / divisor; + return SchemeReal.build(numerator); + } + return SchemeInteger.build( + (n.promote(NumberType.RATIONAL) as SchemeRational).getNumerator(), + ); +}; + +export const denominator = (n: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("denominator: expected number"); + } + if (!is_real(n)) { + // complex number case + // always return an integer + return is_exact(n) ? SchemeInteger.build(1) : SchemeReal.build(1); + } + if (!is_rational(n)) { + // is real number + // get the value of the number + const val = n.coerce(); + // if the value is a defined special case, return accordingly + if (val === Infinity) { + return SchemeReal.INEXACT_ZERO; + } + if (val === -Infinity) { + return SchemeReal.INEXACT_ZERO; + } + if (isNaN(val)) { + return SchemeReal.NAN; + } + // if the value is an integer, return 1 + if (Number.isInteger(val)) { + return SchemeReal.build(1); + } + // else if the value is a float, + // multiply it till it becomes an integer + let multiplier = 1; + while (!Number.isInteger(val * multiplier)) { + multiplier *= 10; + } + let numerator = val * multiplier; + let denominator = multiplier; + // simplify the fraction + const gcd = (a: number, b: number): number => { + if (b === 0) { + return a; + } + return gcd(b, a % b); + }; + const divisor = gcd(numerator, denominator); + denominator = denominator / divisor; + return SchemeReal.build(denominator); + } + return SchemeInteger.build( + (n.promote(NumberType.RATIONAL) as SchemeRational).getDenominator(), + ); +}; + +export const exact = (n: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("exact: expected number"); + } + if (is_exact(n)) { + return n; + } + if (is_real(n)) { + // if the number is a real number, we can convert it to a rational number + // by multiplying it by a power of 10 until it becomes an integer + // and then dividing by the same power of 10 + let multiplier = 1; + let val = n.coerce(); + while (!Number.isInteger(val * multiplier)) { + multiplier *= 10; + } + return SchemeRational.build(val * multiplier, multiplier); + } + // if the number is a complex number, we can convert both the real and imaginary parts + // to exact numbers + return SchemeComplex.build( + exact((n as SchemeComplex).getReal()) as SchemeInteger | SchemeRational, + exact((n as SchemeComplex).getImaginary()) as + | SchemeInteger + | SchemeRational, + ); +}; + +export const inexact = (n: SchemeNumber): SchemeNumber => { + if (!is_number(n)) { + throw new Error("inexact: expected number"); + } + if (is_inexact(n)) { + return n; + } + if (is_real(n)) { + // if the number is a real number, we can convert it to a float + return SchemeReal.build(n.coerce()); + } + // if the number is a complex number, we can convert both the real and imaginary parts + // to inexact numbers + return SchemeComplex.build( + inexact((n as SchemeComplex).getReal()) as SchemeReal, + inexact((n as SchemeComplex).getImaginary()) as SchemeReal, + ); +}; + // for now, exponentials, square roots and the like will be treated as // inexact functions, and will return inexact results. this allows us to // leverage on the inbuilt javascript Math library. @@ -1485,3 +1639,27 @@ export const angle = (n: SchemeNumber): SchemeNumber => { } return SchemeInteger.EXACT_ZERO; }; + +export const odd$63$ = (n: SchemeInteger): boolean => { + if (!is_number(n)) { + throw new Error("odd?: expected integer"); + } + + if (!is_integer(n)) { + throw new Error("odd?: expected integer"); + } + + return n.getBigInt() % 2n === 1n; +}; + +export const even$63$ = (n: SchemeInteger): boolean => { + if (!is_number(n)) { + throw new Error("even?: expected integer"); + } + + if (!is_integer(n)) { + throw new Error("even?: expected integer"); + } + + return n.getBigInt() % 2n === 0n; +}; From f373827212e42c1ee9b8ebaaf4ece64b9877017b Mon Sep 17 00:00:00 2001 From: s-kybound Date: Sun, 14 Apr 2024 23:29:56 +0800 Subject: [PATCH 21/22] change dependabot ecosystem --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index fe3de70..5f0889c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,7 +5,7 @@ version: 2 updates: - - package-ecosystem: "yarn" # See documentation for possible values + - package-ecosystem: "npm" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "weekly" From d9bd5a6572c02781be00e137021f0b79d3367737 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 14 Apr 2024 23:32:43 +0800 Subject: [PATCH 22/22] Bump js-base64 from 3.7.6 to 3.7.7 (#14) Bumps [js-base64](https://github.com/dankogai/js-base64) from 3.7.6 to 3.7.7. - [Commits](https://github.com/dankogai/js-base64/compare/3.7.6...3.7.7) --- updated-dependencies: - dependency-name: js-base64 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Kyriel Abad --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ffed1eb..1b2df4c 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@types/estree": "^1.0.0", "acorn": "^8.8.2", "acorn-walk": "^8.2.0", - "js-base64": "^3.7.5" + "js-base64": "^3.7.7" }, "scripts": { "build-libs": "npx ts-node ./src/compile-libs.ts", diff --git a/yarn.lock b/yarn.lock index 11c0c89..4d7d0c8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3309,10 +3309,10 @@ jest@^29.7.0: import-local "^3.0.2" jest-cli "^29.7.0" -js-base64@^3.7.5: - version "3.7.6" - resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.7.6.tgz#6ccb5d761b48381fd819f9ce04998866dbcbbc99" - integrity sha512-NPrWuHFxFUknr1KqJRDgUQPexQF0uIJWjeT+2KjEePhitQxQEx5EJBG1lVn5/hc8aLycTpXrDOgPQ6Zq+EDiTA== +js-base64@^3.7.7: + version "3.7.7" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.7.7.tgz#e51b84bf78fbf5702b9541e2cb7bfcb893b43e79" + integrity sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw== js-tokens@^4.0.0: version "4.0.0"