diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..a2bbb7f75 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/.vscode/ + diff --git a/code.pyret.org/package-lock.json b/code.pyret.org/package-lock.json index 7c73e2f13..86f0adb98 100644 --- a/code.pyret.org/package-lock.json +++ b/code.pyret.org/package-lock.json @@ -123,6 +123,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -7835,6 +7836,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz", "integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.20.0" } @@ -8121,6 +8123,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -8174,6 +8177,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -9701,6 +9705,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -11769,6 +11774,7 @@ "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "license": "MIT", + "peer": true, "dependencies": { "iconv-lite": "^0.6.2" } @@ -12745,7 +12751,6 @@ "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", "license": "MIT", - "peer": true, "dependencies": { "loader-utils": "^2.0.0", "schema-utils": "^3.0.0" @@ -12766,7 +12771,6 @@ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", "license": "MIT", - "peer": true, "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", @@ -12781,7 +12785,6 @@ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "license": "MIT", - "peer": true, "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -15124,7 +15127,8 @@ "version": "3.7.1", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/js-md5": { "version": "0.4.2", @@ -17286,6 +17290,7 @@ "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -17724,6 +17729,21 @@ "node": ">=4" } }, + "node_modules/pyret-lang/node_modules/asn1.js": { + "version": "4.10.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/pyret-lang/node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.2", + "inBundle": true, + "license": "MIT" + }, "node_modules/pyret-lang/node_modules/assert": { "version": "1.5.1", "inBundle": true, @@ -17955,25 +17975,6 @@ "node": ">= 0.12" } }, - "node_modules/pyret-lang/node_modules/browserify-sign/node_modules/elliptic": { - "version": "6.6.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/pyret-lang/node_modules/browserify-sign/node_modules/elliptic/node_modules/bn.js": { - "version": "4.12.2", - "inBundle": true, - "license": "MIT" - }, "node_modules/pyret-lang/node_modules/browserify-zlib": { "version": "0.2.0", "inBundle": true, @@ -18116,6 +18117,20 @@ "inBundle": true, "license": "MIT" }, + "node_modules/pyret-lang/node_modules/create-ecdh": { + "version": "4.0.4", + "inBundle": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "node_modules/pyret-lang/node_modules/create-ecdh/node_modules/bn.js": { + "version": "4.12.2", + "inBundle": true, + "license": "MIT" + }, "node_modules/pyret-lang/node_modules/create-hash": { "version": "1.2.0", "inBundle": true, @@ -18166,69 +18181,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/pyret-lang/node_modules/crypto-browserify/node_modules/bn.js": { - "version": "4.12.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/pyret-lang/node_modules/crypto-browserify/node_modules/create-ecdh": { - "version": "4.0.4", - "inBundle": true, - "license": "MIT", - "dependencies": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" - } - }, - "node_modules/pyret-lang/node_modules/crypto-browserify/node_modules/diffie-hellman": { - "version": "5.0.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - } - }, - "node_modules/pyret-lang/node_modules/crypto-browserify/node_modules/elliptic": { - "version": "6.6.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/pyret-lang/node_modules/crypto-browserify/node_modules/miller-rabin": { - "version": "4.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - }, - "bin": { - "miller-rabin": "bin/miller-rabin" - } - }, - "node_modules/pyret-lang/node_modules/crypto-browserify/node_modules/public-encrypt": { - "version": "4.0.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, "node_modules/pyret-lang/node_modules/dash-ast": { "version": "1.0.0", "inBundle": true, @@ -18313,6 +18265,21 @@ "node": ">=0.8.0" } }, + "node_modules/pyret-lang/node_modules/diffie-hellman": { + "version": "5.0.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/pyret-lang/node_modules/diffie-hellman/node_modules/bn.js": { + "version": "4.12.2", + "inBundle": true, + "license": "MIT" + }, "node_modules/pyret-lang/node_modules/domain-browser": { "version": "1.2.0", "inBundle": true, @@ -18343,6 +18310,25 @@ "readable-stream": "^2.0.2" } }, + "node_modules/pyret-lang/node_modules/elliptic": { + "version": "6.6.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/pyret-lang/node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.2", + "inBundle": true, + "license": "MIT" + }, "node_modules/pyret-lang/node_modules/es-define-property": { "version": "1.0.1", "inBundle": true, @@ -18800,6 +18786,23 @@ "safe-buffer": "^5.1.2" } }, + "node_modules/pyret-lang/node_modules/miller-rabin": { + "version": "4.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/pyret-lang/node_modules/miller-rabin/node_modules/bn.js": { + "version": "4.12.2", + "inBundle": true, + "license": "MIT" + }, "node_modules/pyret-lang/node_modules/minimalistic-assert": { "version": "1.0.1", "inBundle": true, @@ -18942,21 +18945,6 @@ "node": ">= 0.10" } }, - "node_modules/pyret-lang/node_modules/parse-asn1/node_modules/asn1.js": { - "version": "4.10.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/pyret-lang/node_modules/parse-asn1/node_modules/bn.js": { - "version": "4.12.2", - "inBundle": true, - "license": "MIT" - }, "node_modules/pyret-lang/node_modules/path-browserify": { "version": "1.0.1", "inBundle": true, @@ -18984,20 +18972,49 @@ } }, "node_modules/pyret-lang/node_modules/pbkdf2": { - "version": "3.1.2", + "version": "3.1.3", "inBundle": true, "license": "MIT", "dependencies": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" + "create-hash": "~1.1.3", + "create-hmac": "^1.1.7", + "ripemd160": "=2.0.1", + "safe-buffer": "^5.2.1", + "sha.js": "^2.4.11", + "to-buffer": "^1.2.0" }, "engines": { "node": ">=0.12" } }, + "node_modules/pyret-lang/node_modules/pbkdf2/node_modules/create-hash": { + "version": "1.1.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "sha.js": "^2.4.0" + } + }, + "node_modules/pyret-lang/node_modules/pbkdf2/node_modules/hash-base": { + "version": "2.0.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1" + } + }, + "node_modules/pyret-lang/node_modules/pbkdf2/node_modules/ripemd160": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "hash-base": "^2.0.0", + "inherits": "^2.0.1" + } + }, "node_modules/pyret-lang/node_modules/possible-typed-array-names": { "version": "1.1.0", "inBundle": true, @@ -19019,6 +19036,24 @@ "inBundle": true, "license": "MIT" }, + "node_modules/pyret-lang/node_modules/public-encrypt": { + "version": "4.0.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/pyret-lang/node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.12.2", + "inBundle": true, + "license": "MIT" + }, "node_modules/pyret-lang/node_modules/punycode": { "version": "1.4.1", "inBundle": true, @@ -19201,15 +19236,22 @@ } }, "node_modules/pyret-lang/node_modules/sha.js": { - "version": "2.4.11", + "version": "2.4.12", "inBundle": true, "license": "(MIT AND BSD-3-Clause)", "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" }, "bin": { "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/pyret-lang/node_modules/shasum-object": { @@ -19461,11 +19503,42 @@ "node": ">=0.6.0" } }, + "node_modules/pyret-lang/node_modules/to-buffer": { + "version": "1.2.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/pyret-lang/node_modules/to-buffer/node_modules/isarray": { + "version": "2.0.5", + "inBundle": true, + "license": "MIT" + }, "node_modules/pyret-lang/node_modules/tty-browserify": { "version": "0.0.1", "inBundle": true, "license": "MIT" }, + "node_modules/pyret-lang/node_modules/typed-array-buffer": { + "version": "1.0.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/pyret-lang/node_modules/typedarray": { "version": "0.0.6", "inBundle": true, @@ -19754,6 +19827,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-15.7.0.tgz", "integrity": "sha512-5/MMRYmpmM0sMTHGLossnJCrmXQIiJilD6y3YN3TzAwGFj6zdnMtFv6xmi65PHKRV+pehIHpT7oy67Sr6s9AHA==", "license": "MIT", + "peer": true, "dependencies": { "create-react-class": "^15.6.0", "fbjs": "^0.8.9", @@ -20336,6 +20410,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -23051,6 +23126,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.98.0.tgz", "integrity": "sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==", "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.6", @@ -23098,6 +23174,7 @@ "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^2.1.1", diff --git a/code.pyret.org/src/web/js/ide.js b/code.pyret.org/src/web/js/ide.js index fe9bb6128..8b3f876f4 100644 --- a/code.pyret.org/src/web/js/ide.js +++ b/code.pyret.org/src/web/js/ide.js @@ -1,4 +1,3 @@ -import React from 'react'; import { init, LanguageError, UserError, HoverHighlight } from 'pyret-ide'; import seedrandom from 'seedrandom'; import sExpression from 's-expression'; diff --git a/code.pyret.org/src/web/js/output-ui.js b/code.pyret.org/src/web/js/output-ui.js index acba54a8d..87bef7851 100644 --- a/code.pyret.org/src/web/js/output-ui.js +++ b/code.pyret.org/src/web/js/output-ui.js @@ -520,7 +520,7 @@ while (todo.length > 0) { var first = todo.pop(); if (runtime.hasField(first, "l")) { - // not every AST item has an "l" field (eg. s-global, s-type-global, s-base) + // not every AST item has an "l" field (eg. s-defined-module, s-defined-value, s-defined-var, etc) var l = runtime.getField(first, "l"); if (isSrcloc.app(l)) { // just extra checking; should be unnecessary if (runtime.equal_always(l, loc)) { // stack-safe because srclocs are flat data diff --git a/docs/src/trove/ast.js.rkt b/docs/src/trove/ast.js.rkt index 378598239..5ec054104 100644 --- a/docs/src/trove/ast.js.rkt +++ b/docs/src/trove/ast.js.rkt @@ -262,7 +262,7 @@ } } @constr-spec["s-global"]{ - @members{@member-spec["s"]} + @members{@member-spec["l"] @member-spec["s"]} @with-members{ @method-spec[ "to-compiled-source" @@ -297,7 +297,7 @@ } } @constr-spec["s-type-global"]{ - @members{@member-spec["s"]} + @members{@member-spec["l"] @member-spec["s"]} @with-members{ @method-spec[ "to-compiled-source" @@ -332,7 +332,7 @@ } } @constr-spec["s-atom"]{ - @members{@member-spec["base"] @member-spec["serial"]} + @members{@member-spec["l"] @member-spec["base"] @member-spec["serial"]} @with-members{ @method-spec[ "to-compiled-source" diff --git a/lang/package-lock.json b/lang/package-lock.json index 08bc17c0e..be610f654 100644 --- a/lang/package-lock.json +++ b/lang/package-lock.json @@ -45,6 +45,7 @@ "ws": "^8.18.0" }, "devDependencies": { + "@types/ws": "^8.18.1", "http-server": "^14.1.1", "jest": "^30.0.4" } @@ -94,6 +95,7 @@ "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -1388,6 +1390,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", @@ -2342,6 +2354,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001726", "electron-to-chromium": "^1.5.173", diff --git a/lang/package.json b/lang/package.json index b79e78ccf..561083ac6 100644 --- a/lang/package.json +++ b/lang/package.json @@ -65,6 +65,7 @@ }, "homepage": "https://github.com/brownplt/pyret-lang", "devDependencies": { + "@types/ws": "^8.18.1", "http-server": "^14.1.1", "jest": "^30.0.4" } diff --git a/lang/src/arr/compiler/anf-loop-compiler.arr b/lang/src/arr/compiler/anf-loop-compiler.arr index 24cc05cc8..fee2d61f3 100644 --- a/lang/src/arr/compiler/anf-loop-compiler.arr +++ b/lang/src/arr/compiler/anf-loop-compiler.arr @@ -133,7 +133,7 @@ effective-ids = D.make-mutable-string-dict() fun fresh-id(id :: A.Name) -> A.Name: base-name = if A.is-s-type-global(id): id.tosourcestring() else: id.toname() end no-hyphens = string-replace(base-name, "-", "$") - n = js-names.make-atom(no-hyphens) + n = js-names.make-atom(id.l, no-hyphens) if effective-ids.has-key-now(n.tosourcestring()) block: #awkward name collision! fresh-id(id) else: @@ -2226,11 +2226,11 @@ fun compile-module(self, l, prog-provides, imports-in, prog, freevars, provides, global-binds = for CL.map_list(n from module-and-global-binds.is-false): { maybe-origin; which } = cases(A.Name) n: - | s-module-global(s) => + | s-module-global(_, s) => { env.origin-by-module-name(n.toname()); "modules"} - | s-global(s) => + | s-global(_, s) => { env.origin-by-value-name(n.toname()); "values"} - | s-type-global(s) => + | s-type-global(_, s) => { env.origin-by-type-name(n.toname()); "types"} end @@ -2291,7 +2291,7 @@ fun compile-module(self, l, prog-provides, imports-in, prog, freevars, provides, module-id = fresh-id(compiler-name(l.source)).tosourcestring() module-ref = lam(name): j-bracket(rt-field("modules"), j-str(name)) end input-ids = CL.map_list(lam(i): - if A.is-s-atom(i) and (i.base == "$import"): js-names.make-atom("$$import") + if A.is-s-atom(i) and (i.base == "$import"): js-names.make-atom(i.l, "$$import") else: js-id-of(compiler-name(i.toname())) end end, mod-ids) diff --git a/lang/src/arr/compiler/anf.arr b/lang/src/arr/compiler/anf.arr index a05807000..f5e0a6348 100644 --- a/lang/src/arr/compiler/anf.arr +++ b/lang/src/arr/compiler/anf.arr @@ -18,7 +18,7 @@ names = A.global-names flat-prim-app = A.prim-app-info-c(false) fun mk-id(loc, base): - t = names.make-atom(base) + t = names.make-atom(loc, base) { id: t, id-b: bind(loc, t), id-e: N.a-id(loc, t) } end @@ -325,7 +325,7 @@ fun anf(e :: A.Expr, k :: ANFCont) -> N.AExpr: | s-tuple-get(l, tup, index, index-loc) => anf-name(tup, "anf_tuple_get", lam(v): k(N.a-tuple-get(l, v, index)) end) | s-array(l, values) => - array-id = names.make-atom("anf_array") + array-id = names.make-atom(l, "anf_array") N.a-let( l, bind(l, array-id), @@ -354,7 +354,7 @@ fun anf(e :: A.Expr, k :: ANFCont) -> N.AExpr: | a-blank => A.s-let-expr(l, let-binds, body, blocky) | a-any(_) => A.s-let-expr(l, let-binds, body, blocky) | else => - a = A.global-names.make-atom("inline_body") + a = A.global-names.make-atom(l, "inline_body") A.s-let-expr(l, let-binds + [list: A.s-let-bind(body.l, A.s-bind(l, false, a, ann), body)], diff --git a/lang/src/arr/compiler/ast-anf.arr b/lang/src/arr/compiler/ast-anf.arr index 140b0f614..dd6e82981 100644 --- a/lang/src/arr/compiler/ast-anf.arr +++ b/lang/src/arr/compiler/ast-anf.arr @@ -781,8 +781,8 @@ fun freevars-e(expr :: AExpr) -> FrozenNameDict: where: d = dummy-loc n = A.global-names.make-atom - x = n("x") - y = n("y") + x = n(d, "x") + y = n(d, "y") freevars-e( a-let(d, a-bind(d, x, A.a-blank), a-val(d, a-num(d, 4)), a-lettable(d, a-val(d, a-id(d, y))))).keys-list() is [list: y.key()] diff --git a/lang/src/arr/compiler/ast-util.arr b/lang/src/arr/compiler/ast-util.arr index 4e63d40d9..dbb6b9ad0 100644 --- a/lang/src/arr/compiler/ast-util.arr +++ b/lang/src/arr/compiler/ast-util.arr @@ -150,12 +150,12 @@ end fun binding-type-env-from-env(env): for SD.fold-keys(acc from SD.make-string-dict(), name from env.globals.types): - acc.set(A.s-type-global(name).key(), e-bind(A.dummy-loc, false, b-typ)) + acc.set(A.s-type-global(A.dummy-loc, name).key(), e-bind(A.dummy-loc, false, b-typ)) end end fun binding-env-from-env(env): for SD.fold-keys(acc from SD.make-string-dict(), name from env.globals.values): - acc.set(A.s-global(name).key(), e-bind(A.dummy-loc, false, b-prim(name))) + acc.set(A.s-global(A.dummy-loc, name).key(), e-bind(A.dummy-loc, false, b-prim(name))) end end @@ -526,7 +526,7 @@ inline-lams = A.default-map-visitor.{ cases(A.Expr) f: | s-lam(l, _, _, args, ann, _, body, _, _, _) => if (args.length() == exps.length()): - a = A.global-names.make-atom("inline_body") + a = A.global-names.make-atom(l, "inline_body") let-binds = for lists.map2(arg from args, exp from exps): A.s-let-bind(arg.l, arg, exp.visit(self)) end @@ -897,8 +897,8 @@ strip-annotations-visitor = A.default-map-visitor.{ fun make-renamer(replacements :: SD.StringDict): A.default-map-visitor.{ - method s-atom(self, base, serial): - a = A.s-atom(base, serial) + method s-atom(self, l, base, serial): + a = A.s-atom(l, base, serial) k = a.key() if replacements.has-key(k): replacements.get-value(k) @@ -937,7 +937,7 @@ fun wrap-extra-imports(p :: A.Program, env :: CS.ExtraImports) -> A.Program: |# l = A.dummy-loc for fold(lst from empty, i from imports): - name-to-use = if i.as-name == "_": A.global-names.make-atom("$extra-import") else: A.s-name(l, i.as-name) end + name-to-use = if i.as-name == "_": A.global-names.make-atom(l, "$extra-import") else: A.s-name(l, i.as-name) end ast-dep = cases(CS.Dependency) i.dependency: | builtin(name) => A.s-const-import(p.l, name) | dependency(protocol, args) => A.s-special-import(p.l, protocol, args) @@ -990,7 +990,7 @@ fun ann-to-typ(a :: A.Ann, uri, compile-env) -> T.Type: | a-any(l) => T.t-top(l, false) | a-name(l, id) => cases(A.Name) id: - | s-type-global(name) => + | s-type-global(_, name) => cases(Option) compile-env.globals.types.get(name): | none => raise("Name not found in globals.types: " + name) @@ -998,7 +998,7 @@ fun ann-to-typ(a :: A.Ann, uri, compile-env) -> T.Type: # ```include from string-dict: type StringDict as SD end``` T.t-name(T.module-uri(origin.uri-of-definition), origin.original-name, l, false) end - | s-atom(_, _) => T.t-name(T.module-uri(uri), id, l, false) + | s-atom(_, _, _) => T.t-name(T.module-uri(uri), id, l, false) | else => raise("Bad name found in ann-to-typ: " + id.key()) end | a-type-var(l, id) => @@ -1458,7 +1458,7 @@ fun get-typed-provides(resolved, typed :: TCS.Typed, uri :: URI, compile-env :: transformer = lam(t): cases(T.Type) t: | t-name(origin, name, l, inferred) => - T.t-name(origin, A.s-type-global(name.toname()), l, inferred) + T.t-name(origin, A.s-type-global(l, name.toname()), l, inferred) | else => t end end @@ -1546,3 +1546,80 @@ fun get-typed-provides(resolved, typed :: TCS.Typed, uri :: URI, compile-env :: end end end + + +fun find-name-key-by-srcloc(resolved :: A.Program, srcloc :: Loc) -> Option block: + var result-mangled-name = none + visitor = A.default-iter-visitor.{ + # Use sites: s-id(use-l, atom/global) — match on the outer l, return inner key + method s-id(self, l, id): + if l == srcloc block: + result-mangled-name := some(id.key()) + false + else: + true + end + end, + method s-id-var(self, l, id): + if l == srcloc block: + result-mangled-name := some(id.key()) + false + else: + true + end + end, + method s-id-letrec(self, l, id, safe): + if l == srcloc block: + result-mangled-name := some(id.key()) + false + else: + true + end + end, + # Binding sites: s-atom/s-global appear directly with bind-l — only match + # if the user clicked exactly on a binding site (rare but possible) + method s-atom(self, l, base, serial): + if l == srcloc block: + result-mangled-name := some(A.s-atom(l, base, serial).key()) + false + else: + true + end + end, + method s-global(self, l, s): + if l == srcloc block: + result-mangled-name := some(A.s-global(l, s).key()) + false + else: + true + end + end + } + + resolved.visit(visitor) + result-mangled-name +end + +is-s-name = A.is-s-name +fun find-name-at(prog :: A.Program, line :: Number, col :: Number) -> Option block: + var result-name = none + visitor = A.default-iter-visitor.{ + method s-name(self, l, s): + cases (Loc) l: + | builtin(_) => true + | srcloc(_, sl, sc, _, el, ec, _) => + if (sl <= line) and (line <= el) and (sc <= col) and (col <= ec) block: + result-name := some(A.s-name(l, s)) + false + else: + true + end + end + end + } + + prog.visit(visitor) + result-name +end + + diff --git a/lang/src/arr/compiler/cli-module-loader.arr b/lang/src/arr/compiler/cli-module-loader.arr index 4b9dbf414..cdcf64044 100644 --- a/lang/src/arr/compiler/cli-module-loader.arr +++ b/lang/src/arr/compiler/cli-module-loader.arr @@ -25,64 +25,39 @@ import file("locators/jsfile.arr") as JSF import file("locators/npm.arr") as NPM import file("js-of-pyret.arr") as JSP -j-fun = J.j-fun -j-var = J.j-var -j-id = J.j-id -j-method = J.j-method -j-block = J.j-block -j-true = J.j-true -j-false = J.j-false -j-num = J.j-num -j-str = J.j-str -j-return = J.j-return -j-assign = J.j-assign -j-if = J.j-if -j-if1 = J.j-if1 -j-new = J.j-new -j-app = J.j-app -j-list = J.j-list -j-obj = J.j-obj -j-dot = J.j-dot -j-bracket = J.j-bracket -j-field = J.j-field -j-dot-assign = J.j-dot-assign -j-bracket-assign = J.j-bracket-assign -j-try-catch = J.j-try-catch -j-throw = J.j-throw -j-expr = J.j-expr -j-binop = J.j-binop -j-and = J.j-and -j-lt = J.j-lt -j-eq = J.j-eq -j-neq = J.j-neq -j-geq = J.j-geq -j-unop = J.j-unop -j-decr = J.j-decr -j-incr = J.j-incr -j-not = J.j-not -j-instanceof = J.j-instanceof -j-ternary = J.j-ternary -j-null = J.j-null -j-parens = J.j-parens -j-switch = J.j-switch -j-case = J.j-case -j-default = J.j-default -j-label = J.j-label -j-break = J.j-break -j-while = J.j-while -j-for = J.j-for - -clist = C.clist +include from J: + data JStmt, + data JExpr, + data JBlock, +end -type Loadable = CS.Loadable +include from E: + data Either +end +include from CS: + type Loadable +end -type Either = E.Either +clist = C.clist fun uri-to-path(uri, name): name + "-" + crypto.sha256(uri) end +type CacheManager = { + cached-available :: (String, String, String, Number -> Option), + get-cached :: (String, String, String, Any -> Any), + get-cached-if-available :: (String, Any -> Any), + get-loadable :: (String, List, Any, Any -> Option), + set-loadable :: (String, Any, Any -> String), + get-builtin-locator :: (String, List, String -> Any), + set-surface-ast :: (String, Any -> Nothing), + get-surface-ast :: (String -> Option), + set-named-result :: (String, Any -> Nothing), + get-named-result :: (String -> Option) +} + # NOTE(joe): This is just a little one-off type to represent a simple # situation: Builtin pure-JS files are stored in single files with a hash # followed by .js, while builtin Pyret files are stored in two files – one with @@ -100,7 +75,7 @@ end # it's fine to pass a modified time of 0 to indicate that we're always happy # with the compiled version of the file. -fun cached-available(basedir, uri, name, modified-time) -> Option: +fun file-cached-available(basedir, uri, name, modified-time) -> Option: saved-path = Filesystem.join(basedir, uri-to-path(uri, name)) if (Filesystem.exists(saved-path + "-static.js") and @@ -114,7 +89,17 @@ fun cached-available(basedir, uri, name, modified-time) -> Option: end end -fun get-cached(basedir, uri, name, cache-type): +fun mem-cached-available(store, basedir, uri, name, _) -> Option: + key = basedir + uri + name + if store.has-key-now(key): + some(nothing) + else: + none + end +end + + +fun file-get-cached(basedir, uri, name, cache-type): saved-path = Filesystem.join(basedir, uri-to-path(uri, name)) {static-path; module-path} = cases(CachedType) cache-type: # NOTE(joe): leaving off .js because builtin-raw-locator below @@ -172,16 +157,70 @@ fun get-cached(basedir, uri, name, cache-type): } end -fun get-cached-if-available(basedir, loc) block: - get-cached-if-available-known-mtimes(basedir, loc, [SD.string-dict:]) +fun mem-get-cached(store, _, uri, name, _): + { + method get-uncached(_): none end, + method needs-compile(_, _): false end, + method get-modified-time(_): 0 end, + method get-options(_, options): options.{ checks: "none" } end, + method get-module(_): + cases(Option) store.get-now(uri): + | some(entry) => + cases(Option) entry.surface-ast: + | some(ast) => CL.pyret-ast(ast) + | none => raise("No cached source for module " + uri) + end + | none => raise("No cached source for module " + uri) + end + end, + method get-extra-imports(_): + if CL.is-builtin-module(uri): CS.minimal-imports + else: CS.standard-imports + end + end, + method get-dependencies(_): + cases(Option) store.get-now(uri): + | some(entry) => + cases(Option) entry.surface-ast: + | some(ast) => + if CL.is-builtin-module(uri): + CL.get-dependencies(CL.pyret-ast(ast), uri) + else: + CL.get-standard-dependencies(CL.pyret-ast(ast), uri) + end + | none => empty + end + | none => empty + end + end, + method get-native-modules(_): [list:] end, + method get-globals(_): CS.standard-globals end, + method uri(_): uri end, + method name(_): name end, + method set-compiled(_, _, _): nothing end, + method get-compiled(_): + cases(Option) store.get-now(uri): + | some(entry) => entry.loadable + | none => none + end + end, + method _equals(self, other, req-eq): + req-eq(self.uri(), other.uri()) + end + } +end + + +fun get-cached-if-available(cache-manager, basedir, loc) block: + get-cached-if-available-known-mtimes(cache-manager, basedir, loc, [SD.string-dict:]) end -fun get-cached-if-available-known-mtimes(basedir, loc, max-dep-times) block: +fun get-cached-if-available-known-mtimes(cache-manager, basedir, loc, max-dep-times) block: saved-path = Filesystem.join(basedir, uri-to-path(loc.uri(), loc.name())) dependency-based-mtime = if max-dep-times.has-key(loc.uri()): max-dep-times.get-value(loc.uri()) else: loc.get-modified-time() end - cached-type = cached-available(basedir, loc.uri(), loc.name(), dependency-based-mtime) + cached-type = cache-manager.cached-available(basedir, loc.uri(), loc.name(), dependency-based-mtime) cases(Option) cached-type: | none => cases(Option) loc.get-uncached(): @@ -189,54 +228,53 @@ fun get-cached-if-available-known-mtimes(basedir, loc, max-dep-times) block: | none => loc end - | some(ct) => get-cached(basedir, loc.uri(), loc.name(), ct).{ + | some(ct) => cache-manager.get-cached(basedir, loc.uri(), loc.name(), ct).{ method get-uncached(self): some(loc) end } end end -fun get-file-locator(basedir, real-path): +fun get-file-locator(cache-manager, basedir, real-path): loc = FL.file-locator(real-path, CS.standard-globals) - get-cached-if-available(basedir, loc) + get-cached-if-available(cache-manager, basedir, loc) end -fun get-builtin-locator(basedir, read-only-basedirs, modname): +fun file-get-builtin-locator(cache-manager, basedir, read-only-basedirs, modname): all-dirs = read-only-basedirs first-available = for find(rob from all-dirs): - is-some(cached-available(rob, "builtin://" + modname, modname, 0)) + is-some(cache-manager.cached-available(rob, "builtin://" + modname, modname, 0)) end cases(Option) first-available: | none => cases(Option) BL.maybe-make-builtin-locator(modname) block: | some(loc) => - get-cached-if-available(basedir, loc) + get-cached-if-available(cache-manager, basedir, loc) | none => raise("Could not find builtin module " + modname + " in any of " + all-dirs.join-str(", ")) end | some(ro-basedir) => - ca = cached-available(ro-basedir, "builtin://" + modname, modname, 0).or-else(split) - get-cached(ro-basedir, "builtin://" + modname, modname, ca) + ca = cache-manager.cached-available(ro-basedir, "builtin://" + modname, modname, 0).or-else(split) + cache-manager.get-cached(ro-basedir, "builtin://" + modname, modname, ca) end end -fun get-builtin-test-locator(basedir, modname): +fun get-builtin-test-locator(cache-manager, basedir, modname): loc = BL.make-builtin-locator(modname).{ method uri(_): "builtin-test://" + modname end } - get-cached-if-available(basedir, loc) + cache-manager.get-cached-if-available(basedir, loc) end -fun get-loadable(basedir, read-only-basedirs, l, max-dep-times) -> Option: +fun get-loadable-impl(cache-manager, basedir, read-only-basedirs, l, max-dep-times) -> Option: locuri = l.locator.uri() -# cached = cached-available(basedir, l.locator.uri(), l.locator.name(), l.locator.get-modified-time()) first-available = for find(rob from link(basedir, read-only-basedirs)): - is-some(cached-available(rob, l.locator.uri(), l.locator.name(), max-dep-times.get-value(locuri))) + is-some(cache-manager.cached-available(rob, l.locator.uri(), l.locator.name(), max-dep-times.get-value(locuri))) end cases(Option) first-available block: | none => none - | some(found-basedir) => - c = cached-available(found-basedir, l.locator.uri(), l.locator.name(), max-dep-times.get-value(locuri)) + | some(found-basedir) => + c = cache-manager.cached-available(found-basedir, l.locator.uri(), l.locator.name(), max-dep-times.get-value(locuri)) saved-path = Filesystem.join(found-basedir, uri-to-path(locuri, l.locator.name())) {static-path; module-path} = cases(CachedType) c.or-else(single-file): | split => @@ -298,6 +336,7 @@ end type CLIContext = { current-load-path :: String, cache-base-dir :: String, + compiled-read-only-dirs :: List, url-file-mode :: CS.UrlFileMode } @@ -316,17 +355,18 @@ fun maybe-add-slash(s): end end -fun locate-file(ctxt :: CLIContext, rel-path :: String): +fun locate-file(cache-manager :: CacheManager, ctxt :: CLIContext, rel-path :: String): clp = ctxt.current-load-path real-path = get-real-path(clp, rel-path) new-context = ctxt.{current-load-path: Filesystem.dirname(real-path)} if Filesystem.exists(real-path): - some(CL.located(get-file-locator(ctxt.cache-base-dir, real-path), new-context)) + some(CL.located(get-file-locator(cache-manager, ctxt.cache-base-dir, real-path), new-context)) else: none end end -fun module-finder(ctxt :: CLIContext, dep :: CS.Dependency): +fun module-finder-with(cache-manager :: CacheManager, ctxt :: CLIContext, dep :: CS.Dependency): + shadow locate-file = locate-file(cache-manager, _, _) cases(CS.Dependency) dep: | dependency(protocol, args) => if protocol == "file": @@ -367,7 +407,7 @@ fun module-finder(ctxt :: CLIContext, dep :: CS.Dependency): new-context = ctxt.{current-load-path: Filesystem.dirname(real-path)} CL.located(locator, new-context) else if protocol == "builtin-test": - l = get-builtin-test-locator(ctxt.cache-base-dir, args.first) + l = get-builtin-test-locator(cache-manager, ctxt.cache-base-dir, args.first) force-check-mode = l.{ method get-options(self, options): options.{ checks: "all", type-check: false } @@ -393,10 +433,11 @@ fun module-finder(ctxt :: CLIContext, dep :: CS.Dependency): raise("Unknown import type: " + protocol) end | builtin(modname) => - CL.located(get-builtin-locator(ctxt.cache-base-dir, ctxt.compiled-read-only-dirs, modname), ctxt) + CL.located(file-get-builtin-locator(cache-manager, ctxt.cache-base-dir, ctxt.compiled-read-only-dirs, modname), ctxt) end end + default-start-context = { current-load-path: Filesystem.resolve("./"), cache-base-dir: Filesystem.resolve("./compiled"), @@ -413,6 +454,7 @@ default-test-context = { fun compile(path, options): base-module = CS.dependency("file", [list: path]) + shadow module-finder = module-finder-with(options.cache-manager, _, _) base = module-finder({ current-load-path: Filesystem.resolve(options.base-dir), cache-base-dir: options.compiled-cache, @@ -443,6 +485,120 @@ fun propagate-exit(result) block: end end +fun make-file-cache() -> CacheManager: + { + cached-available: file-cached-available, + get-cached: file-get-cached, + method get-cached-if-available(self, basedir, loc): + get-cached-if-available(self, basedir, loc) + end, + method get-loadable(self, basedir, read-only-basedirs, l, max-dep-times): + get-loadable-impl(self, basedir, read-only-basedirs, l, max-dep-times) + end, + method set-loadable(self, basedir, locator, loadable): + set-loadable(basedir, locator, loadable) + end, + method get-builtin-locator(self, basedir, read-only-basedirs, modname): + file-get-builtin-locator(self, basedir, read-only-basedirs, modname) + end, + method set-surface-ast(self, _, _): nothing end, + method get-surface-ast(self, _): none end, + method set-named-result(self, _, _): nothing end, + method get-named-result(self, _): none end, + } +end + +fun make-in-memory-cache() -> CacheManager: + store = [SD.mutable-string-dict:] + + fun get-entry(uri): + store.get-now(uri) + end + + fun update-entry(uri, updater): + existing = cases(Option) store.get-now(uri): + | some(v) => v + | none => { surface-ast: none, named-result: none, loadable: none } + end + store.set-now(uri, updater(existing)) + end + + { + # TODO: falling back to file-based lookup for builtins is a workaround; + # ideally builtins would be loaded into the in-memory store directly + cached-available: lam(basedir, uri, name, mtime): + if store.has-key-now(uri): some(nothing) + else if string-index-of(uri, "builtin://") == 0: + file-cached-available(basedir, uri, name, mtime) + else: none + end + end, + get-cached: lam(basedir, uri, name, cache-type): + if store.has-key-now(uri): + mem-get-cached(store, basedir, uri, name, cache-type) + else if string-index-of(uri, "builtin://") == 0: + file-get-cached(basedir, uri, name, cache-type) + else: + raise("No in-memory cache entry for non-builtin module " + uri) + end + end, + method get-cached-if-available(self, _, loc): + if store.has-key-now(loc.uri()): + self.get-cached("", loc.uri(), loc.name(), nothing).{ + method get-uncached(_): some(loc) end + } + else: + cases(Option) loc.get-uncached(): + | some(shadow loc) => loc + | none => loc + end + end + end, + method get-loadable(self, basedir, read-only-basedirs, l, max-dep-times): + cases(Option) get-entry(l.locator.uri()): + | some(e) => e.loadable + | none => + # Fall back to file-based lookup for builtins so they land in + # starter-modules and skip compile-module entirely. + if string-index-of(l.locator.uri(), "builtin://") == 0: + get-loadable-impl(self, basedir, read-only-basedirs, l, max-dep-times) + else: + none + end + end + end, + method set-loadable(self, _, locator, loadable) block: + update-entry(locator.uri(), lam(e): e.{loadable: some(loadable)} end) + locator.uri() + end, + # TODO: builtins should be loaded into the in-memory store rather than + # going through file-based lookup. Blocked on having a serialization + # format for Loadable that doesn't depend on the JS file infrastructure + # (builtin-raw-locator / -static.js / -module.js). + method get-builtin-locator(self, basedir, read-only-basedirs, modname): + file-get-builtin-locator(self, basedir, read-only-basedirs, modname) + end, + method set-surface-ast(self, uri, ast): + update-entry(uri, lam(e): e.{surface-ast: some(ast)} end) + end, + method get-surface-ast(self, uri): + cases(Option) get-entry(uri): + | some(e) => e.surface-ast + | none => none + end + end, + method set-named-result(self, uri, named-result): + update-entry(uri, lam(e): e.{named-result: some(named-result)} end) + end, + method get-named-result(self, uri): + cases(Option) get-entry(uri): + | some(e) => e.named-result + | none => none + end + end, + } +end + fun run(path, options, subsequent-command-line-arguments): stats = SD.make-mutable-string-dict() maybe-program = build-program(path, options, stats) @@ -461,10 +617,33 @@ fun run(path, options, subsequent-command-line-arguments): end end +# TODO: this shares a lot of commonality with `build-program`. +fun compile-for-query(options, program) block: + base-module = CS.dependency("file", [list: program]) + shadow module-finder = module-finder-with(options.cache-manager, _, _) + base = module-finder({ + current-load-path: Filesystem.resolve(options.base-dir), + cache-base-dir: options.compiled-cache, + compiled-read-only-dirs: options.compiled-read-only.map(Filesystem.resolve), + url-file-mode: options.url-file-mode + }, base-module) + wl = CL.compile-worklist(module-finder, base.locator, base.context) + # starter-modules = CL.modules-from-worklist(wl, + # lam(l, _): cache-manager.get-loadable("", empty, l, [SD.string-dict:]) end) + starter-modules = CL.modules-from-worklist(wl, options.cache-manager.get-loadable(options.compiled-cache, options.compiled-read-only.map(Filesystem.resolve), _, _)) + CL.compile-program-with(wl, starter-modules, options) + base.locator.uri() +end + fun build-program(path, options, stats) block: doc: ```Returns the program as a JavaScript AST of module list and dependency map, and its native dependencies as a list of strings``` + # TODO: this should probably refactored into default opts + shadow options = options.{ + cache-manager: if options.query: make-in-memory-cache() else: make-file-cache() end + } + print-progress-clearing = lam(s, to-clear): when options.display-progress: options.log(s, to-clear) @@ -478,6 +657,7 @@ fun build-program(path, options, stats) block: end print-progress(str) base-module = CS.dependency("file", [list: path]) + shadow module-finder = module-finder-with(options.cache-manager, _, _) base = module-finder({ current-load-path: Filesystem.resolve(options.base-dir), cache-base-dir: options.compiled-cache, @@ -490,12 +670,13 @@ fun build-program(path, options, stats) block: max-dep-times = CL.dep-times-from-worklist(wl) shadow wl = for map(located from wl): - located.{ locator: get-cached-if-available-known-mtimes(options.compiled-cache, located.locator, max-dep-times) } + located.{ locator: get-cached-if-available-known-mtimes(options.cache-manager, options.compiled-cache, located.locator, max-dep-times) } end clear-and-print("Loading existing compiled modules...") - starter-modules = CL.modules-from-worklist(wl, get-loadable(options.compiled-cache, options.compiled-read-only.map(Filesystem.resolve), _, _)) + starter-modules = CL.modules-from-worklist(wl, + options.cache-manager.get-loadable(options.compiled-cache, options.compiled-read-only.map(Filesystem.resolve), _, _)) cached-modules = starter-modules.count-now() total-modules = wl.length() - cached-modules @@ -550,7 +731,7 @@ fun build-runnable-standalone(path, require-config-path, outfile, options) block | none => nothing | some(tb) => cases(JSON.JSON) tb: - | j-arr(l) => + | j-arr(l) => BL.set-typable-builtins(l.map(_.s)) | else => raise("Expected a list for typable-builtins, but got: " + to-repr(tb)) end @@ -578,7 +759,7 @@ fun build-runnable-standalone(path, require-config-path, outfile, options) block end ans = make-standalone-res and html-res - + when options.collect-times block: standalone-end = time-now() - stats.get-value-now("standalone") stats.set-now("standalone", [list: "Outputing JS: " + tostring(standalone-end) + "ms"]) @@ -608,3 +789,7 @@ fun build-require-standalone(path, options): print(prog.to-ugly-source()) end + + +# backwards compatibility +module-finder = module-finder-with(make-file-cache(), _, _) diff --git a/lang/src/arr/compiler/compile-lib.arr b/lang/src/arr/compiler/compile-lib.arr index bcaea4b08..7b013c77c 100644 --- a/lang/src/arr/compiler/compile-lib.arr +++ b/lang/src/arr/compiler/compile-lib.arr @@ -24,6 +24,18 @@ import file("type-check.arr") as T import file("desugar-check.arr") as CH import file("resolve-scope.arr") as RS +include from J: + data JExpr, + data JStmt, + data JCase, + data JBinop, + data JField, +end + +include from E: + data Either +end + data CompilationPhase: | start(time :: Number) | phase(name :: String, result :: Any, time :: Number, prev :: CompilationPhase) @@ -42,57 +54,6 @@ sharing: end end -j-fun = J.j-fun -j-var = J.j-var -j-id = J.j-id -j-method = J.j-method -j-block = J.j-block -j-true = J.j-true -j-false = J.j-false -j-num = J.j-num -j-str = J.j-str -j-return = J.j-return -j-assign = J.j-assign -j-if = J.j-if -j-if1 = J.j-if1 -j-new = J.j-new -j-app = J.j-app -j-list = J.j-list -j-obj = J.j-obj -j-dot = J.j-dot -j-bracket = J.j-bracket -j-field = J.j-field -j-dot-assign = J.j-dot-assign -j-bracket-assign = J.j-bracket-assign -j-try-catch = J.j-try-catch -j-throw = J.j-throw -j-expr = J.j-expr -j-binop = J.j-binop -j-and = J.j-and -j-lt = J.j-lt -j-eq = J.j-eq -j-neq = J.j-neq -j-geq = J.j-geq -j-unop = J.j-unop -j-decr = J.j-decr -j-incr = J.j-incr -j-not = J.j-not -j-instanceof = J.j-instanceof -j-ternary = J.j-ternary -j-null = J.j-null -j-parens = J.j-parens -j-switch = J.j-switch -j-case = J.j-case -j-default = J.j-default -j-label = J.j-label -j-break = J.j-break -j-while = J.j-while -j-for = J.j-for - - -left = E.left -right = E.right -type Either = E.Either mtd = [SD.string-dict:] @@ -296,6 +257,7 @@ end type CompiledProgram = {loadables :: List, modules :: SD.MutableStringDict} +# TODO: use cache-manager here? fun compile-program-with(worklist :: List, modules, options) -> CompiledProgram block: cache = modules loadables = for map(w from worklist): @@ -363,6 +325,7 @@ fun compile-module(locator :: Locator, provide-map :: SD.StringDict, module | pyret-ast(module-ast) => module-ast end + options.cache-manager.set-surface-ast(locator.uri(), ast) var ret = start(time-now()) fun add-phase(name, value) block: if options.collect-all: @@ -427,6 +390,7 @@ fun compile-module(locator :: Locator, provide-map :: SD.StringDict, module var desugared = D.desugar(spied) spied := nothing named-result.env.bindings.merge-now(desugared.new-binds) + options.cache-manager.set-named-result(locator.uri(), named-result) # ...in order to be checked for bad assignments here any-errors := RS.check-unbound-ids-bad-assignments(desugared.ast, named-result, env) add-phase("Fully desugared", desugared.ast) @@ -464,17 +428,24 @@ fun compile-module(locator :: Locator, provide-map :: SD.StringDict, module when not(options.type-check) block: provides := AU.get-named-provides(named-result, locator.uri(), env) end - {final-provides; cr} = JSP.trace-make-compiled-pyret(add-phase, cleaned, env, named-result.env, provides, options) - cleaned := nothing - canonical-provides = AU.canonicalize-provides(final-provides, env) - #| - spy "compile-lib:canonicalize-provides": - final-provides, - canonical-provides + if options.query block: + { + module-as-string(provides, env, named-result.env, CS.ok(cleaned)); + if options.collect-all or options.collect-times: ret.tolist() else: empty end + } + else: + {final-provides; cr} = JSP.trace-make-compiled-pyret(add-phase, cleaned, env, named-result.env, provides, options) + cleaned := nothing + canonical-provides = AU.canonicalize-provides(final-provides, env) + #| + spy "compile-lib:canonicalize-provides": + final-provides, + canonical-provides + end + |# + mod-result = module-as-string(canonical-provides, env, named-result.env, cr) + {mod-result; if options.collect-all or options.collect-times: ret.tolist() else: empty end} end - |# - mod-result = module-as-string(canonical-provides, env, named-result.env, cr) - {mod-result; if options.collect-all or options.collect-times: ret.tolist() else: empty end} | err(_) => { module-as-string(provides, env, CS.computed-none, type-checked); if options.collect-all or options.collect-times: @@ -569,11 +540,11 @@ fun make-standalone(wl, compiled, options): check-str = if not(options.check-mode): "none" else: options.checks end - runtime-options = J.j-obj( + runtime-options = j-obj( [C.clist: - J.j-field("checksFormat", j-str(options.checks-format)), - J.j-field("checks", j-str(check-str)), - J.j-field("disableAnnotationChecks", + j-field("checksFormat", j-str(options.checks-format)), + j-field("checks", j-str(check-str)), + j-field("disableAnnotationChecks", if options.runtime-annotations: j-false else: diff --git a/lang/src/arr/compiler/compile-structs.arr b/lang/src/arr/compiler/compile-structs.arr index 563a1e010..84c024fc0 100644 --- a/lang/src/arr/compiler/compile-structs.arr +++ b/lang/src/arr/compiler/compile-structs.arr @@ -255,7 +255,7 @@ sharing: end | d-type(_, _) => remote-datatype end - some(T.t-name(T.module-uri(de.origin.uri-of-definition), A.s-type-global(de.typ.name), de.origin.local-bind-site, false)) + some(T.t-name(T.module-uri(de.origin.uri-of-definition), A.s-type-global(A.dummy-loc, de.typ.name), de.origin.local-bind-site, false)) | none => cases(Option) provides-of-aliased.aliases.get(name): | some(typ) => @@ -461,11 +461,11 @@ fun type-from-raw(uri, typ, tyvar-env :: SD.StringDict) block: T.t-tuple(for map(e from typ.elts): tfr(e) end, l, false) | t == "name" then: if typ.origin.import-type == "$ELF": - T.t-name(T.module-uri(uri), A.s-type-global(typ.name), l, false) + T.t-name(T.module-uri(uri), A.s-type-global(l, typ.name), l, false) else if typ.origin.import-type == "uri": - T.t-name(T.module-uri(typ.origin.uri), A.s-type-global(typ.name), l, false) + T.t-name(T.module-uri(typ.origin.uri), A.s-type-global(l, typ.name), l, false) else: - T.t-name(T.dependency(make-dep(typ.origin)), A.s-type-global(typ.name), l, false) + T.t-name(T.dependency(make-dep(typ.origin)), A.s-type-global(l, typ.name), l, false) end | t == "tyvar" then: cases(Option) tyvar-env.get(typ.name): @@ -474,7 +474,7 @@ fun type-from-raw(uri, typ, tyvar-env :: SD.StringDict) block: end | t == "forall" then: new-env = for fold(new-env from tyvar-env, a from typ.args): - tvn = A.global-names.make-atom(a) + tvn = A.global-names.make-atom(l, a) new-env.set(a, tvn) end params = for SD.map-keys(k from new-env): @@ -518,7 +518,7 @@ fun datatype-from-raw(uri, datatyp): d-alias(origin, datatyp.name) else if datatyp.tag == "data": pdict = for fold(pdict from SD.make-string-dict(), a from datatyp.params): - tvn = A.global-names.make-atom(a) + tvn = A.global-names.make-atom(l, a) pdict.set(a, tvn) end params = for SD.map-keys(k from pdict): @@ -2981,7 +2981,14 @@ default-compile-options = { html-file: none, deps-file: "build/bundled-node-deps.js", standalone-file: "src/js/base/handalone.js", - url-file-mode: all-remote + url-file-mode: all-remote, + query: false, + cache-manager: { + method set-surface-ast(self, _, _): nothing end, + method get-surface-ast(self, _): none end, + method set-named-result(self, _, _): nothing end, + method get-named-result(self, _): none end, + } } fun make-default-compile-options(this-pyret-dir): @@ -3003,7 +3010,7 @@ t-within-num = t-arrow([list: t-number], t-arrow([list: t-number, t-number], t-b t-within-any = t-arrow([list: t-number], t-arrow([list: t-top, t-top], t-boolean)) fun t-forall1(f): - n = A.global-names.make-atom("a") + n = A.global-names.make-atom(A.dummy-loc, "a") t-forall([list: t-var(n)], f(t-var(n))) end @@ -3315,16 +3322,16 @@ no-globals = globals([string-dict:], [string-dict:], [string-dict:]) reactor-optional-fields = [SD.string-dict: - "last-image", {(l): A.a-name(l, A.s-type-global("Function"))}, - "on-tick", {(l): A.a-name(l, A.s-type-global("Function"))}, - "to-draw", {(l): A.a-name(l, A.s-type-global("Function"))}, - "on-key", {(l): A.a-name(l, A.s-type-global("Function"))}, - "on-raw-key", {(l): A.a-name(l, A.s-type-global("Function"))}, - "on-mouse", {(l): A.a-name(l, A.s-type-global("Function"))}, - "stop-when", {(l): A.a-name(l, A.s-type-global("Function"))}, - "seconds-per-tick", {(l): A.a-name(l, A.s-type-global("NumPositive"))}, - "title", {(l): A.a-name(l, A.s-type-global("String"))}, - "close-when-stop", {(l): A.a-name(l, A.s-type-global("Boolean"))} + "last-image", {(l): A.a-name(l, A.s-type-global(l, "Function"))}, + "on-tick", {(l): A.a-name(l, A.s-type-global(l, "Function"))}, + "to-draw", {(l): A.a-name(l, A.s-type-global(l, "Function"))}, + "on-key", {(l): A.a-name(l, A.s-type-global(l, "Function"))}, + "on-raw-key", {(l): A.a-name(l, A.s-type-global(l, "Function"))}, + "on-mouse", {(l): A.a-name(l, A.s-type-global(l, "Function"))}, + "stop-when", {(l): A.a-name(l, A.s-type-global(l, "Function"))}, + "seconds-per-tick", {(l): A.a-name(l, A.s-type-global(l, "NumPositive"))}, + "title", {(l): A.a-name(l, A.s-type-global(l, "String"))}, + "close-when-stop", {(l): A.a-name(l, A.s-type-global(l, "Boolean"))} ] reactor-fields = reactor-optional-fields.set("init", {(l): A.a-any(l)}) diff --git a/lang/src/arr/compiler/desugar-post-tc.arr b/lang/src/arr/compiler/desugar-post-tc.arr index 071e19918..431e7f467 100644 --- a/lang/src/arr/compiler/desugar-post-tc.arr +++ b/lang/src/arr/compiler/desugar-post-tc.arr @@ -19,7 +19,7 @@ desugar-visitor = A.default-map-visitor.{ A.s-prim-app(l, "throwUnfinishedTemplate", [list: A.s-srcloc(l, l)], flat-prim-app) end, method s-cases-else(self, l, typ, val, branches, els, blocky): - name = A.global-names.make-atom("cases") + name = A.global-names.make-atom(l, "cases") typ-compiled = typ.visit(self) val-exp = val.visit(self) val-id = A.s-id(l, name) @@ -28,7 +28,7 @@ desugar-visitor = A.default-map-visitor.{ els.visit(self), true), false) end, method s-cases(self, l, typ, val, branches, blocky): - name = A.global-names.make-atom("cases") + name = A.global-names.make-atom(l, "cases") typ-compiled = typ.visit(self) val-exp = val.visit(self) val-id = A.s-id(l, name) @@ -37,7 +37,7 @@ desugar-visitor = A.default-map-visitor.{ A.s-block(l, [list: no-cases-exn(l, val-id)]), true), false) end, method s-check(self, l, name, body, keyword-check): - A.s-id(l, A.s-global("nothing")) + A.s-id(l, A.s-global(l, "nothing")) end } diff --git a/lang/src/arr/compiler/desugar.arr b/lang/src/arr/compiler/desugar.arr index 19e088049..ac785f280 100644 --- a/lang/src/arr/compiler/desugar.arr +++ b/lang/src/arr/compiler/desugar.arr @@ -24,8 +24,8 @@ end mt-d-env = d-env([tree-set: ], [tree-set: ], [tree-set: ]) var generated-binds = SD.make-mutable-string-dict() -fun g(id): A.s-global(id) end -fun gid(l, id): A.s-id(l, g(id)) end +fun g(l, id): A.s-global(l, id) end +fun gid(l, id): A.s-id(l, g(l, id)) end fun bid(l, name): A.s-dot(l, A.s-prim-val(l, "builtins"), name) end flat-prim-app = A.prim-app-info-c(false) @@ -138,13 +138,13 @@ fun desugar(program :: A.Program): end fun mk-id-ann(loc, base, ann) block: - a = names.make-atom(base) + a = names.make-atom(loc, base) generated-binds.set-now(a.key(), C.value-bind(C.bo-local(loc, a), C.vb-let, a, ann)) { id: a, id-b: A.s-bind(loc, false, a, ann), id-e: A.s-id(loc, a) } end fun mk-id-var-ann(loc, base, ann) block: - a = names.make-atom(base) + a = names.make-atom(loc, base) generated-binds.set-now(a.key(), C.value-bind(C.bo-local(loc, a), C.vb-var, a, ann)) { id: a, id-b: A.s-bind(loc, false, a, ann), id-e: A.s-id-var(loc, a) } end @@ -278,7 +278,7 @@ fun ds-curry(l, f, args): where: d = A.dummy-loc n = A.s-global - id = lam(s): A.s-id(d, A.s-global(s)) end + id = lam(s): A.s-id(d, A.s-global(d, s)) end under = A.s-id(d, A.s-underscore(d)) ds-ed = ds-curry( d, @@ -699,7 +699,7 @@ fun desugar-expr(expr :: A.Expr): | s-table-extend-reducer(shadow l, name, reducer-expr, _, _) => reducer = reducers.get-value(name) acc = accs.get-value(name) - nothing-expr = A.s-id(l, A.s-global("nothing")) + nothing-expr = A.s-id(l, A.s-global(A.dummy-loc, "nothing")) link(A.s-let-bind(l, reducer.id-b, desugar-expr(reducer-expr)), link(A.s-var-bind(l, acc.id-b, nothing-expr), reducers-acc)) @@ -784,7 +784,7 @@ fun desugar-expr(expr :: A.Expr): flat-prim-app), # Data with-initialized-reducers( - A.s-app(l, A.s-id(l, A.s-global("raw-array-map-1")), [list: + A.s-app(l, A.s-id(l, A.s-global(A.dummy-loc, "raw-array-map-1")), [list: data-pop-mapfun(true), data-pop-mapfun(false), A.s-dot(A.dummy-loc, tbl.id-e, "_rows-raw-array")]))], flat-prim-app)]), true) @@ -823,7 +823,7 @@ fun desugar-expr(expr :: A.Expr): # Header A.s-dot(A.dummy-loc, tbl.id-e, "_header-raw-array"), # Data - A.s-app(l, A.s-id(A.dummy-loc, g("raw-array-map")), [list: + A.s-app(l, A.s-id(A.dummy-loc, g(A.dummy-loc, "raw-array-map")), [list: A.s-lam(A.dummy-loc, "", empty, [list: row.id-b], A.a-blank, "", A.s-let-expr(A.dummy-loc, link( @@ -862,7 +862,7 @@ fun desugar-expr(expr :: A.Expr): # Header A.s-array(A.dummy-loc, columns.map(_.name)), # Data - A.s-app(l, A.s-id(A.dummy-loc, g("raw-array-map")), [list: + A.s-app(l, A.s-id(A.dummy-loc, g(A.dummy-loc, "raw-array-map")), [list: A.s-lam(A.dummy-loc, "", empty, [list: row.id-b], A.a-blank, "", A.s-array(A.dummy-loc, columns.map(lam(c): @@ -880,7 +880,7 @@ fun desugar-expr(expr :: A.Expr): get-table-column(l, table.l, tbl.id-e, {l: column.l, name: A.s-str(A.dummy-loc,column.s)}))], # Table Construction A.s-prim-app(A.dummy-loc, "raw_array_to_list", [list: - A.s-app(l, A.s-id(A.dummy-loc, g("raw-array-map")), [list: + A.s-app(l, A.s-id(A.dummy-loc, g(A.dummy-loc, "raw-array-map")), [list: A.s-lam(A.dummy-loc, "", empty, [list: row.id-b], A.a-blank, "", A.s-prim-app(A.dummy-loc, "raw_array_get", [list: row.id-e, col.id-e], flat-prim-app), none, none, true), A.s-dot(A.dummy-loc, tbl.id-e, "_rows-raw-array")])], flat-prim-app), true) @@ -894,7 +894,7 @@ fun desugar-expr(expr :: A.Expr): | s-table-filter(l, column-binds, predicate) => row = mk-id(A.dummy-loc, "row") tbl = mk-id(l, "table") - pred-res = mk-id-ann(predicate.l, "pred", A.a-name(predicate.l, A.s-type-global("Boolean"))) + pred-res = mk-id-ann(predicate.l, "pred", A.a-name(predicate.l, A.s-type-global(l, "Boolean"))) columns = column-binds.binds.map(lam(c): @@ -916,7 +916,7 @@ fun desugar-expr(expr :: A.Expr): # Header A.s-dot(A.dummy-loc, tbl.id-e, "_header-raw-array"), # Data - A.s-app(l, A.s-id(A.dummy-loc, g("raw-array-filter")), [list: + A.s-app(l, A.s-id(A.dummy-loc, g(A.dummy-loc, "raw-array-filter")), [list: A.s-lam(A.dummy-loc, "", empty, [list: row.id-b], A.a-blank, "", A.s-let-expr(A.dummy-loc, columns.map(lam(column): @@ -956,8 +956,8 @@ fun desugar-expr(expr :: A.Expr): where: d = A.dummy-loc unglobal = A.default-map-visitor.{ - method s-global(self, s): A.s-name(d, s) end, - method s-atom(self, base, serial): A.s-name(d, base) end + method s-global(self, l, s): A.s-name(l, s) end, + method s-atom(self, l, base, serial): A.s-name(l, base) end } p = lam(str): PP.surface-parse(str, "test").block.visit(A.dummy-loc-visitor) end ds = lam(prog): desugar-expr(prog).visit(unglobal).visit(A.dummy-loc-visitor) end diff --git a/lang/src/arr/compiler/query.arr b/lang/src/arr/compiler/query.arr new file mode 100644 index 000000000..32f1082f2 --- /dev/null +++ b/lang/src/arr/compiler/query.arr @@ -0,0 +1,34 @@ +provide * + +# NOTE: New LSP/queries go here as functions that take the cache-manager and +# query parameters, then return results. The cache-manager has surface-ast, +# named-result, and loadable for every compiled module. + +import either as E +import srcloc as S +import file("ast-util.arr") as AU +import file("compile-structs.arr") as CS + +fun jump-to-def(cache-manager, uri :: String, line :: Number, col :: Number) -> E.Either, {String; S.Srcloc}>: + cases(Option) cache-manager.get-surface-ast(uri): + | none => E.left([list:]) + | some(ast) => + cases(Option) AU.find-name-at(ast, line, col): + | none => E.left([list:]) + | some(name) => + cases(Option) cache-manager.get-named-result(uri): + | none => E.left([list:]) + | some(named-result) => + cases(Option) AU.find-name-key-by-srcloc(named-result.ast, name.l): + | none => E.left([list:]) + | some(key) => + cases(Option) named-result.env.bindings.get-now(key): + | none => E.left([list:]) + | some(vb) => + E.right({vb.origin.uri-of-definition; vb.origin.definition-bind-site}) + end + end + end + end + end +end diff --git a/lang/src/arr/compiler/resolve-scope.arr b/lang/src/arr/compiler/resolve-scope.arr index 77da061d2..5e076d493 100644 --- a/lang/src/arr/compiler/resolve-scope.arr +++ b/lang/src/arr/compiler/resolve-scope.arr @@ -45,7 +45,7 @@ fun desugar-toplevel-types(stmts :: List) -> List block: | s-newtype(l, name, namet) => rev-type-binds := link(A.s-newtype-bind(l, name, namet), rev-type-binds) | s-data(l, name, params, mixins, variants, shared, _check-loc, _check) => - namet = names.make-atom(name) + namet = names.make-atom(l, name) rev-type-binds := link(A.s-newtype-bind(l, A.s-name(l, name), namet), rev-type-binds) rev-stmts := link(A.s-data-expr(l, name, namet, params, mixins, variants, shared, _check-loc, _check), rev-stmts) | else => @@ -263,7 +263,7 @@ fun simplify-let-bind(rebuild, l, bind, expr, lbs :: List) -> List {bound-expr; binding} = cases(Option) as-name: | none => - name = names.make-atom("tup") + name = names.make-atom(lb, "tup") ann = A.a-tuple(lb, for map(f from fields): cases(A.Bind) f: | s-bind(_, _, _, a) => a @@ -383,7 +383,7 @@ fun desugar-scope-block(stmts :: List, binding-group :: BindingGroup) -> A.s-letrec-bind(v.l, b(v.l, checker-name), get-part(checker-name)) ] end - blob-id = names.make-atom(name) + blob-id = names.make-atom(l, name) data-expr = A.s-data-expr(l, name, namet, params, mixins, variants, shared, _check-loc, _check) bind-data = A.s-letrec-bind(l, bn(l, blob-id), data-expr) bind-data-pred = A.s-letrec-bind(l, b(l, A.make-checker-name(name)), A.s-dot(l, A.s-id-letrec(l, blob-id, true), name)) @@ -689,7 +689,7 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com datatypes = SD.make-mutable-string-dict() fun make-anon-import-for(l, s, env, shadow bindings, b) block: - atom = names.make-atom(s) + atom = names.make-atom(l, s) bindings.set-now(atom.key(), b(atom)) { atom: atom, env: env } end @@ -723,17 +723,17 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com # origin: env.get-value(s) # end # end - atom = names.make-atom(s) + atom = names.make-atom(l, s) binding = make-binding(atom) bindings.set-now(atom.key(), binding) { atom: atom, env: env.set(s, binding) } | s-underscore(l) => - atom = names.make-atom("$underscore") + atom = names.make-atom(l, "$underscore") bindings.set-now(atom.key(), make-binding(atom)) { atom: atom, env: env } # NOTE(joe): an s-atom is pre-resolved to all its uses, so no need to add # it or do any more work. - | s-atom(_, _) => + | s-atom(_, _, _) => binding = make-binding(name) bindings.set-now(name.key(), binding) { atom: name, env: env } @@ -769,13 +769,13 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com | some(shadow val-info) => cases(C.ValueExport) val-info block: | v-var(_, t) => - b = C.value-bind(C.bo-global(some(origin), uri-of-definition, origin.original-name), C.vb-var, names.s-global(name), A.a-blank) - bindings.set-now(names.s-global(name).key(), b) + b = C.value-bind(C.bo-global(some(origin), uri-of-definition, origin.original-name), C.vb-var, names.s-global(A.dummy-loc, name), A.a-blank) + bindings.set-now(names.s-global(A.dummy-loc, name).key(), b) acc.set-now(name, b) | else => # TODO(joe): Good place to add _location_ to valueexport to report errs better - b = C.value-bind(C.bo-global(some(origin), uri-of-definition, origin.original-name), C.vb-let, names.s-global(name), A.a-blank) - bindings.set-now(names.s-global(name).key(), b) + b = C.value-bind(C.bo-global(some(origin), uri-of-definition, origin.original-name), C.vb-let, names.s-global(A.dummy-loc, name), A.a-blank) + bindings.set-now(names.s-global(A.dummy-loc, name).key(), b) acc.set-now(name, b) end end @@ -788,8 +788,8 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com for SD.each-key(name from initial.globals.types) block: origin = initial.globals.types.get-value(name) type-info = initial.type-by-origin-value(origin) - b = C.type-bind(C.bo-global(some(origin), origin.uri-of-definition, origin.original-name), C.tb-type-let, names.s-type-global(name), C.tb-typ(type-info)) - type-bindings.set-now(names.s-type-global(name).key(), b) + b = C.type-bind(C.bo-global(some(origin), origin.uri-of-definition, origin.original-name), C.tb-type-let, names.s-type-global(A.dummy-loc, name), C.tb-typ(type-info)) + type-bindings.set-now(names.s-type-global(A.dummy-loc, name).key(), b) acc.set-now(name, b) end acc.freeze() @@ -800,8 +800,8 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com for SD.each-key(name from initial.globals.modules) block: origin = initial.globals.modules.get-value(name) mod-info = initial.provides-by-origin-value(origin) - b = C.module-bind(C.bo-global(some(origin), origin.uri-of-definition, origin.original-name), names.s-module-global(name), mod-info.modules.get-value(name)) - module-bindings.set-now(names.s-module-global(name).key(), b) + b = C.module-bind(C.bo-global(some(origin), origin.uri-of-definition, origin.original-name), names.s-module-global(A.dummy-loc, name), mod-info.modules.get-value(name)) + module-bindings.set-now(names.s-module-global(A.dummy-loc, name).key(), b) acc.set-now(name, b) end acc.freeze() @@ -837,9 +837,9 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com if env.has-key(s): env.get-value(s).atom else: - names.s-global(s) + names.s-global(l2, s) end - | s-atom(_, _) => id + | s-atom(_, _, _) => id | s-underscore(_) => id | else => raise("Wasn't expecting a non-s-name in resolve-names id: " + torepr(id)) end @@ -855,7 +855,7 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com | tb-type-var => A.a-type-var(l, name) end else: - A.a-name(l, names.s-type-global(s)) + A.a-name(l, names.s-type-global(l, s)) end | else => A.a-name(l, id) end @@ -1054,14 +1054,14 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com new-header = A.s-import(l, file, atom-env-m.atom) { imp-e; imp-te; atom-env-m.env; link(new-header, imp-imps) } | s-import-fields(l, fields, file) => - synth-include-name = names.make-atom(include-name()) + synth-include-name = names.make-atom(l, include-name()) updated = add-import(acc, A.s-import(l, file, synth-include-name)) add-import(updated, A.s-include-from(l, [list: synth-include-name], fields.map(lam(f): A.s-include-name(l, A.s-module-ref(l, [list: f], none)) end))) | s-include(l, file) => - synth-include-name = names.make-atom(include-name()) + synth-include-name = names.make-atom(l, include-name()) updated = add-import(acc, A.s-import(l, file, synth-include-name)) add-import(updated, A.s-include-from(l, [list: synth-include-name], [list: @@ -1702,7 +1702,7 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com when self.type-env.has-key(s) block: name-errors := link(C.type-id-used-as-value(id, self.type-env.get-value(s).origin), name-errors) end - A.s-id(l2, names.s-global(s)) + A.s-id(l2, names.s-global(l2, s)) | some(vb) => cases (C.ValueBinder) vb.binder: | vb-let => A.s-id(l2, vb.atom) @@ -1710,7 +1710,7 @@ fun resolve-names(p :: A.Program, thismodule-uri :: String, initial-env :: C.Com | vb-var => A.s-id-var(l2, vb.atom) end end - | s-atom(_, _) => A.s-id(l, id) + | s-atom(_, _, _) => A.s-id(l, id) | s-underscore(_) => A.s-id(l, id) | else => raise("Wasn't expecting a non-s-name in resolve-names id: " + torepr(id)) end diff --git a/lang/src/arr/compiler/server.arr b/lang/src/arr/compiler/server.arr index ca3e15ed8..44bf031a1 100644 --- a/lang/src/arr/compiler/server.arr +++ b/lang/src/arr/compiler/server.arr @@ -1,4 +1,7 @@ -provide * +provide: + serve +end + import either as E import json as J @@ -8,100 +11,180 @@ import render-error-display as RED import js-file("server") as S import file("./cli-module-loader.arr") as CLI import file("./compile-structs.arr") as CS +import file("./compile-lib.arr") as CL +import file("./query.arr") as Q import file("locators/builtin.arr") as B +fun get-compile-opts(options): + opts = CS.make-default-compile-options(options.get-value("this-pyret-dir")) + opts.{ + base-dir: options.get("base-dir").or-else(opts.base-dir), + this-pyret-dir : options.get-value("this-pyret-dir"), + check-mode : not(options.get("no-check-mode").or-else(not(opts.check-mode))), + type-check : options.get("type-check").or-else(opts.type-check), + allow-shadowed : options.get("allow-shadowed").or-else(opts.allow-shadowed), + collect-all: options.get("collect-all").or-else(opts.collect-all), + ignore-unbound: options.get("ignore-unbound").or-else(opts.ignore-unbound), + proper-tail-calls: not(options.get("improper-tail-calls").or-else(not(opts.proper-tail-calls))), + compiled-cache: options.get("compiled-dir").or-else("./compiled"), + compiled-read-only: options.get("compiled-read-only").or-else(opts.compiled-read-only), + standalone-file: options.get("standalone-file").or-else(opts.standalone-file), + checks: options.get("checks").or-else(opts.checks), + checks-format: options.get("checks-format").or-else(opts.checks-format), + display-progress: options.get("display-progress").or-else(opts.display-progress), + log: options.get("log").or-else(opts.log), + log-error: options.get("log-error").or-else(opts.log-error), + deps-file: options.get("deps-file").or-else(opts.deps-file), + user-annotations: options.get("user-annotations").or-else(opts.user-annotations) + } +end + +fun handle-compile-opts(pyret-dir, msg, send-message) block: + # print("Got message in pyret-land: " + msg) + opts = J.read-json(msg).native() + # print(torepr(opts)) + # print("\n") + when opts.has-key("builtin-js-dir"): + if is-List(opts.get-value("builtin-js-dir")): + B.set-builtin-js-dirs(opts.get-value("builtin-js-dir")) + else: + B.set-builtin-js-dirs([list: opts.get-value("builtin-js-dir")]) + end + end + when opts.has-key("builtin-arr-dir"): + if is-List(opts.get-value("builtin-arr-dir")): + B.set-builtin-arr-dirs(opts.get-value("builtin-arr-dir")) + else: + B.set-builtin-arr-dirs([list: opts.get-value("builtin-arr-dir")]) + end + end + when opts.has-key("allow-builtin-overrides"): + B.set-allow-builtin-overrides(opts.get-value("allow-builtin-overrides")) + end + fun log(s, to-clear): + d = [SD.string-dict: "type", J.j-str("echo-log"), "contents", J.j-str(s)] + with-clear = cases(Option) to-clear: + | none => d.set("clear-first", J.j-bool(false)) + | some(n) => d.set("clear-first", J.j-num(n)) + end + send-message(J.j-obj(with-clear).serialize()) + end + fun err(s): + d = [SD.string-dict: "type", J.j-str("echo-err"), "contents", J.j-str(s)] + send-message(J.j-obj(d).serialize()) + end + opts-prime = opts + .set("log", log) + .set("log-error", err) + .set("this-pyret-dir", pyret-dir) + .set("compiled-read-only", link(P.resolve(P.join(pyret-dir, "lib-compiled")), empty)) + opts-prim2 = if opts.has-key("perilous") and opts.get-value("perilous"): + opts-prime.set("user-annotations", false) + else: + opts-prime + end + opts-prim2 + .set("require-config", opts.get("require-config").or-else(P.resolve(P.join(pyret-dir, "config.json")))) +end + fun compile(options): outfile = cases(Option) options.get("outfile"): | some(v) => v | none => options.get-value("program") + ".jarr" end - compile-opts = CS.make-default-compile-options(options.get-value("this-pyret-dir")) + opts = get-compile-opts(options).{ + cache-manager: CLI.make-file-cache() + } CLI.build-runnable-standalone( options.get-value("program"), options.get-value("require-config"), outfile, - compile-opts.{ - base-dir: options.get-value("base-dir"), - this-pyret-dir : options.get-value("this-pyret-dir"), - check-mode : not(options.get("no-check-mode").or-else(false)), - type-check : options.get("type-check").or-else(false), - allow-shadowed : options.get("allow-shadowed").or-else(false), - collect-all: options.get("collect-all").or-else(false), - ignore-unbound: options.get("ignore-unbound").or-else(false), - proper-tail-calls: options.get("improper-tail-calls").or-else(true), - compiled-cache: options.get("compiled-dir").or-else("./compiled"), - compiled-read-only: options.get("compiled-read-only").or-else(empty), - standalone-file: options.get("standalone-file").or-else(compile-opts.standalone-file), - checks: options.get-value("checks"), - checks-format: options.get-value("checks-format"), - display-progress: options.get("display-progress").or-else(true), - log: options.get("log").or-else(compile-opts.log), - log-error: options.get("log-error").or-else(compile-opts.log-error), - deps-file: options.get("deps-file").or-else(compile-opts.deps-file), - user-annotations: options.get("user-annotations").or-else(compile-opts.user-annotations) - }) + opts + ) end -fun serve(port, pyret-dir): - S.make-server(port, lam(msg, send-message) block: - # print("Got message in pyret-land: " + msg) - opts = J.read-json(msg).native() - # print(torepr(opts)) - # print("\n") - when opts.has-key("builtin-js-dir"): - if is-List(opts.get-value("builtin-js-dir")): - B.set-builtin-js-dirs(opts.get-value("builtin-js-dir")) - else: - B.set-builtin-js-dirs([list: opts.get-value("builtin-js-dir")]) - end - end - when opts.has-key("builtin-arr-dir"): - if is-List(opts.get-value("builtin-arr-dir")): - B.set-builtin-arr-dirs(opts.get-value("builtin-arr-dir")) - else: - B.set-builtin-arr-dirs([list: opts.get-value("builtin-arr-dir")]) - end - end - when opts.has-key("allow-builtin-overrides"): - B.set-allow-builtin-overrides(opts.get-value("allow-builtin-overrides")) - end - fun log(s, to-clear): - d = [SD.string-dict: "type", J.j-str("echo-log"), "contents", J.j-str(s)] - with-clear = cases(Option) to-clear: - | none => d.set("clear-first", J.j-bool(false)) - | some(n) => d.set("clear-first", J.j-num(n)) - end - send-message(J.j-obj(with-clear).serialize()) - end - fun err(s): - d = [SD.string-dict: "type", J.j-str("echo-err"), "contents", J.j-str(s)] +fun query-compile(options, cache-manager): + program = options.get-value("program") + pyret-dir = options.get-value("this-pyret-dir") + compile-opts = get-compile-opts(options).{ + query: true, + cache-manager: cache-manager + } + CLI.compile-for-query(compile-opts, program) +end + +fun on-compile(pyret-dir, msg, send-message): + opts = handle-compile-opts(pyret-dir, msg, send-message) + result = run-task(lam(): compile(opts) end) + cases(E.Either) result block: + | right(exn) => + err-str = RED.display-to-string(exn-unwrap(exn).render-reason(), tostring, empty) + opts.get-value("log-error")(err-str + "\n") + d = [SD.string-dict: "type", J.j-str("compile-failure")] + send-message(J.j-obj(d).serialize()) + | left(val) => + d = [SD.string-dict: "type", J.j-str("compile-success")] send-message(J.j-obj(d).serialize()) + nothing + end +end + +fun on-query(pyret-dir, cache-manager, query, compile-opts, query-opts, send-message): + shadow compile-opts = handle-compile-opts(pyret-dir, compile-opts, send-message) + shadow query-opts = J.read-json(query-opts).native() + result = run-task(lam(): + base-uri = query-compile(compile-opts, cache-manager) + # NOTE: To add a new query feature, add a case here and # a function in + # query.arr. The cache-manager has surface-ast, named-result, and loadable + # for every compiled module. + ask: + | query == "jump-to-def" then: + line = query-opts.get-value("line") + col = query-opts.get-value("col") + Q.jump-to-def(cache-manager, base-uri, line, col) end - with-logger = opts.set("log", log) - with-error = with-logger.set("log-error", err) - with-pyret-dir = with-error.set("this-pyret-dir", pyret-dir) - with-compiled-read-only-dirs = with-pyret-dir.set("compiled-read-only", - link(P.resolve(P.join(pyret-dir, "lib-compiled")), empty)) - with-perilous = if opts.has-key("perilous") and opts.get-value("perilous"): - with-compiled-read-only-dirs.set("user-annotations", false) - else: - with-compiled-read-only-dirs + end) + + err = compile-opts.get-value("log-error") + + cases(E.Either) result block: + | right(exn) => + err-str = RED.display-to-string(exn-unwrap(exn).render-reason(), tostring, empty) + err(err-str + "\n") + d = [SD.string-dict: "type", J.j-str(query + "-failure")] + send-message(J.j-obj(d).serialize()) + | left(info-result) => + # NOTE: Each query is responsible for its own response serialization + # here, since response shapes differ per feature. + # TODO: should probably refactor this + ask: + | query == "jump-to-def" then: + cases(E.Either) info-result block: + | left(errors) => + err("jump-to-def: no result (errors: " + torepr(errors) + ")\n") + d = [SD.string-dict: "type", J.j-str("jump-to-def-failure")] + send-message(J.j-obj(d).serialize()) + | right(loc-info) => + srcloc = loc-info.{1} + d = [SD.string-dict: + "type", J.j-str("jump-to-def-success"), + "uri", J.j-str(loc-info.{0}), + "start-line", J.j-num(srcloc.start-line), + "start-column", J.j-num(srcloc.start-column), + "end-line", J.j-num(srcloc.end-line), + "end-column", J.j-num(srcloc.end-column) + ] + send-message(J.j-obj(d).serialize()) + end end - with-require-config = with-perilous.set("require-config", - opts.get("require-config").or-else(P.resolve(P.join(pyret-dir, "config.json")))) - result = run-task(lam(): - compile(with-require-config) - end) - cases(E.Either) result block: - | right(exn) => - err-str = RED.display-to-string(exn-unwrap(exn).render-reason(), tostring, empty) - err(err-str + "\n") - d = [SD.string-dict: "type", J.j-str("compile-failure")] - send-message(J.j-obj(d).serialize()) - | left(val) => - d = [SD.string-dict: "type", J.j-str("compile-success")] - send-message(J.j-obj(d).serialize()) - nothing end - end) +end + +fun serve(port, pyret-dir): + cache-manager = CLI.make-in-memory-cache() + S.make-server( + port, + on-compile(pyret-dir, _, _), + on-query(pyret-dir, cache-manager, _, _, _, _) + ) end diff --git a/lang/src/arr/compiler/server.js b/lang/src/arr/compiler/server.js index 6845baa7b..eb2d58ec9 100644 --- a/lang/src/arr/compiler/server.js +++ b/lang/src/arr/compiler/server.js @@ -1,22 +1,31 @@ -{ +/** @satisfies {PyretModule} */ +({ provides: { values: { "make-server": "tany" } }, requires: [], - nativeRequires: ['http', 'ws'], - theModule: function(runtime, _, uri, http, ws) { + nativeRequires: ['http', 'ws', 'fs'], + theModule: function( + runtime, + _, + uri, + /** @type {import('node:http')} */ http, + ws, + /** @type {import('node:fs')} */ fs, + ) { + /** @import ws from "ws" */ const INFO = 4; const LOG = 3; const WARN = 2; const ERROR = 1; const SILENT = 0; - var LOG_LEVEL = ERROR; + let LOG_LEVEL = ERROR; - function makeLogger(level) { - return function(...args) { + function makeLogger(/** @type {number} */ level) { + return function(/** @type {...any} */...args) { if(LOG_LEVEL >= level) { console.log.apply(console, ["[server] ", new Date()].concat(args)); } @@ -30,13 +39,57 @@ // Port is a string for a file path, like /tmp/some-sock, - const makeServer = function(port, onmessage) { - - var runQueue = []; + const makeServer = function( + /** @type {string} */ port, + /** @type {PyretFunction} */ onCompile, + /** @type {PyretFunction} */ onQuery, + ) { + /** + * @typedef {{respondForPy: PyretFunction, respondJSON: function, closeConn: function}} QueueCommon + * @typedef {{command: 'compile', options: unknown}} CompileQueueItem + * @typedef {{command: 'query', query: string, compileOptions: unknown, queryOptions: unknown}} QueryQueueItem + * @typedef {QueueCommon & (CompileQueueItem | QueryQueueItem)} QueueItem + */ + + /** @type {QueueItem[]} */ + let runQueue = []; + let running = false; + + function tryQueue() { + if (running || runQueue.length === 0) { return; } + running = true; + const current = /** @type {QueueItem} */ (runQueue.shift()); + info(`Running queued command: ${current.command}, queue length now ${runQueue.length}`); + runtime.runThunk(function() { + const { respondForPy: respond } = current; + switch (current.command) { + case 'compile': + return onCompile.app(current.options, respond); + case 'query': + return onQuery.app(current.query, current.compileOptions, current.queryOptions, current.respondForPy); + } + }, function(result) { + if (runtime.isFailureResult(result)) { + const exn = result.exn; + const inner = exn && exn.exn !== undefined ? exn.exn : exn; + error("Failed (raw exn):", inner); + error("Failed (stack):", exn && exn.stack); + error("Failed (pyretStack):", exn && exn.pyretStack); + const exnStr = inner !== undefined + ? (typeof inner === 'object' ? JSON.stringify(inner) : String(inner)) + : String(exn); + current.respondJSON({type: "echo-err", contents: "Internal error: " + exnStr}); + if (exn && exn.stack) { current.respondJSON({type: "echo-err", contents: exn.stack}); } + } + current.closeConn(); + running = false; + tryQueue(); + }); + } //info("Starting up server"); return runtime.pauseStack(function(restarter) { - var server = http.createServer(function(request, response) { + const server = http.createServer(function(request, response) { response.writeHead(404); response.end(); }); @@ -58,76 +111,80 @@ } }); - var wsServer = new ws.Server({ + /** @type {ws.Server} */ + const wsServer = new ws.Server({ server: server }); wsServer.on('connection', function(connection) { - - function respond(jsonData) { + function respond(/** @type {string} */ jsonData) { info("Sending: ", jsonData); connection.send(jsonData); return runtime.nothing; } - function respondJSON(json) { return respond(JSON.stringify(json)); } + function respondJSON(/** @type {*} */ json) { return respond(JSON.stringify(json)); } const respondForPy = runtime.makeFunction(respond, "respond"); + function closeConn() { connection.close(); } - function tryQueue() { - info("Trying run queue, length is ", runQueue.length); - if(runQueue.length > 0) { - var current = runQueue.pop(); - runtime.runThunk(function() { - return onmessage.app(current, respondForPy); - }, function(result) { - if(runtime.isFailureResult(result)) { - error("Failed: ", result.exn.exn, result.exn.stack, result.exn.pyretStack); - respondJSON({type: "echo-err", contents: "There was an internal error, please report this as a bug"}); - respondJSON({type: "echo-err", contents: String(result.exn.exn) }); - connection.close(); - // restarter.error(result.exn); - } - else { - connection.close(); - // info("Success: ", result); - } - tryQueue(); - }); - } - } - - info((new Date()) + ' Connection accepted.'); + info(`${new Date()} Connection accepted.`); - connection.on('message', function(message) { - info('Received Message: ' + message); + /** + * @typedef {{command: 'stop'} | + * {command: 'shutdown'} | + * {command: 'compile', compileOptions: unknown} | + * {command: 'query', query: string, compileOptions: unknown, queryOptions: unknown}} + * ServerMessage + */ - var parsed = JSON.parse(message); + connection.on('message', function(/** @type {string} */ message) { + info(`Received Message: ${message}`); - if(parsed.command === "stop") { - runtime.schedulePause(function(restarter) { - restarter.break(); - }); - tryQueue(); - } - - if(parsed.command === "shutdown") { - runtime.breakAll(); - info("Exiting due to shutdown request"); - process.exit(0); - } + /** @type {ServerMessage} */ + const parsed = JSON.parse(message); - if(parsed.command === "compile") { - runQueue.push(parsed.compileOptions); - tryQueue(); + switch (parsed.command) { + case "stop": { + runtime.schedulePause(function(restarter) { + restarter.break(); + }); + tryQueue(); + break; + } + case "shutdown": { + runtime.breakAll(); + info("Exiting due to shutdown request"); + process.exit(0); + break; + } + case "compile": { + runQueue.push({ + command: parsed.command, + options: parsed.compileOptions, + respondForPy, respondJSON, closeConn + }); + tryQueue(); + break; + }; + case "query": { + runQueue.push({ + command: parsed.command, + query: parsed.query, + compileOptions: parsed.compileOptions, + queryOptions: parsed.queryOptions, + respondForPy, respondJSON, closeConn + }); + tryQueue(); + break; + } } - }); connection.on('close', function(reasonCode, description) { // info((new Date()) + ' Peer ' + connection.remoteAddress + ' disconnected.'); }); }); - + info("Server startup successful"); if(process.send) { process.send({type: 'success'}); @@ -144,4 +201,4 @@ "make-server": runtime.makeFunction(makeServer, "make-server") }, {}); } -} +}) diff --git a/lang/src/arr/compiler/type-check.arr b/lang/src/arr/compiler/type-check.arr index b1274bee2..bcf5f1f02 100644 --- a/lang/src/arr/compiler/type-check.arr +++ b/lang/src/arr/compiler/type-check.arr @@ -169,7 +169,7 @@ fun type-check(program :: A.Program, compile-env :: C.CompileEnvironment, post-c globvs = compile-env.globals.values globts = compile-env.globals.types shadow context = globvs.fold-keys(lam(g, shadow context): - if context.global-types.has-key(A.s-global(g).key()): + if context.global-types.has-key(A.s-global(A.dummy-loc, g).key()): context else: # TODO(joe): type-check vars by making them refs @@ -177,12 +177,12 @@ fun type-check(program :: A.Program, compile-env :: C.CompileEnvironment, post-c if (g == "_"): context else: - context.set-global-types(context.global-types.set(A.s-global(g).key(), compile-env.global-value-value(g).t)) + context.set-global-types(context.global-types.set(A.s-global(A.dummy-loc, g).key(), compile-env.global-value-value(g).t)) end end end, context) shadow context = globts.fold-keys(lam(g, shadow context): - if context.aliases.has-key(A.s-type-global(g).key()): + if context.aliases.has-key(A.s-type-global(A.dummy-loc, g).key()): context else: origin = globts.get-value(g) @@ -195,11 +195,11 @@ fun type-check(program :: A.Program, compile-env :: C.CompileEnvironment, post-c | none => cases(Option) provs.data-definitions.get(g): | none => raise("Key " + g + " not found in " + torepr(provs)) - | some(v) => t-name(TS.builtin-uri, A.s-type-global(g), SL.builtin("global"), false) + | some(v) => t-name(TS.builtin-uri, A.s-type-global(A.dummy-loc, g), SL.builtin("global"), false) end | some(v) => v end - context.set-aliases(context.aliases.set(A.s-type-global(g).key(), t)) + context.set-aliases(context.aliases.set(A.s-type-global(A.dummy-loc, g).key(), t)) | none => raise("Could not find module " + torepr(origin.uri-of-definition) + " in " + torepr(compile-env.all-modules) + " in " + torepr(program.l)) end @@ -1570,15 +1570,16 @@ fun synthesis-app-fun(app-loc :: Loc, _fun :: Expr, args :: List, context end cases(Expr) _fun: | s-id(fun-loc, id) => + d = A.dummy-loc ask: - | id == A.s-global("_plus") then: choose-type("_plus") - | id == A.s-global("_times") then: choose-type("_times") - | id == A.s-global("_divide") then: choose-type("_divide") - | id == A.s-global("_minus") then: choose-type("_minus") - | id == A.s-global("_lessthan") then: choose-type("_lessthan") - | id == A.s-global("_lessequal") then: choose-type("_lessequal") - | id == A.s-global("_greaterthan") then: choose-type("_greaterthan") - | id == A.s-global("_greaterequal") then: choose-type("_greaterequal") + | id == A.s-global(d, "_plus") then: choose-type("_plus") + | id == A.s-global(d, "_times") then: choose-type("_times") + | id == A.s-global(d, "_divide") then: choose-type("_divide") + | id == A.s-global(d, "_minus") then: choose-type("_minus") + | id == A.s-global(d, "_lessthan") then: choose-type("_lessthan") + | id == A.s-global(d, "_lessequal") then: choose-type("_lessequal") + | id == A.s-global(d, "_greaterthan") then: choose-type("_greaterthan") + | id == A.s-global(d, "_greaterequal") then: choose-type("_greaterequal") | otherwise: synthesis(_fun, false, context).fold-bind(lam(_, new-type, shadow context): fold-result(new-type, context) @@ -1815,7 +1816,7 @@ fun lam-to-type(coll :: SD.StringDict, l :: Loc, params :: List, a shadow context = context.add-variable(ret-type) fold-arg-types = map-fold-result(lam(arg, shadow context): arg-is-underscore = cases(A.Name) arg.id: - | s-atom(base, _) => base == "$underscore" + | s-atom(_, base, _) => base == "$underscore" | else => false end arg-type = coll.get-value(arg.id.key()) @@ -2400,7 +2401,7 @@ fun ignore-checker(l :: Loc, binds :: List, body :: Expr, blocky, con if binds.length() == 1: binding = binds.get(0) cases(A.Name) binding.b.id: - | s-atom(base, _) => + | s-atom(_, base, _) => if string-length(base) >= 19: name = string-substring(base, 0, 19) if name == "result-after-checks": diff --git a/lang/src/arr/compiler/type-defaults.arr b/lang/src/arr/compiler/type-defaults.arr index 81f880635..c5aae759b 100644 --- a/lang/src/arr/compiler/type-defaults.arr +++ b/lang/src/arr/compiler/type-defaults.arr @@ -44,31 +44,31 @@ string-dict = SD.string-dict s-atom = A.s-atom -tva = t-var(A.global-names.make-atom("A")) -tvb = t-var(A.global-names.make-atom("B")) -tvc = t-var(A.global-names.make-atom("C")) -tvd = t-var(A.global-names.make-atom("D")) -tve = t-var(A.global-names.make-atom("E")) -tvf = t-var(A.global-names.make-atom("F")) -tvg = t-var(A.global-names.make-atom("G")) -tvh = t-var(A.global-names.make-atom("H")) - -t-image = t-name(module-uri("builtin://image-lib"), A.s-type-global("Image")) -t-option = t-name(module-uri("builtin://option"), A.s-type-global("Option")) -t-reactor = t-name(module-uri("builtin://reactors"), A.s-type-global("Reactor")) -t-equality-result = t-name(module-uri("builtin://equality"), A.s-type-global("EqualityResult")) -t-value-skeleton = t-name(module-uri("builtin://valueskeleton"), A.s-type-global("ValueSkeleton")) -t-list = t-name(module-uri("builtin://lists"), A.s-type-global("List")) -t-big-array = t-name(module-uri("builtin://arrays"), A.s-type-global("Array")) -t-set = t-name(module-uri("builtin://sets"), A.s-type-global("Set")) -t-avl = t-name(module-uri("builtin://sets"), A.s-type-global("AVLTree")) -t-runtime-error = t-name(module-uri("builtin://error"), A.s-type-global("RuntimeError")) -t-parse-error = t-name(module-uri("builtin://error"), A.s-type-global("ParseError")) -t-either = t-name(module-uri("builtin://either"), A.s-type-global("Either")) -t-s-exp = t-name(module-uri("builtin://s-exp-structs"), A.s-type-global("S-Exp")) -t-pick = t-name(module-uri("builtin://pick"), A.s-type-global("Pick")) -t-json = t-name(module-uri("builtin://json-structs"), A.s-type-global("JSON")) -t-string-dict = t-name(module-uri("builtin://string-dict"), A.s-type-global("StringDict")) +tva = t-var(A.global-names.make-atom(A.dummy-loc, "A")) +tvb = t-var(A.global-names.make-atom(A.dummy-loc, "B")) +tvc = t-var(A.global-names.make-atom(A.dummy-loc, "C")) +tvd = t-var(A.global-names.make-atom(A.dummy-loc, "D")) +tve = t-var(A.global-names.make-atom(A.dummy-loc, "E")) +tvf = t-var(A.global-names.make-atom(A.dummy-loc, "F")) +tvg = t-var(A.global-names.make-atom(A.dummy-loc, "G")) +tvh = t-var(A.global-names.make-atom(A.dummy-loc, "H")) + +t-image = t-name(module-uri("builtin://image-lib"), A.s-type-global(A.dummy-loc, "Image")) +t-option = t-name(module-uri("builtin://option"), A.s-type-global(A.dummy-loc, "Option")) +t-reactor = t-name(module-uri("builtin://reactors"), A.s-type-global(A.dummy-loc, "Reactor")) +t-equality-result = t-name(module-uri("builtin://equality"), A.s-type-global(A.dummy-loc, "EqualityResult")) +t-value-skeleton = t-name(module-uri("builtin://valueskeleton"), A.s-type-global(A.dummy-loc, "ValueSkeleton")) +t-list = t-name(module-uri("builtin://lists"), A.s-type-global(A.dummy-loc, "List")) +t-big-array = t-name(module-uri("builtin://arrays"), A.s-type-global(A.dummy-loc, "Array")) +t-set = t-name(module-uri("builtin://sets"), A.s-type-global(A.dummy-loc, "Set")) +t-avl = t-name(module-uri("builtin://sets"), A.s-type-global(A.dummy-loc, "AVLTree")) +t-runtime-error = t-name(module-uri("builtin://error"), A.s-type-global(A.dummy-loc, "RuntimeError")) +t-parse-error = t-name(module-uri("builtin://error"), A.s-type-global(A.dummy-loc, "ParseError")) +t-either = t-name(module-uri("builtin://either"), A.s-type-global(A.dummy-loc, "Either")) +t-s-exp = t-name(module-uri("builtin://s-exp-structs"), A.s-type-global(A.dummy-loc, "S-Exp")) +t-pick = t-name(module-uri("builtin://pick"), A.s-type-global(A.dummy-loc, "Pick")) +t-json = t-name(module-uri("builtin://json-structs"), A.s-type-global(A.dummy-loc, "JSON")) +t-string-dict = t-name(module-uri("builtin://string-dict"), A.s-type-global(A.dummy-loc, "StringDict")) fun t-either-app(typ-1 :: Type, typ-2 :: Type): t-app(t-either, [list: typ-1, typ-2]) @@ -265,7 +265,7 @@ module-const-pick = t-module("builtin://pick", "_match", t-top ])), SD.make-string-dict() - .set("Pick", t-name(module-uri("builtin://pick"), A.s-type-global("Pick"))) + .set("Pick", t-name(module-uri("builtin://pick"), A.s-type-global(A.dummy-loc, "Pick"))) ) set-constructor = diff --git a/lang/src/arr/compiler/type-structs.arr b/lang/src/arr/compiler/type-structs.arr index 2216b3a07..1591e7213 100644 --- a/lang/src/arr/compiler/type-structs.arr +++ b/lang/src/arr/compiler/type-structs.arr @@ -548,7 +548,7 @@ sharing: + ")" | t-var(id, _, _) => cases(Name) id: - | s-atom(base, _) => + | s-atom(_, base, _) => if base == "%tyvar": cases(Option) tyvar-mapping.get-now(typ.key()) block: | some(name) => name @@ -592,29 +592,29 @@ check: t-forall([list: a-name, b-name], b-name, A.dummy-loc, false).to-string() is "forall a, b . b" t-ref(a-name, A.dummy-loc, false).to-string() is "ref a" t-data-refinement(a-name, "a", A.dummy-loc, false).to-string() is "(a % is-a)" - t-var(A.s-atom("%tyvar", 0), A.dummy-loc, false).to-string() is "A" + t-var(A.s-atom(A.dummy-loc, "%tyvar", 0), A.dummy-loc, false).to-string() is "A" t-var(A.s-name(A.dummy-loc, "a"), A.dummy-loc, false).to-string() is "a" t-existential(A.s-name(A.dummy-loc, "a"), A.dummy-loc, false).to-string() is "?-1" end fun new-existential(l :: Loc, inferred :: Boolean): - t-existential(A.global-names.make-atom("%exists"), l, inferred) + t-existential(A.global-names.make-atom(l, "%exists"), l, inferred) end fun new-type-var(l :: Loc): - t-var(A.global-names.make-atom("%tyvar"), l, false) + t-var(A.global-names.make-atom(l, "%tyvar"), l, false) end # TODO(MATT): which of these should be kept builtin-uri = module-uri("builtin://global") -t-array-name = t-name(builtin-uri, A.s-type-global("RawArray"), A.dummy-loc, false) +t-array-name = t-name(builtin-uri, A.s-type-global(A.dummy-loc, "RawArray"), A.dummy-loc, false) -t-number = lam(l): t-name(builtin-uri, A.s-type-global("Number"), l, false) end -t-string = lam(l): t-name(builtin-uri, A.s-type-global("String"), l, false) end -t-boolean = lam(l): t-name(builtin-uri, A.s-type-global("Boolean"), l, false) end -t-nothing = lam(l): t-name(builtin-uri, A.s-type-global("Nothing"), l, false) end -t-srcloc = lam(l): t-name(builtin-uri, A.s-type-global("Loc"), l, false) end +t-number = lam(l): t-name(builtin-uri, A.s-type-global(l, "Number"), l, false) end +t-string = lam(l): t-name(builtin-uri, A.s-type-global(l, "String"), l, false) end +t-boolean = lam(l): t-name(builtin-uri, A.s-type-global(l, "Boolean"), l, false) end +t-nothing = lam(l): t-name(builtin-uri, A.s-type-global(l, "Nothing"), l, false) end +t-srcloc = lam(l): t-name(builtin-uri, A.s-type-global(l, "Loc"), l, false) end t-array = lam(v, l): t-app(t-array-name.set-loc(l), [list: v], l, false) end -t-option = lam(v, l): t-app(t-name(module-uri("builtin://option"), A.s-type-global("Option"), l, false), [list: v], l, false) end -t-table = lam(l): t-name(builtin-uri, A.s-type-global("Table"), l, false) end +t-option = lam(v, l): t-app(t-name(module-uri("builtin://option"), A.s-type-global(l, "Option"), l, false), [list: v], l, false) end +t-table = lam(l): t-name(builtin-uri, A.s-type-global(l, "Table"), l, false) end diff --git a/lang/src/arr/trove/ast.arr b/lang/src/arr/trove/ast.arr index 5910bd7bc..7a34038ed 100644 --- a/lang/src/arr/trove/ast.arr +++ b/lang/src/arr/trove/ast.arr @@ -125,7 +125,7 @@ data Name: method toname(self): self.s end, method key(self): "name#" + self.s end - | s-global(s :: String) with: + | s-global(l :: Loc, s :: String) with: method to-compiled-source(self): PP.str(self.to-compiled()) end, method to-compiled(self): self.s end, method tosource(self): PP.str(self.s) end, @@ -133,7 +133,7 @@ data Name: method toname(self): self.s end, method key(self): "global#" + self.s end - | s-module-global(s :: String) with: + | s-module-global(l :: Loc, s :: String) with: method to-compiled-source(self): PP.str(self.to-compiled()) end, method to-compiled(self): "$module$" + self.s end, method tosource(self): PP.str(self.s) end, @@ -141,7 +141,7 @@ data Name: method toname(self): self.s end, method key(self): "mglobal#" + self.s end - | s-type-global(s :: String) with: + | s-type-global(l :: Loc, s :: String) with: method to-compiled-source(self): PP.str(self.to-compiled()) end, method to-compiled(self): "$type$" + self.s end, method tosource(self): PP.str(self.s) end, @@ -149,7 +149,7 @@ data Name: method toname(self): self.s end, method key(self): "tglobal#" + self.s end - | s-atom(base :: String, serial :: Number) with: + | s-atom(l :: Loc, base :: String, serial :: Number) with: method to-compiled-source(self): PP.str(self.to-compiled()) end, method to-compiled(self): self.base + tostring(self.serial) end, method tosource(self): PP.str(self.toname()) end, @@ -169,9 +169,9 @@ end fun MakeName(start): var count = start - fun atom(base :: String) block: + fun atom(l :: Loc, base :: String) block: count := 1 + count - s-atom(base, count) + s-atom(l, base, count) end { reset: lam(): count := start end, @@ -578,6 +578,7 @@ sharing: end end +# ZACK: no loc data DefinedModule: | s-defined-module(name :: String, value :: Name, uri :: String) with: method label(self): "s-defined-module" end, @@ -590,6 +591,7 @@ sharing: end end +# ZACK: no loc data DefinedValue: | s-defined-value(name :: String, value :: Expr) with: method label(self): "s-defined-value" end, @@ -1371,6 +1373,7 @@ sharing: end end +# ZACK: doesn't have loc data ConstructModifier: | s-construct-normal with: method label(self): "s-construct-normal" end, @@ -1490,6 +1493,7 @@ sharing: end end +# ZACK: no loc data ColumnSortOrder: | ASCENDING with: method tosource(self): @@ -1570,6 +1574,7 @@ sharing: end end +# ZACK: no loc data VariantMemberType: | s-normal with: method label(self): "s-normal" end, @@ -1669,6 +1674,7 @@ sharing: end end +# ZACK: NO LOC data CasesBindType: | s-cases-bind-ref with: method label(self): "s-cases-bind-ref" end, @@ -1777,6 +1783,7 @@ sharing: end end +# a-blank HAS NO LOC, nor a-checked data Ann: | a-blank with: method label(self): "a-blank" end, @@ -1898,20 +1905,20 @@ default-map-visitor = { s-name(l, s) end, - method s-type-global(self, s): - s-type-global(s) + method s-type-global(self, l, s): + s-type-global(l, s) end, - method s-module-global(self, s): - s-module-global(s) + method s-module-global(self, l, s): + s-module-global(l, s) end, - method s-global(self, s): - s-global(s) + method s-global(self, l, s): + s-global(l, s) end, - method s-atom(self, base, serial): - s-atom(base, serial) + method s-atom(self, l, base, serial): + s-atom(l, base, serial) end, method s-star(self, l, hidden): @@ -2524,16 +2531,16 @@ default-iter-visitor = { method s-name(self, l, s): true end, - method s-global(self, s): + method s-global(self, l, s): true end, - method s-type-global(self, s): + method s-type-global(self, l, s): true end, - method s-module-global(self, s): + method s-module-global(self, l, s): true end, - method s-atom(self, base, serial): + method s-atom(self, l, base, serial): true end, @@ -3142,17 +3149,17 @@ dummy-loc-visitor = { method s-name(self, l, s): s-name(dummy-loc, s) end, - method s-global(self, s): - s-global(s) + method s-global(self, l, s): + s-global(dummy-loc, s) end, - method s-type-global(self, s): - s-type-global(s) + method s-type-global(self, l, s): + s-type-global(dummy-loc, s) end, - method s-module-global(self, s): - s-module-global(s) + method s-module-global(self, l, s): + s-module-global(dummy-loc, s) end, - method s-atom(self, base, serial): - s-atom(base, serial) + method s-atom(self, l, base, serial): + s-atom(dummy-loc, base, serial) end, method s-star(self, _, hidden): diff --git a/lang/src/js/trove/source-map-lib.js b/lang/src/js/trove/source-map-lib.js index 5b22e46d8..d36ffbd18 100644 --- a/lang/src/js/trove/source-map-lib.js +++ b/lang/src/js/trove/source-map-lib.js @@ -1,4 +1,4 @@ -{ +({ nativeRequires: ["source-map"], requires: [], provides: { @@ -75,4 +75,4 @@ }, {}); } -} +}) diff --git a/lang/src/types.d.ts b/lang/src/types.d.ts new file mode 100644 index 000000000..dfe98eac6 --- /dev/null +++ b/lang/src/types.d.ts @@ -0,0 +1,431 @@ +declare const __pyret_module_return: unique symbol; +type ret = { [__pyret_module_return]: void }; + +declare const __pyret_value: symbol; +type val = { [__pyret_value]: void }; + +declare const __pyret_type: unique symbol; +type typ = { [__pyret_type]: void }; + +type todo = unknown; +type todo_func = (...args: todo[]) => todo; + +type pyret_pos = { + startRow: number; + startCol: number; + startChar: number; + endRow: number; + endChar: number; +}; + +interface PyretFFI { + makePyretPos(filename: string, p: pyret_pos): val; + combinePyretPos(filename: string, p1: pyret_pos, p2: pyret_pos): val; + + // #region throw + throwUpdateNonObj(loc: val, objval: val, objloc: todo): never; + throwUpdateFrozenRef( + loc: val, + objloc: val, + fieldname: string, + fieldloc: val, + ): never; + throwUpdateNonRef: todo_func; + throwUpdateNonExistentField: todo_func; + throwNumStringBinopError: todo_func; + throwNumericBinopError: todo_func; + throwInternalError: todo_func; + throwSpinnakerError: todo_func; + throwFieldNotFound: todo_func; + throwConstructorSyntaxNonConstructor: todo_func; + throwLookupConstructorNotObject: todo_func; + throwLookupNonObject: todo_func; + throwLookupNonTuple: todo_func; + throwBadTupleBind: todo_func; + throwNonTupleBind: todo_func; + throwLookupLargeIndex: todo_func; + throwExtendNonObject: todo_func; + throwTypeMismatch: todo_func; + throwInvalidArrayIndex: todo_func; + throwMessageException(msg: string): never; + throwMultiErrorException: todo_func; + throwUserException: todo_func; + throwEqualityException: todo_func; + throwUninitializedId: todo_func; + throwUninitializedIdMkLoc: todo_func; + throwArityError: todo_func; + throwArityErrorC: todo_func; + throwHeaderRowMismatch: todo_func; + throwRowLengthMismatch: todo_func; + throwColLengthMismatch: todo_func; + throwConstructorArityErrorC: todo_func; + throwCasesArityError: todo_func; + throwCasesArityErrorC: todo_func; + throwCasesSingletonError: todo_func; + throwCasesSingletonErrorC: todo_func; + throwNonBooleanCondition: todo_func; + throwNonBooleanOp: todo_func; + throwNoBranchesMatched: todo_func; + throwNoCasesMatched: todo_func; + throwNonFunApp: todo_func; + throwColumnNotFound: todo_func; + throwDuplicateColumn: todo_func; + throwUnfinishedTemplate: todo_func; + throwModuleLoadFailureL: todo_func; + throwParseErrorBadApp: todo_func; + throwParseErrorBadFunHeader: todo_func; + throwParseErrorNextToken: todo_func; + throwParseErrorColonColon: todo_func; + throwParseErrorEOF: todo_func; + throwParseErrorUnterminatedString: todo_func; + throwParseErrorBadNumber: todo_func; + throwParseErrorBadOper: todo_func; + throwParseErrorBadCheckOper: todo_func; + // #endregion + + makeBadBracketException: todo_func; + makeRecordFieldsFail: todo_func; + makeTupleAnnsFail: todo_func; + makeFieldFailure: todo_func; + makeAnnFailure: todo_func; + makeMissingField: todo_func; + makeTupleLengthMismatch: todo_func; + makeTypeMismatch: todo_func; + makeRefInitFail: todo_func; + makePredicateFailure: todo_func; + makeDotAnnNotPresent: todo_func; + makeFailureAtArg: todo_func; + contractOk: todo; + contractFail: todo; + contractFailArg: todo; + isOk: todo; + isFail: todo; + isFailArg: todo; + + equal: val; + notEqual: val; + unknown: val; + isEqual(v: val): boolean; + isNotEqual(v: val): boolean; + isUnknown(v: val): boolean; + isEqualityResult(v: val): boolean; + + makeInternalError(message: string, otherArgs: val): val; + makeMessageException(message: string): val; + makeUserException(val: val): val; + makeModuleLoadFailureL(names: string[]): val; + + cases( + pred: (val: val) => boolean, + predName: string, + val: val, + casesObj: Record, + ): val; + checkArity: PyretRuntime["checkArity"]; + + makeList(items: val[]): val; + isList(val: val): boolean; + isLink(val: val): boolean; + isEmpty(val: val): boolean; + listLength(val: val): number; + + toArray(val: val): val[]; + + isOption: (val: val) => boolean; + isSome: (val: val) => boolean; + isNone: (val: val) => boolean; + makeSome(val: val): val; + makeNone: () => val; + + isEither: (val: val) => boolean; + isLeft: (val: val) => boolean; + isRight: (val: val) => boolean; + makeLeft(val: val): val; + makeRight(val: val): val; +} + +declare const __type_brander__: unique symbol; + +type SrcLocJs = + | [builtInModule: string] + | [ + source: string, + startLine: number, + startColumn: number, + startChar: number, + endLine: number, + endColumn: number, + endChar: number, + ]; +type func = (...args: any[]) => any; + +type restarter = { + resume: (val: val) => void; + // TODO: + break: func; + error: func; +}; + +type Resumer = (restarter: restarter) => void; + +declare namespace ABI { + class PBase { + protected constructor(); + brands: Record; + dict: Record; + + extendWith(fields: Record): PBase; + } + + class POpaque extends PBase {} + + class PNothing extends PBase { + brand(b: string): PNothing; + } + + class PFunction extends PBase { + protected constructor(fun: func, arity: number, name: string); + app: func; + name: number; + + brand(b: string): PFunction; + } + + class PMethod extends PBase { + protected constructor(meth: func, full_meth: func, name: string); + meth: func; + full_meth: func; + name: string; + } + + const enum RefState { + GRAPHABLE = 0, + UNGRAPHABLE = 1, + SET = 2, + FROZEN = 3, + } + + class PRef { + state: RefState; + // TODO: + anns: unknown[]; + value: unknown; + } + + class PTuple extends PBase { + vals: val[]; + } + + class SuccessResult { + result: PBase; + stats: unknown | undefined; + } + + type __ErrorExt = { + pyretStack?: string[]; + exn?: val; + } + + class FailureResult { + exn: Error & __ErrorExt; + stats: unknown | undefined; + } +} + +type PyretObject = val & ABI.PBase; +type PyretOpaque = val & ABI.POpaque; +type PyretNothing = val & ABI.PNothing; +type PyretString = val & string; +type PyretBoolean = val & boolean; +type PyretRoughNum = val & number; + +type PyretFunction = val & ABI.PFunction; +type PyretMethod = val & ABI.PMethod; + +type PyretTuple = val & ABI.PTuple; + +// TODO: +type RunOptions = { + sync: boolean; + initialGas: number; + initialRunGas: number; +}; + + +// NOTE: this is far from complete +interface PyretRuntime { + builtins: val; + run( + program: unknown, + namespace: unknown, + options: RunOptions, + onDone: unknown, + ): unknown; + runThunk( + f: () => T, + then: (result: T) => unknown, + options?: RunOptions, + ): void; + safeCall(fun: () => T, after: (result: T) => U, stackFrame: string): U; + + ffi: PyretFFI; + + pauseStack(resumer: Resumer): val; + schedulePause(resumer: Resumer): void; + breakAll(): void; + await(promise: Promise): T; + + getField(obj: val, field: string): T; + getFieldLoc(obj: val, field: string, loc: SrcLocJs): T; + getFieldRef(obj: val, field: string, loc: SrcLocJs): T; + getFields(obj: val): string[]; + getBracket(loc: SrcLocJs, obj: val, field: string): T; + getColonField(val: val, field: string): T; + getColonFieldLoc(val: val, field: string, loc: SrcLocJs): T; + getTuple(tup: val, index: number, loc: SrcLocJs): T; + checkTupleBind(tup: val, index: number, loc: SrcLocJs): boolean; + extendObj(loc: SrcLocJs, obj: val, extension: Record): val; + + isBase(v: unknown): v is PyretObject; + isNothing(v: unknown): v is PyretNothing; + isNumber(v: unknown): v is val; + isRoughnum(v: unknown): v is PyretRoughNum; + isExactnum(v: unknown): v is val; + isString(v: unknown): v is PyretString; + isBoolean(v: unknown): v is PyretBoolean; + isFunction(v: unknown): v is func; + isMethod(v: unknown): v is func; + isTuple(v: unknown): v is PyretTuple; + isObject(v: unknown): v is val; + isDataValue(v: unknown): v is val; + isRef(v: unknown): v is val; + isOpaque(v: unknown): v is PyretOpaque; + isPyretVal(v: unknown): v is val; + + isSuccessResult(v: unknown): v is ABI.SuccessResult; + makeSuccessResult(r: unknown): ABI.SuccessResult; + isFailureResult(v: unknown): v is ABI.FailureResult; + makeFailureResult(e: unknown): ABI.FailureResult; + + makeNothing(): PyretNothing; + makeNumber(n: number): val; + makeNumberBig(n: unknown): val; + makeNumberFromString(s: string): val; + makeBoolean(b: boolean): PyretBoolean; + makeString(s: string): PyretString; + makeFunction(fun: func, name: string): PyretFunction; + makeMethod(meth: func, full_meth: func, name: string): PyretMethod; + // ... + makeTuple(tup: val[]): PyretTuple; + makeObject(obj: Record): PyretObject; + makeArray(arr: val[]): val; + makeArrayN(n: number): val; + + // TODO: + checkArrayIndex: func; + makeBrandedObject: func; + makeGraphableRef: func; + makeRef: func; + makeUnsafeSetRef: func; + makeVariantConstructor: func; + makeDataValue: func; + makeDataTypeConstructor: func; + makeMatch: func; + makeOpqaue(val: any): PyretOpaque; + + // ... + + wrap(v: any): val; + unwrap(v: val): T; + + checkArity: ( + expected: number, + args: IArguments, + source: string, + isMethod: boolean, + ) => void; + checkString(v: string): void; + checkNumber(v: number): void; + checkExactnum(v: number): void; + checkRoughnum(v: number): void; + checkNumInteger(v: number): void; + checkNumRational(v: number): void; + checkNumNegative(v: number): void; + checkNumPositive(v: number): void; + checkNumNonPositive(v: number): void; + checkNumNonNegative(v: number): void; + checkTuple(v: val): void; + checkArray(v: val): void; + checkBoolean(v: boolean): void; + checkObject(v: val): void; + checkFunction(v: val): void; + checkMethod(v: val): void; + checkOpaque(v: val): void; + checkPyretVal(v: unknown): void; + + nothing: val; + toRepr(v: val): val; + + makeSrcloc(srcloc: SrcLocJs): val; + + makeJSModuleReturn: (jsMod: any) => ret; + makeModuleReturn: ( + values: Record, + types: Record, + internal?: Record, + ) => ret; + + modules: Record; + + stdout: typeof process.stdout; + stderr: typeof process.stderr; + stdin: typeof process.stdin; + console: typeof console; + + makePrimAnn: unknown; +} + +type PrimType = + | "Number" + | "String" + | "Boolean" + | "Nothing" + | "Any" + | "tany" + | "tbot"; +type TypeId = ["tid", string]; +type Arrow = ["arrow", InteropSignature[], InteropSignature]; +type ForAll = ["forall", string[], InteropSignature]; +type ListOf = ["List", InteropSignature]; +type ArrayOf = ["Array", InteropSignature]; +type RawArrayOf = ["RawArray", InteropSignature]; +type OptionOf = ["Option", InteropSignature]; +type Maker = ["Maker", InteropSignature]; +type InteropSignature = + | ForAll + | Arrow + | TypeId + | PrimType + | ListOf + | ArrayOf + | RawArrayOf + | OptionOf + | Maker; + +type RequireSpec = + | { "import-type": "builtin"; name: string } + | { "import-type": "dependency"; protocol: string; args: any[] }; +interface PyretModule { + requires: RequireSpec[]; + nativeRequires: any[]; + provides: { + values?: Record; + types?: Record; + }; + theModule: ( + runtime: PyretRuntime, + namespace: string, + uri: string, + ...imports: any[] + ) => ret; +} diff --git a/lang/tests/pyret/tests/test-compile-lib.arr b/lang/tests/pyret/tests/test-compile-lib.arr index c00f127e4..3bc9bcb08 100644 --- a/lang/tests/pyret/tests/test-compile-lib.arr +++ b/lang/tests/pyret/tests/test-compile-lib.arr @@ -413,7 +413,7 @@ check "raw-provide-syntax": l = SL.builtin("test-raw-provides") bn = lam(modname, name): - T.t-name(T.module-uri("builtin://" + modname), A.s-type-global(name), l, false) + T.t-name(T.module-uri("builtin://" + modname), A.s-type-global(l, name), l, false) end g = lam(name): bn("global", name) @@ -452,7 +452,7 @@ check: # MARK(joe/ben): modules mt, [string-dict: - "x", CM.v-just-type(o("x"), T.t-name(T.module-uri("builtin://global"), A.s-global("Number"), A.dummy-loc, false)) + "x", CM.v-just-type(o("x"), T.t-name(T.module-uri("builtin://global"), A.s-global(A.dummy-loc, "Number"), A.dummy-loc, false)) ], mt, mt) @@ -478,7 +478,7 @@ check: canon is CM.provides("test-provides1", mt, #MARK(joe/ben): modules [string-dict: - "x", CM.v-just-type(o("x"), T.t-name(T.module-uri("builtin://global"), A.s-global("Number"), A.dummy-loc, false)) + "x", CM.v-just-type(o("x"), T.t-name(T.module-uri("builtin://global"), A.s-global(A.dummy-loc, "Number"), A.dummy-loc, false)) ], mt, mt) diff --git a/lang/tests/pyret/tests/test-letrec.arr b/lang/tests/pyret/tests/test-letrec.arr index e8cc0c320..5bfc721d7 100644 --- a/lang/tests/pyret/tests/test-letrec.arr +++ b/lang/tests/pyret/tests/test-letrec.arr @@ -1,3 +1,5 @@ +use context empty-context + import either as Eth import error as E diff --git a/lang/tests/type-check/main.arr b/lang/tests/type-check/main.arr index e9397749e..ad33efcd5 100644 --- a/lang/tests/type-check/main.arr +++ b/lang/tests/type-check/main.arr @@ -146,7 +146,7 @@ end check "All builtins should have a type": covered = TD.make-default-types() for each(builtin from CS.standard-globals.values.keys-list()): - builtin-typ = covered.get-now(A.s-global(builtin).key()) + builtin-typ = covered.get-now(A.s-global(A.dummy-loc, builtin).key()) builtin-typ satisfies is-some when is-none(builtin-typ): "Should have a type: " is builtin diff --git a/lang/tsconfig.json b/lang/tsconfig.json new file mode 100644 index 000000000..cbcbe30d0 --- /dev/null +++ b/lang/tsconfig.json @@ -0,0 +1,12 @@ +{ + "include": ["src/**/*.js", "src/**/*.d.ts"], + "exclude": [".pyret/**/*", "node_modules"], + "compilerOptions": { + "strict": true, + "allowJs": true, + "checkJs": true, + "module": "nodenext", + "target": "esnext", + "noEmit": true + } +} diff --git a/lsp/.gitignore b/lsp/.gitignore new file mode 100644 index 000000000..75dc6efa4 --- /dev/null +++ b/lsp/.gitignore @@ -0,0 +1,2 @@ +/node_modules +/out diff --git a/lsp/README.md b/lsp/README.md new file mode 100644 index 000000000..e4246104c --- /dev/null +++ b/lsp/README.md @@ -0,0 +1,13 @@ +To hack on the LSP, you must jump through a few build hoops +(at least until we get some better monorepo make rules): +1. In `lang`, make sure to run `make libA`. This builds the libraries + (in `libs.arr`) in addition to the compiler itself, and is needed + by the LSP. +2. Build CPO, following the instructions in the README + (it should reference `lang` via symlink) +3. Build this server, using `npm run build` +4. Build and launch the VSCode extension for debugging, following the + instructions in the README + +To see the server's logged messages, check VSCode's Output tab +(`CTRL`+`SHIFT`+`U`) and select `Pyret Language Server`. diff --git a/lsp/package-lock.json b/lsp/package-lock.json new file mode 100644 index 000000000..2d4704b88 --- /dev/null +++ b/lsp/package-lock.json @@ -0,0 +1,125 @@ +{ + "name": "pyret-lsp", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pyret-lsp", + "version": "1.0.0", + "license": "UNLICENSED", + "dependencies": { + "@types/ws": "^8.18.1", + "vscode-languageserver": "^9.0.1", + "vscode-languageserver-textdocument": "^1.0.12", + "ws": "^8.19.0" + }, + "devDependencies": { + "@types/node": "^25.3.0", + "typescript": "^5.9.3" + } + }, + "node_modules/@types/node": { + "version": "25.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.0.tgz", + "integrity": "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "license": "MIT" + }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", + "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "license": "MIT", + "dependencies": { + "vscode-languageserver-protocol": "3.17.5" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "license": "MIT" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "license": "MIT" + }, + "node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/lsp/package.json b/lsp/package.json new file mode 100644 index 000000000..e633a5026 --- /dev/null +++ b/lsp/package.json @@ -0,0 +1,23 @@ +{ + "name": "pyret-lsp", + "version": "1.0.0", + "description": "", + "license": "UNLICENSED", + "author": "", + "type": "commonjs", + "main": "index.js", + "scripts": { + "build": "tsc -p tsconfig.json", + "watch": "tsc -p tsconfig.json --watch" + }, + "dependencies": { + "@types/ws": "^8.18.1", + "vscode-languageserver": "^9.0.1", + "vscode-languageserver-textdocument": "^1.0.12", + "ws": "^8.19.0" + }, + "devDependencies": { + "@types/node": "^25.3.0", + "typescript": "^5.9.3" + } +} diff --git a/lsp/src/server-browser.ts b/lsp/src/server-browser.ts new file mode 100644 index 000000000..aae16f968 --- /dev/null +++ b/lsp/src/server-browser.ts @@ -0,0 +1,12 @@ +import { + BrowserMessageReader, + BrowserMessageWriter, + createConnection, +} from 'vscode-languageserver/browser'; +import { setupServer } from './server-shared'; + +const messageReader = new BrowserMessageReader(globalThis); +const messageWriter = new BrowserMessageWriter(globalThis); + +const connection = createConnection(messageReader, messageWriter); +setupServer(connection); diff --git a/lsp/src/server-node-tmp.ts b/lsp/src/server-node-tmp.ts new file mode 100644 index 000000000..2876f8e9b --- /dev/null +++ b/lsp/src/server-node-tmp.ts @@ -0,0 +1,252 @@ +import { createConnection, ProposedFeatures } from "vscode-languageserver/node"; + +import { + Connection, + ServerCapabilities, + TextDocuments, + TextDocumentSyncKind, +} from "vscode-languageserver"; +import { TextDocument } from "vscode-languageserver-textdocument"; +import * as childProcess from "child_process"; +import * as path from "path"; +import * as fs from "fs"; +import * as os from "os"; +import WebSocket from "ws"; + +const connection = createConnection(ProposedFeatures.all); + +// Pyret server management +const compilerPath = path.join( + __dirname, + "..", + "..", + "lang", + "build", + "phaseA", + "pyret.jarr", +); +let pyretServerProcess: childProcess.ChildProcess | null = null; + +function getSocketPath(): string { + const name = "parley-lsp-" + os.userInfo().username; + const dir = path.join(os.tmpdir(), name); + + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + + return path.join(dir, `comm-${process.pid}.sock`); +} + +function startPyretServer(portFile: string): Promise { + return new Promise((resolve, reject) => { + connection.console.log( + `Starting Pyret server with compiler at: ${compilerPath}`, + ); + + if (!fs.existsSync(compilerPath)) { + reject(new Error(`Compiler not found at ${compilerPath}`)); + return; + } + + const child = childProcess.fork( + compilerPath, + ["-serve", "--port", portFile], + { + stdio: [0, 1, 2, "ipc"], + execArgv: ["-max-old-space-size=8192"], + }, + ); + + pyretServerProcess = child; + + child.on("message", (msg: any) => { + if (msg.type === "success") { + connection.console.log("Pyret server started successfully"); + child.unref(); + child.disconnect(); + resolve(); + } else { + reject(msg); + } + }); + + child.on("error", (err) => { + connection.console.error(`Pyret server error: ${err.message}`); + reject(err); + }); + + child.on("exit", (code, signal) => { + connection.console.log( + `Pyret server exited with code ${code}, signal ${signal}`, + ); + pyretServerProcess = null; + }); + }); +} + +function shutdownPyretServer(portFile: string): void { + if (fs.existsSync(portFile)) { + try { + fs.unlinkSync(portFile); + } catch (e) { + connection.console.warn(`Could not remove port file: ${e}`); + } + } + + if (pyretServerProcess) { + pyretServerProcess.kill(); + pyretServerProcess = null; + } +} + +connection.onInitialize(async (_params) => { + // NOTE(lsp): Register new LSP capabilities here (e.g. hoverProvider: true) + const capabilities: ServerCapabilities = { + textDocumentSync: TextDocumentSyncKind.Incremental, + definitionProvider: true, + }; + + const portFile = getSocketPath(); + try { + if (!fs.existsSync(portFile)) { + await startPyretServer(portFile); + } else { + connection.console.log("Pyret server already running at " + portFile); + } + } catch (err) { + connection.console.error(`Failed to start Pyret server: ${err}`); + } + + return { capabilities }; +}); + +connection.onShutdown((_params) => { + const portFile = getSocketPath(); + shutdownPyretServer(portFile); +}); + +interface JumpToDefSuccess { + uri: string; + startLine: number; + startColumn: number; + endLine: number; + endColumn: number; +} + +// NOTE(lsp): To add a new LSP feature: +// 1. Add a send*Request function below (following this pattern) +// 2. Add a connection.on* handler at the bottom that calls it +// 3. Register the capability in onInitialize +// 4. Add an info-type case in server.arr's info handler +// 5. Add the Pyret-side logic in lsp.arr + +function sendJumpToDefRequest( + portFile: string, + filePath: string, + line: number, + col: number, +): Promise { + return new Promise((resolve, reject) => { + const client = new WebSocket("ws+unix://" + portFile); + let settled = false; + + client.on("error", (err) => { + if (!settled) { + settled = true; + reject(err); + } + }); + + client.on("open", () => { + client.send( + JSON.stringify({ + command: "query", + query: "jump-to-def", + compileOptions: JSON.stringify({ + program: filePath, + "base-dir": ".", // TODO + }), // TODO allow configuring default compileOptions + queryOptions: JSON.stringify({ line, col }), + }), + ); + }); + + client.on("message", (data: WebSocket.RawData) => { + const msg = JSON.parse(data.toString()); + if (msg.type === "jump-to-def-success") { + if (!settled) { + settled = true; + resolve({ + uri: msg.uri, + startLine: msg["start-line"], + startColumn: msg["start-column"], + endLine: msg["end-line"], + endColumn: msg["end-column"], + }); + } + } else if (msg.type === "jump-to-def-failure") { + if (!settled) { + settled = true; + resolve(null); + } + } else if (msg.type === "echo-err") { + connection.console.error("[pyret jump-to-def] " + msg.contents); + } else if (msg.type === "echo-log") { + connection.console.log("[pyret jump-to-def] " + msg.contents); + } + }); + + client.on("close", () => { + if (!settled) { + settled = true; + resolve(null); + } + }); + }); +} + +connection.onDefinition(async (params) => { + const portFile = getSocketPath(); + if (!fs.existsSync(portFile)) { + connection.console.error( + "Pyret server not running, cannot jump to definition", + ); + return null; + } + + // LSP positions are 0-indexed; Pyret srclocs are 1-indexed + const line = params.position.line + 1; + const col = params.position.character + 1; + + // Strip file:// scheme to get a plain file path + const fileUri = params.textDocument.uri; + const filePath = fileUri.startsWith("file://") + ? decodeURIComponent(fileUri.slice(7)) + : fileUri; + + try { + const result = await sendJumpToDefRequest(portFile, filePath, line, col); + if (!result) { + return null; + } + + return { + uri: result.uri, + range: { + start: { + line: result.startLine - 1, + character: result.startColumn, + }, + end: { line: result.endLine - 1, character: result.endColumn }, + }, + }; + } catch (err) { + connection.console.error(`jump-to-def error: ${err}`); + return null; + } +}); + +const documents = new TextDocuments(TextDocument); +documents.listen(connection); +connection.listen(); diff --git a/lsp/src/server-node.ts b/lsp/src/server-node.ts new file mode 100644 index 000000000..5bb5217ae --- /dev/null +++ b/lsp/src/server-node.ts @@ -0,0 +1,5 @@ +import { createConnection, ProposedFeatures } from 'vscode-languageserver/node'; +import { setupServer } from './server-shared'; + +const connection = createConnection(ProposedFeatures.all); +setupServer(connection); diff --git a/lsp/src/server-shared.ts b/lsp/src/server-shared.ts new file mode 100644 index 000000000..f615bc977 --- /dev/null +++ b/lsp/src/server-shared.ts @@ -0,0 +1,22 @@ +import { + Connection, + ServerCapabilities, + TextDocuments, + TextDocumentSyncKind, +} from 'vscode-languageserver'; +import { TextDocument } from 'vscode-languageserver-textdocument'; + +export function setupServer(connection: Connection) { + connection.onInitialize(_params => { + const capabilities: ServerCapabilities = { + textDocumentSync: TextDocumentSyncKind.Incremental, + }; + return { capabilities }; + }); + + connection.onShutdown(_params => {}); + + const documents = new TextDocuments(TextDocument); + documents.listen(connection); + connection.listen(); +} diff --git a/lsp/tsconfig.json b/lsp/tsconfig.json new file mode 100644 index 000000000..d4525ec76 --- /dev/null +++ b/lsp/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "ES2020", + "outDir": "out", + "rootDir": "src", + "sourceMap": true, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules"] +} diff --git a/vscode/.vscode/launch.json b/vscode/.vscode/launch.json new file mode 100644 index 000000000..bb95fd1d8 --- /dev/null +++ b/vscode/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": ["--extensionDevelopmentPath=${workspaceRoot}"], + "outFiles": ["${workspaceFolder}/out/**/*.js"], + "preLaunchTask": "npm: compile" + } + ] +} diff --git a/vscode/README.dev.md b/vscode/README.dev.md index eb52c6f99..579f18e34 100644 --- a/vscode/README.dev.md +++ b/vscode/README.dev.md @@ -1,10 +1,10 @@ -Borrows heavily from https://github.com/microsoft/vscode-extension-samples/tree/main/custom-editor-sample +Borrows heavily from To run, first you must symlink `build` to a the `build/` directory of `code.pyret.org`. You can get one by cloning `code.pyret.org` elsewhere and symlinking to it. -Then: +Then, to test the web extension (in a new `github.dev`-like browser environment): ``` npm i @@ -12,6 +12,11 @@ npm run compile npx vscode-test-web --browserType=chromium --extensionDevelopmentPath . ./sampleFiles/ ``` +To test locally without the web extension (say, to test or debug a feature that +is not yet properly supported on the web), the easiest way to do so is to +1. Open up `src/extension.ts`, then +2. Press F5 (or run `Debug: Start Debugging` from the Command Palette) + User settings for avoiding diff views using the fancy editor; put in `.vscode/settings.json` (or set via the menu): @@ -23,6 +28,6 @@ User settings for avoiding diff views using the fancy editor; put in } ``` -(Courtesy of https://github.com/microsoft/vscode-discussions/discussions/799) +(Courtesy of ) Grammar and language-configuration contributed by Seth Poulsen. diff --git a/vscode/package-lock.json b/vscode/package-lock.json index 641457fb2..7f5bba270 100644 --- a/vscode/package-lock.json +++ b/vscode/package-lock.json @@ -9,7 +9,8 @@ "version": "0.42.40", "license": "Apache-2.0", "dependencies": { - "mustache": "^4.2.0" + "mustache": "^4.2.0", + "vscode-languageclient": "^9.0.1" }, "devDependencies": { "@types/mustache": "^4.2.5", @@ -3455,16 +3456,23 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/bare-events": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz", - "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==", + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", + "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", "dev": true, "license": "Apache-2.0", - "optional": true + "optional": true, + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } }, "node_modules/bare-fs": { "version": "4.0.1", @@ -3940,7 +3948,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -12812,7 +12819,6 @@ "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -15264,6 +15270,57 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageclient": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-9.0.1.tgz", + "integrity": "sha512-JZiimVdvimEuHh5olxhxkht09m3JzUGwggb5eRUkzzJhZ2KjCN0nh55VfiED9oez9DyF8/fz1g1iBV3h+0Z2EA==", + "license": "MIT", + "dependencies": { + "minimatch": "^5.1.0", + "semver": "^7.3.7", + "vscode-languageserver-protocol": "3.17.5" + }, + "engines": { + "vscode": "^1.82.0" + } + }, + "node_modules/vscode-languageclient/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "license": "MIT" + }, "node_modules/vscode-uri": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", diff --git a/vscode/package.json b/vscode/package.json index e4def3a66..894ffd6da 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -22,6 +22,7 @@ "syntaxes/pyret.tmLanguage.json" ], "browser": "./dist/web/extension.js", + "main": "./dist/extension.js", "contributes": { "languages": [ { @@ -118,7 +119,14 @@ "Load `url-file()` imports from the local filesystem if they exist, otherwise from the URL" ], "description": "Controls how `url-file()` imports are resolved" - } + }, + "pyret.trace.server": { + "type": "string", + "enum": ["off", "messages", "verbose"], + "default": "verbose", + "scope": "resource", + "description": "Traces logs of the communication between VS Code and the language server." + } } } }, @@ -147,6 +155,7 @@ "yo": "^5.1.0" }, "dependencies": { - "mustache": "^4.2.0" + "mustache": "^4.2.0", + "vscode-languageclient": "^9.0.1" } } diff --git a/vscode/src/extension.ts b/vscode/src/extension.ts new file mode 100644 index 000000000..438e89272 --- /dev/null +++ b/vscode/src/extension.ts @@ -0,0 +1,67 @@ +import * as vscode from "vscode"; +import * as path from "path"; +import { PyretCPOWebProvider, makeCommandHandler } from "./pyretCPOWebEditor"; +import { + LanguageClient, + LanguageClientOptions, + ServerOptions, + TransportKind, +} from "vscode-languageclient/node"; + +let client: LanguageClient; + +export function activate(context: vscode.ExtensionContext) { + context.subscriptions.push(PyretCPOWebProvider.register(context)); + context.subscriptions.push( + vscode.commands.registerCommand( + "pyret-parley.run-file", + makeCommandHandler(context), + ), + ); + + const serverModule = context.asAbsolutePath( + path.join("..", "lsp", "out", "server-node-tmp.js"), + ); + const debugOptions = { execArgv: ["--nolazy", "--inspect=6009"] }; + const outputChannel = vscode.window.createOutputChannel( + "Pyret Server", + ); + const traceOutputChannel = vscode.window.createOutputChannel( + "Pyret Language Server", + ); + + const serverOptions: ServerOptions = { + run: { module: serverModule, transport: TransportKind.ipc }, + debug: { + module: serverModule, + transport: TransportKind.ipc, + options: debugOptions, + }, + }; + + const clientOptions: LanguageClientOptions = { + documentSelector: [{ scheme: "file", language: "pyret" }], + synchronize: { + fileEvents: vscode.workspace.createFileSystemWatcher("**/*.arr"), + }, + outputChannel: outputChannel, + traceOutputChannel: traceOutputChannel, + }; + + client = new LanguageClient( + "pyret", + "Pyret Language Server", + serverOptions, + clientOptions, + ); + + client.start(); + outputChannel.appendLine("Pyret Language Server started"); +} + +export function deactivate(): Thenable | undefined { + if (!client) { + return undefined; + } + return client.stop(); +} diff --git a/vscode/src/webExtension.ts b/vscode/src/webExtension.ts index 34dd42554..bd73e9cdb 100644 --- a/vscode/src/webExtension.ts +++ b/vscode/src/webExtension.ts @@ -5,4 +5,4 @@ export function activate(context: vscode.ExtensionContext) { // Register our custom editor providers context.subscriptions.push(PyretCPOWebProvider.register(context)); context.subscriptions.push(vscode.commands.registerCommand("pyret-parley.run-file", makeCommandHandler(context))); -} \ No newline at end of file +} diff --git a/vscode/webpack.config.js b/vscode/webpack.config.js index 9893296ff..dd706f861 100644 --- a/vscode/webpack.config.js +++ b/vscode/webpack.config.js @@ -87,4 +87,47 @@ const webExtensionConfig = { }, }; -module.exports = [ webExtensionConfig ]; \ No newline at end of file +/** @type WebpackConfig */ +const nodeExtensionConfig = { + mode: 'none', + target: 'node', // extensions run in a Node.js context + entry: { + 'extension': './src/extension.ts' + }, + output: { + filename: '[name].js', + path: path.join(__dirname, './dist'), + libraryTarget: 'commonjs2', + devtoolModuleFilenameTemplate: '../[resource-path]' + }, + resolve: { + mainFields: ['main', 'module'], + extensions: ['.ts', '.js'], + }, + module: { + rules: [{ + test: /\.ts$/, + exclude: /node_modules/, + use: [{ + loader: 'ts-loader' + }] + }, + { + test: /\.html/, + type: 'asset/source' + } + ] + }, + externals: { + 'vscode': 'commonjs vscode', + }, + performance: { + hints: false + }, + devtool: 'nosources-source-map', + infrastructureLogging: { + level: "log", + }, +}; + +module.exports = [ webExtensionConfig, nodeExtensionConfig ]; \ No newline at end of file