Compare commits

..

85 Commits

Author SHA1 Message Date
28e72503a6 Update .github/workflows/tagged-release.yml
Some checks failed
tagged-release / Tagged Release (push) Failing after 2m14s
2025-01-01 22:33:57 +00:00
d0a0796e14 Merge pull request 'topchetoeu-bump-0-10' (#32) from topchetoeu-bump-0-10 into master
Some checks failed
tagged-release / Tagged Release (push) Failing after 11m3s
Reviewed-on: #32
2025-01-01 22:20:16 +00:00
0de54e5505 Update gradle.properties 2025-01-01 22:19:56 +00:00
96c9d29a6a Update .github/workflows/tagged-release.yml 2025-01-01 22:19:36 +00:00
37dc844cc4 fix: use typescript instead 2025-01-01 22:16:01 +00:00
b97e4bf163 improve: UserValue API 2024-12-28 13:20:04 +02:00
9ce0504948 fix: debugger concurrency issues 2024-12-28 13:19:53 +02:00
4c53689d9c feat: some behavioral fixes 2024-12-28 13:19:35 +02:00
6c8c329992 feat: improve transpiler infrastructure 2024-12-28 13:19:02 +02:00
74f08b7483 small stdlib improvements
Some checks failed
tagged-release / Tagged Release (push) Failing after 7m8s
2024-12-28 13:17:45 +02:00
2bf920c681 remove old soruce map primordials and add next tick scheduler 2024-12-27 19:18:41 +02:00
1ac68425af feat: implement basic promises 2024-12-27 19:18:18 +02:00
7423e3f283 feat: more sophisticated stdlib definitions 2024-12-27 19:16:55 +02:00
afe4542682 fix: concurrency issues with debug server 2024-12-27 19:16:26 +02:00
c971fde0e2 feat: implement binary number literals 2024-12-27 19:15:20 +02:00
8a34db833c refactor: implement source mapping in javascript, instead of java 2024-12-27 19:15:09 +02:00
398d88c0a5 add more functions in stdlib, fix some issues 2024-12-27 19:10:56 +02:00
f80266618c fix: throw copied return syntax too closely 2024-12-27 19:09:01 +02:00
f1997e4584 fix: incorrect continue jump location in for loops 2024-12-25 03:01:49 +02:00
e93498fed5 fix typescript compiler script 2024-12-25 02:56:16 +02:00
29bea68525 feat: add back debugger from old version 2024-12-25 02:55:54 +02:00
3a6401094f fix: don't omit stack trace elements without locations 2024-12-25 02:54:22 +02:00
a705bf296e fix: empty file yields an error 2024-12-25 02:53:53 +02:00
c3432306f7 fix: global object can be an arbitrary value 2024-12-25 02:53:44 +02:00
d8c18ccf17 more utility 2024-12-25 02:53:24 +02:00
277f59e54a prepare codebase for source maps 2024-12-25 02:53:11 +02:00
a1bb5bdba6 separate the call stack from the debug context 2024-12-25 02:52:10 +02:00
c699102e56 try-catch AGAIN 2024-12-25 02:51:02 +02:00
b2f5068e12 some utility methods 2024-12-25 02:50:37 +02:00
05bafc7317 improve json parsing 2024-12-25 02:48:37 +02:00
c7c31c3859 feat: implement a lot of built-ins 2024-12-25 02:48:04 +02:00
c18015b9a0 fix: wrong precedences of && and ||
(how the fuck did i get this wrong)
2024-12-13 02:33:25 +02:00
5346b8917d refactor: add semicolons and spaces -> tabs in gradle scripts 2024-12-13 02:33:01 +02:00
2ed7959088 refacor: remove pesky console.log 2024-12-13 02:29:55 +02:00
239d0ae8d7 feat: implement a lot of stdlibs 2024-12-13 02:29:41 +02:00
6fac295b99 fix: try-catch broken (again) 2024-12-13 02:29:09 +02:00
66440a9649 fix: a plethora of loop off by one and null issues 2024-12-13 02:28:36 +02:00
274a925ff8 fix: broken labeled jumps 2024-12-13 02:27:57 +02:00
130fe17441 missed you lil fucker 2024-12-13 02:27:32 +02:00
493c54ed39 fix: symbol was falsy 2024-12-13 02:27:21 +02:00
75786f39ad fix: access of symbols in string 2024-12-13 02:27:12 +02:00
bce8b7293c fix: parseInt was broken 2024-12-13 02:26:34 +02:00
00aeef5321 fix: make member algorithms correct 2024-12-13 02:26:12 +02:00
ff4aa3dcfd fix: return null when loading an inexistent resource 2024-12-11 11:53:29 +02:00
45f52ff123 fix: catch variable causing issues 2024-12-11 11:53:19 +02:00
bed4014bef feat: implement simple env + ts loader 2024-12-11 11:53:02 +02:00
17406c6b81 feat: create build process for environment 2024-12-11 11:51:03 +02:00
3abdd8d3c9 feat: add constructor target support 2024-12-10 15:37:39 +02:00
a329f615cf fix: "e1" literal read as a number exponent 2024-12-10 01:21:44 +02:00
058d20b27f feat: dead simple Map and RegExp implementations 2024-12-10 01:11:07 +02:00
ea158c1e60 fix: converts symbol key to string when assigning function to member 2024-12-10 01:10:43 +02:00
4e18c76bb1 some toStrings 2024-12-10 01:10:14 +02:00
8d7939d85a fix: wrong index calculation 2024-12-10 01:09:50 +02:00
814e0d7b7e feat: allow to invoke compiler with variable encapsulation enabled 2024-12-10 00:44:18 +02:00
138baebacb fix: call regex constructor correctly 2024-12-10 00:43:56 +02:00
db241919f2 fix: when capturing, must check if already in captures 2024-12-10 00:43:15 +02:00
db45eb529d change indentation to tabs 2024-12-09 23:58:43 +02:00
caf44a50e5 Merge pull request #31 from TopchetoEU:TopchetoEU/revert-ES5
TopchetoEU/revert-ES5
2024-12-09 23:39:57 +02:00
65f9debecc fix: use default construct method 2024-12-09 23:39:05 +02:00
3f5e1a5fd8 feat: implement user values 2024-12-09 23:38:53 +02:00
b0d8a072aa add hashCode to primitives 2024-12-09 23:38:39 +02:00
2e8e123ec4 small parser fixes 2024-12-09 23:37:08 +02:00
54d55814af fix: errors with out of range arguments 2024-12-09 22:16:24 +02:00
28679f44d5 fix: symbols not stringified properly 2024-12-09 22:15:51 +02:00
611be55bbb fix: should throw engine exceptions, not java exceptions 2024-12-09 22:15:38 +02:00
4992d0211b fix: nasty issues with compilation 2024-12-09 22:15:15 +02:00
ba7505e148 fix: globalThis and for-in not parsed 2024-11-24 12:49:31 +02:00
3c13799c2f feat: make function logging configurable 2024-11-24 12:49:04 +02:00
5c2fd00bfb fix: add location data for LOAD_FUNCs 2024-11-24 12:48:49 +02:00
39eb6ffac5 fix: do variable inits properly 2024-11-24 12:48:30 +02:00
7f6df49fc5 fix: scope issues 2024-11-24 12:47:51 +02:00
61c5df5003 fix: gd damn it 2024-11-24 12:47:15 +02:00
41bb27e4dd implement all changes in runtime 2024-11-23 20:15:42 +02:00
b4e7a42975 regress: remove ES6 stuff (except apply and construct constraints) from funcs 2024-11-23 20:11:57 +02:00
92fb0dbbfd regress: simplify invoke model 2024-11-23 20:11:12 +02:00
fe8f65faf5 some final stuff in parsing 2024-11-23 20:10:47 +02:00
54fe16393a regress: remove infrastructure for super references 2024-11-23 20:10:11 +02:00
14e4aade35 regress: remove infrastructure needed for ES6 stuff, simplify loops 2024-11-23 20:09:29 +02:00
754648fbf6 regress: remove ES6 instructions 2024-11-23 20:08:01 +02:00
20f2c3c5e9 regress: remove ES6 stuff from members 2024-11-23 20:07:10 +02:00
c5067cbfdd regress: remove ES6 variables and simplify scope 2024-11-23 20:06:24 +02:00
5644966dd7 regress: remove ES6 nodes 2024-11-23 20:06:09 +02:00
50eb204da7 fix: remove unnecessary reference from core to compiler 2024-11-23 20:04:19 +02:00
45308e6d65 refactor: remove periods from ends of error msgs 2024-11-23 20:04:03 +02:00
0ebf189c95 fix: remove multi-key bullcrap 2024-11-23 20:01:00 +02:00
200 changed files with 14374 additions and 10539 deletions

View File

@@ -11,6 +11,8 @@ jobs:
runs-on: "ubuntu-latest" runs-on: "ubuntu-latest"
steps: steps:
- name: Clone repository
uses: actions/checkout@v2
- name: Setup Java - name: Setup Java
uses: actions/setup-java@v3 uses: actions/setup-java@v3
with: with:
@@ -18,16 +20,13 @@ jobs:
java-version: '11' java-version: '11'
- name: Setup Gradle - name: Setup Gradle
uses: gradle/gradle-build-action@v2 uses: gradle/gradle-build-action@v2
- name: Clone repository
uses: GuillaumeFalourd/clone-github-repo-action@main
with: with:
branch: 'master' cache-disabled: true
owner: 'TopchetoEU' gradle-version: "8.10"
repository: 'java-jscript' - name: NPM
run: npm i
- name: Build - name: Build
run: | run: gradle build
cd java-jscript; gradle build
- uses: "marvinpinto/action-automatic-releases@latest" - uses: "marvinpinto/action-automatic-releases@latest"
with: with:
repo_token: "${{ secrets.GITHUB_TOKEN }}" repo_token: "${{ secrets.GITHUB_TOKEN }}"

16
.gitignore vendored
View File

@@ -1,24 +1,18 @@
* /*
!/src !/src
!/src/**/*
!/doc !/doc
!/doc/**/*
!/tests !/tests
!/tests/**/*
!/.github !/.github
!/.github/**/*
!/.gitignore !/.gitignore
!/.gitattributes !/.gitattributes
!/LICENSE !/LICENSE
!/README.md !/README.md
!/settings.gradle !/settings.gradle
!/build.gradle !/build.gradle
!/gradle.properties !/gradle.properties
!/gradle
!/gradle/wrapper !/package.json
!/gradle/wrapper/gradle-wrapper.properties !/rollup.config.js

View File

@@ -1,90 +1,124 @@
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
plugins { plugins {
id 'application' id 'application';
id 'net.nemerosa.versioning' version '2.15.0' id 'com.github.node-gradle.node' version '5.0.0';
id 'org.ajoberstar.grgit' version '5.0.0-rc.3' // required by gradle id 'net.nemerosa.versioning' version '2.15.0';
id 'org.ajoberstar.grgit' version '5.0.0-rc.3'; // required by gradle
// TODO: figure out how to integrate proguard // TODO: figure out how to integrate proguard
// id "com.github.xaverkapeller.proguard-annotations" // id "com.github.xaverkapeller.proguard-annotations"
}
base.archivesName = project.project_name;
version = project.project_version;
group = project.project_group;
description = 'ES5-compliant JavaScript interpreter';
node {
version = '20.0.0';
npmVersion = '8.0.0';
download = true;
}
task compileEnv(type: NpmTask) {
inputs.files('rollup.config.js');
inputs.dir('src/lib/libs');
outputs.files("build/js/env.js");
// group = 'build'
args = ['run', 'build-env'];
}
task compileTypescript(type: NpmTask) {
inputs.files('rollup.config.js');
inputs.dir('src/lib/transpiler');
outputs.files("build/js/ts.js");
// nom nom tasty ram
environment.put("NODE_OPTIONS", "--max-old-space-size=4096");
// group = 'build'
args = ['run', 'build-ts'];
} }
base.archivesName = project.project_name
version = project.project_version
group = project.project_group
description = 'ES5-compliant JavaScript interpreter'
repositories { repositories {
mavenCentral() mavenCentral();
} }
dependencies { dependencies {
annotationProcessor 'com.github.bsideup.jabel:jabel-javac-plugin:0.4.2' annotationProcessor 'com.github.bsideup.jabel:jabel-javac-plugin:0.4.2';
compileOnly 'com.github.bsideup.jabel:jabel-javac-plugin:0.4.2' compileOnly 'com.github.bsideup.jabel:jabel-javac-plugin:0.4.2';
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2' testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2';
testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testRuntimeOnly 'org.junit.platform:junit-platform-launcher';
} }
java { java {
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_17;
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17;
toolchain { toolchain {
languageVersion = JavaLanguageVersion.of(17) languageVersion = JavaLanguageVersion.of(17);
} }
} }
configure([tasks.compileJava]) { configure([tasks.compileJava]) {
options.release = 8 options.release = 8;
} }
jar { jar {
manifest { manifest {
attributes( attributes(
'Main-Class': project.main_class, 'Main-Class': project.main_class,
'Build-Timestamp': new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()), 'Build-Timestamp': new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()),
'Build-Branch': versioning.info.branch, 'Build-Branch': versioning.info.branch,
'Build-Revision': versioning.info.commit, 'Build-Revision': versioning.info.commit,
'Build-Jdk': "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']})", 'Build-Jdk': "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']})",
'Build-Author': 'TopchetoEU' 'Build-Author': 'TopchetoEU',
) );
} }
} }
application { application {
mainClass = project.main_class mainClass = project.main_class;
applicationDefaultJvmArgs = ['-Xmx2G', '-Xms2G', '-server', '-Dfile.encoding=UTF-8'] applicationDefaultJvmArgs = ['-Xmx2G', '-Xms2G', '-server', '-Dfile.encoding=UTF-8'];
} }
distZip { distZip {
eachFile { file -> eachFile { file ->
if (file.path.contains('bin')) { if (file.path.contains('bin')) {
file.exclude() file.exclude();
} }
} }
} }
distTar { distTar {
eachFile { file -> eachFile { file ->
if (file.path.contains('bin')) { if (file.path.contains('bin')) {
file.exclude() file.exclude();
} }
} }
} }
processResources { processResources {
filesMatching "metadata.json", { dependsOn compileEnv;
expand( dependsOn compileTypescript;
version: project.project_version,
name: project.project_name from("build/js") {
) into "lib";
} }
filesMatching "metadata.json", {
expand(
version: project.project_version,
name: project.project_name,
);
}
} }
test { test {
useJUnitPlatform() useJUnitPlatform();
} }
wrapper { wrapper {
gradleVersion = '8.10' gradleVersion = '8.10';
} }

View File

@@ -1,4 +1,4 @@
project_group = me.topchetoeu project_group = me.topchetoeu
project_name = jscript project_name = jscript
project_version = 0.9.41-beta project_version = 0.10-beta
main_class = me.topchetoeu.jscript.runtime.SimpleRepl main_class = me.topchetoeu.jscript.repl.SimpleRepl

View File

@@ -1,7 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

31
package.json Normal file
View File

@@ -0,0 +1,31 @@
{
"scripts": {
"build-env": "rollup -c --environment INPUT:src/lib/libs/_entry.ts,OUTPUT:build/js/index.js,POLYFILLS:src/lib/libs/polyfills",
"build-ts": "rollup -c --environment INPUT:src/lib/transpiler/_entry.ts,OUTPUT:build/js/ts.js"
},
"dependencies": {
"@babel/core": "^7.26.0",
"@babel/runtime": "^7.26.0",
"@babel/standalone": "^7.26.4",
"@rollup/plugin-json": "^6.1.0",
"@types/babel__preset-env": "^7.9.7",
"@types/babel__standalone": "^7.1.9",
"@types/coffeescript": "^2.5.7",
"coffeescript": "^2.7.0",
"typescript": "^5.7.2"
},
"devDependencies": {
"@babel/plugin-transform-class-properties": "^7.25.9",
"@babel/plugin-transform-runtime": "^7.25.9",
"@babel/plugin-transform-typescript": "^7.25.9",
"@babel/preset-env": "^7.26.0",
"@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-commonjs": "^28.0.1",
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^12.1.1",
"@types/node": "^22.10.1",
"rollup": "^4.24.0",
"tslib": "^2.8.0"
}
}

130
rollup.config.js Normal file
View File

@@ -0,0 +1,130 @@
const { defineConfig } = require("rollup");
const terser = require("@rollup/plugin-terser");
const typescript = require("@rollup/plugin-typescript");
const babel = require("@rollup/plugin-babel");
const commonjs = require("@rollup/plugin-commonjs");
const nodeResolve = require("@rollup/plugin-node-resolve");
const json = require("@rollup/plugin-json");
const { resolve } = require("path");
const shouldMinify = () => false;
const shouldEmitSourcemaps = () => true;
const shouldPolyfill = () => !!process.env.POLYFILLS;
const construct = (input, output) => defineConfig({
input,
plugins: [
shouldPolyfill() && {
name: "babel-fake-runtime",
resolveId(source) {
if (source.startsWith("!polyfills:/helpers")) return resolve(process.env.POLYFILLS) + source.slice(19) + ".js";
}
},
commonjs(),
nodeResolve(),
json(),
babel({
extensions: [],
exclude: ["node_modules/**"],
babelHelpers: "runtime",
plugins: [
["@babel/plugin-transform-typescript", {
onlyRemoveTypeImports: true,
optimizeConstEnums: true,
allowDeclareFields: true,
}],
["@babel/plugin-transform-class-properties"],
["@babel/plugin-transform-runtime", {
moduleName: shouldPolyfill() ? "!polyfills:" : undefined,
version: "^7.24.0",
}],
]
}),
babel({
extensions: [],
exclude: shouldPolyfill() ? [process.env.POLYFILLS + "/**"] : [],
assumptions: {
ignoreToPrimitiveHint: true,
noClassCalls: true,
},
env: {
development: { compact: false },
},
babelHelpers: "runtime",
plugins: [
"@babel/plugin-transform-arrow-functions",
"@babel/plugin-transform-block-scoping",
"@babel/plugin-transform-classes",
"@babel/plugin-transform-computed-properties",
"@babel/plugin-transform-destructuring",
"@babel/plugin-transform-for-of",
"@babel/plugin-transform-object-super",
"@babel/plugin-transform-parameters",
"@babel/plugin-transform-shorthand-properties",
"@babel/plugin-transform-spread",
"@babel/plugin-transform-object-rest-spread",
"@babel/plugin-transform-template-literals",
"@babel/plugin-transform-unicode-escapes",
"@babel/plugin-transform-unicode-regex",
"@babel/plugin-transform-exponentiation-operator",
"@babel/plugin-transform-async-to-generator",
"@babel/plugin-transform-async-generator-functions",
"@babel/plugin-transform-nullish-coalescing-operator",
"@babel/plugin-transform-optional-chaining",
"@babel/plugin-transform-logical-assignment-operators",
"@babel/plugin-transform-numeric-separator",
"@babel/plugin-transform-class-properties",
"@babel/plugin-transform-class-static-block",
"@babel/plugin-transform-regenerator",
["@babel/plugin-transform-runtime", {
moduleName: shouldPolyfill() ? "!polyfills:" : undefined,
version: "^7.24.0",
}],
],
}),
typescript({
exclude: ["node_modules/**", "*.js"],
compilerOptions: {
allowImportingTsExtensions: true,
noEmit: true,
},
noForceEmit: true,
noEmitOnError: true,
}),
shouldMinify() && terser({
sourceMap: shouldEmitSourcemaps(),
keep_classnames: true,
}),
],
output: {
file: output,
format: "iife",
globals: {
fs: "null",
path: "null",
os: "null",
inspector: "null",
tty: "null",
util: "null",
assert: "null",
url: "null",
"@babel/preset-typescript/package.json": "null",
module: "null",
process: "null",
v8: "null",
},
// plugins: [babel.getBabelOutputPlugin({
// allowAllFormats: true,
// })],
sourcemap: shouldEmitSourcemaps(),
inlineDynamicImports: true,
},
});
module.exports = construct(process.env.INPUT, process.env.OUTPUT);

View File

@@ -1,13 +1,12 @@
pluginManagement { pluginManagement {
repositories { repositories {
mavenCentral() mavenCentral();
gradlePluginPortal() gradlePluginPortal();
} }
} }
plugins { plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0' id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0';
} }
rootProject.name = properties.project_name;
rootProject.name = properties.project_name

81
src/lib/libs/_entry.ts Normal file
View File

@@ -0,0 +1,81 @@
import { object, setGlobalPrototypes, target } from "./primordials.ts";
import { Error, RangeError, SyntaxError, TypeError } from "./errors.ts";
import { Boolean } from "./boolean.ts";
import { Function } from "./function.ts";
import { Number } from "./number.ts";
import { Object } from "./object.ts";
import { String } from "./string.ts";
import { Symbol } from "./symbol.ts";
import { Array } from "./array.ts";
import { Map, WeakMap } from "./map.ts";
import { RegExp } from "./regex.ts";
import { Date } from "./date.ts";
import { Math as _Math } from "./math.ts";
import { Set, WeakSet } from "./set.ts";
import { JSON } from "./json.ts";
import { console } from "./console.ts";
import { encodeURI, encodeURIComponent } from "./url.ts";
import { Promise } from "./promise.ts";
declare global {
function print(...args: any[]): void;
function measure(func: Function): void;
}
function fixup<T extends Function>(clazz: T) {
object.setPrototype(clazz, Function.prototype);
object.setPrototype(clazz.prototype as any, Object.prototype);
return clazz;
}
object.setPrototype(target, Object.prototype);
object.defineField(target, "undefined", { e: false, c: false, w: false, v: void 0 });
target.Symbol = fixup(Symbol);
target.Number = fixup(Number);
target.String = fixup(String);
target.Boolean = fixup(Boolean);
target.Object = Object;
target.Function = fixup(Function);
target.Array = fixup(Array);
target.Error = fixup(Error);
target.RangeError = RangeError;
target.SyntaxError = SyntaxError;
target.TypeError = TypeError;
target.Map = fixup(Map);
target.WeakMap = fixup(WeakMap);
target.Set = fixup(Set);
target.WeakSet = fixup(WeakSet);
target.RegExp = fixup(RegExp);
target.Date = fixup(Date);
target.Promise = fixup(Promise);
target.Math = object.setPrototype(_Math, Object.prototype);
target.JSON = object.setPrototype(JSON, Object.prototype);
target.console = object.setPrototype(console, Object.prototype);
target.TYPED_ARRAY_SUPPORT = false;
target.parseInt = Number.parseInt;
target.parseFloat = Number.parseFloat;
target.NaN = Number.NaN;
target.Infinity = Number.POSITIVE_INFINITY;
target.encodeURI = encodeURI;
target.encodeURIComponent = encodeURIComponent;
setGlobalPrototypes({
string: String.prototype,
number: Number.prototype,
boolean: Boolean.prototype,
symbol: Symbol.prototype,
object: Object.prototype,
array: Array.prototype,
function: Function.prototype,
error: Error.prototype,
syntax: SyntaxError.prototype,
range: RangeError.prototype,
type: TypeError.prototype,
regex: RegExp,
});

330
src/lib/libs/array.ts Normal file
View File

@@ -0,0 +1,330 @@
import { Error } from "./errors.ts";
import { func, object, string } from "./primordials.ts";
import { String } from "./string.ts";
import { limitI, symbols, wrapI } from "./utils.ts";
export const Array = (() => {
class Array {
public forEach(this: any[], cb: (val: any, i: number, self: this) => void, self?: any) {
for (let i = 0; i < this.length; i++) {
if (i in this) func.invoke(cb, self, [this[i], i, this]);
}
}
public join(this: any[], delim = ",") {
delim = String(delim);
const parts = [];
if (delim) {
for (let i = 0; i < this.length; i++) {
if (i) parts[parts.length] = delim;
parts[parts.length] = (i in this) ? String(this[i]) : "";
}
}
else {
for (let i = 0; i < this.length; i++) {
parts[i] = (i in this) ? String(this[i]) : "";
}
}
return string.stringBuild(parts);
}
public push(this: any[]) {
const start = this.length;
for (let i = arguments.length - 1; i >= 0; i--) {
this[start + i] = arguments[i];
}
return arguments.length;
}
public pop(this: any[]) {
if (this.length === 0) return undefined;
else {
const res = this[this.length - 1];
this.length--;
return res;
}
}
public unshift(this: any[]) {
for (let i = this.length + arguments.length - 1; i >= arguments.length; i--) {
this[i] = this[i - arguments.length];
}
for (let i = 0; i < arguments.length; i++) {
this[i] = arguments[i];
}
return arguments.length;
}
public shift(this: any[]) {
if (this.length === 0) return undefined;
const tmp = this[0];
for (let i = 1; i < this.length; i++) {
this[i - 1] = this[i];
}
this.length--;
return tmp;
}
public concat(this: any[]) {
const res: any[] = [];
function add(arr: any) {
if (Array.isArray(arr) || symbols.isConcatSpreadable in arr) {
const start = res.length;
res.length += arr.length;
for (let i = 0; i < res.length; i++) {
if (i in arr) res[start + i] = arr[i];
}
}
else res[res.length] = arr;
}
add(this);
for (let i = 0; i < arguments.length; i++) {
add(arguments[i]);
}
return res;
}
public slice(this: any[], start = 0, end = this.length) {
start = wrapI(start, this.length);
end = wrapI(end, this.length);
if (end <= start) return [];
const res: any[] = [];
res.length = end - start;
for (let i = 0; i < end - start; i++) {
res[i] = this[start + i];
}
return res;
}
public splice(this: any[], start = 0, count = this.length - start) {
const vals: any[] = []
for (let i = 0; i < arguments.length - 2; i++) vals[i] = arguments[i + 2];
start = limitI(wrapI(start, this.length), this.length);
count = limitI(wrapI(count, this.length), this.length - start);
const res: any[] = [];
const change = vals.length - count;
for (let i = start; i < start + count; i++) {
res[i - start] = this[i];
}
if (change < 0) {
for (let i = start - change; i < this.length; i++) {
this[i + change] = this[i];
}
this.length = this.length + change;
}
else {
for (let i = this.length - 1; i >= start - change; i--) {
this[i + change] = this[i];
}
}
for (let i = 0; i < vals.length; i++) {
this[i + start] = vals[i];
}
return res;
}
public map(this: any[], cb: Function, self?: any) {
const res = [];
res.length = this.length;
for (let i = 0; i < this.length; i++) {
if (i in this) res[i] = func.invoke(cb, self, [this[i], i, this]);
}
return res;
}
public filter(this: any[], cb: Function, self?: any) {
const res = [];
for (let i = 0; i < this.length; i++) {
if (i in this && func.invoke(cb, self, [this[i], i, this])) res[res.length] = this[i];
}
return res;
}
public reduce(this: any[], cb: Function, initial: any) {
let i = 0;
if (arguments.length <= 1) initial = this[i++];
for (; i < this.length; i++) {
initial = cb(initial, this[i], i, this);
}
return initial;
}
public some(this: any[], cb: Function, self?: any) {
for (let i = 0; i < this.length; i++) {
if (i in this && func.invoke(cb, self, [this[i], i, this])) return true;
}
return false;
}
public every(this: any[], cb: Function, self?: any) {
for (let i = 0; i < this.length; i++) {
if (i in this && !func.invoke(cb, self, [this[i], i, this])) return false;
}
return true;
}
public find(this: any[], cb: Function, self?: any) {
for (let i = 0; i < this.length; i++) {
if (i in this && func.invoke(cb, self, [this[i], i, this])) return this[i];
}
return undefined;
}
public indexOf(this: any[], val: any, start = 0) {
start |= 0;
if (start < 0) start = 0;
for (let i = start; i < this.length; i++) {
if (i in this && this[i] === val) return i;
}
return -1;
}
public lastIndexOf(this: any[], val: any, start = 0) {
start |= 0;
if (start < 0) start = 0;
for (let i = this.length - 1; i >= start; i--) {
if (i in this && this[i] === val) return i;
}
return -1;
}
public includes(this: any[], val: any) {
for (let i = 0; i < this.length; i++) {
if (i in this && this[i] === val) return i;
}
return false;
}
public sort(this: any[], cb?: Function) {
cb ||= (a: any, b: any) => {
if (String(a) < String(b)) return -1;
if (String(a) === String(b)) return 0;
return 1;
};
return object.sort(this, cb);
}
public reverse(this: any[]) {
const mid = this.length >> 1;
const end = this.length - 1;
for (let i = 0; i < mid; i++) {
const tmp = this[i];
this[i] = this[end - i];
this[end - i] = tmp;
}
return this;
}
public [symbols.iterator](this: any[]) {
let i = 0;
let arr: any[] | undefined = func.invoke(Array.prototype.slice, this, []);
return {
next() {
if (arr == null) return { done: true, value: undefined };
if (i >= arr.length) {
arr = undefined;
return { done: true, value: undefined };
}
while (true) {
const res = arr![i];
if (i in arr!) {
i++;
return { done: false, value: res };
}
else i++;
}
},
[symbols.iterator]() { return this; }
};
}
public constructor (len: unknown) {
if (arguments.length === 1 && typeof len === "number") {
const res: any[] = [];
res.length = len;
return res as any;
}
else {
const res: any[] = [];
res.length = arguments.length;
for (let i = 0; i < arguments.length; i++) {
res[i] = arguments[i];
}
return res as any;
}
}
public static isArray(val: any): val is any[] {
return object.isArray(val);
}
public static from(val: any, cb?: Function, self?: any): any[] {
if (symbols.iterator in val) {
const res = [];
const it = val[symbols.iterator]();
if (cb) {
for (let val = it.next(); !val.done; val = it.next()) {
res[res.length] = func.invoke(cb, self, [val.value]);
}
}
else {
for (let val = it.next(); !val.done; val = it.next()) {
res[res.length] = val.value;
}
}
return res;
}
else if ("length" in val) {
const res = [];
if (cb) {
for (let i = 0; i < val.length; i++) {
if (i in val) res[i] = func.invoke(cb, self, [val[i]]);
}
}
else {
for (let i = 0; i < val.length; i++) {
if (i in val) res[i] = val[i];
}
}
return res;
}
else if (val == null) throw new Error("Illegal argument");
else return [];
}
}
func.setCallable(Array, true);
func.setConstructable(Array, true);
return Array as any as typeof Array & ((value?: unknown) => object);
})();
export type Array = InstanceType<typeof Array>;

29
src/lib/libs/boolean.ts Normal file
View File

@@ -0,0 +1,29 @@
import { func } from "./primordials.ts";
import { unwrapThis, valueKey } from "./utils.ts";
export const Boolean = (() => {
class Boolean {
[valueKey]!: boolean;
public toString() {
return "" + unwrapThis(this, "boolean", Boolean, "Boolean.prototype.toString");
}
public valueOf() {
return unwrapThis(this, "boolean", Boolean, "Boolean.prototype.valueOf");
}
public constructor(value?: unknown) {
if (func.invokeType(arguments, this) === "call") {
if (arguments.length === 0) return false as any;
else return !!value as any;
}
this[valueKey] = (Boolean as any)(value);
}
};
func.setCallable(Boolean, true);
func.setConstructable(Boolean, true);
return Boolean as any as typeof Boolean & ((value?: unknown) => symbol);
})();
export type Boolean = InstanceType<typeof Boolean>;

11
src/lib/libs/console.ts Normal file
View File

@@ -0,0 +1,11 @@
import { func, json, object } from "./primordials";
export const console = {};
function method(name: string, func: Function) {
object.defineField(console, name, { c: true, e: false, w: true, v: func });
}
method("log", function log() {
func.invoke(print, null, arguments as any);
});

20
src/lib/libs/date.ts Normal file
View File

@@ -0,0 +1,20 @@
import { now, symbol } from "./primordials.ts";
const timeKey: unique symbol = symbol.makeSymbol("") as any;
export const Date = (() => {
class Date {
[timeKey]!: number;
public constructor() {
}
public static now() {
return now();
}
};
return Date as any as typeof Date & ((val?: unknown) => string);
})();
export type Date = InstanceType<typeof Date>;

39
src/lib/libs/errors.ts Normal file
View File

@@ -0,0 +1,39 @@
import { func, object } from "./primordials.ts";
import { String } from "./string.ts";
export class Error {
public declare name: string;
public declare message: string;
public toString() {
let res = this.name || "Error";
const msg = this.message;
if (msg) res += ": " + msg;
return res;
}
public constructor (msg = "") {
if (func.invokeType(arguments, this) === "call") return new Error(msg);
this.message = String(msg);
}
}
object.defineField(Error.prototype, "name", { c: true, e: false, w: true, v: "Error" });
object.defineField(Error.prototype, "message", { c: true, e: false, w: true, v: "" });
func.setCallable(Error, true);
func.setConstructable(Error, true);
export class SyntaxError extends Error { }
object.defineField(SyntaxError.prototype, "name", { c: true, e: false, w: true, v: "SyntaxError" });
func.setCallable(SyntaxError, true);
func.setConstructable(SyntaxError, true);
export class TypeError extends Error { }
object.defineField(TypeError.prototype, "name", { c: true, e: false, w: true, v: "TypeError" });
func.setCallable(TypeError, true);
func.setConstructable(TypeError, true);
export class RangeError extends Error { }
object.defineField(RangeError.prototype, "name", { c: true, e: false, w: true, v: "RangeError" });
func.setCallable(RangeError, true);
func.setConstructable(RangeError, true);

82
src/lib/libs/function.ts Normal file
View File

@@ -0,0 +1,82 @@
import { compile, func, string } from "./primordials.ts";
import { String } from "./string.ts";
export const Function = (() => {
class Function {
declare public readonly name: string;
declare public readonly length: number;
public toString(this: Function) {
if (this.name !== "") return "function " + this.name + "(...) { ... }";
else return "function (...) { ... }";
}
public valueOf() {
return this;
}
public apply(this: (...args: any) => any, self: any, args: any[]) {
return func.invoke(this, self, args);
}
public call(this: (...args: any) => any, self: any) {
const args: any[] = [];
for (let i = arguments.length - 1; i >= 1; i--) args[i - 1] = arguments[i];
return func.invoke(this, self, args);
}
public bind(this: (...args: any) => any, self: any) {
const cb = this;
if (arguments.length === 0) return function (this: any) { return func.invoke(cb, this, arguments as any) };
if (arguments.length <= 1) return function () { return func.invoke(cb, self, arguments as any); }
const base: any[] = [];
const offset = arguments.length - 1;
base.length = offset;
for (let i = 0; i < offset; i++) base[i] = arguments[i + 1];
return function () {
for (let i = 0; i < arguments.length; i++) {
base[offset + i] = arguments[i];
}
return func.invoke(cb, self, base);
};
}
public constructor () {
const parts = ["(function anonymous("];
for (let i = 0; i < arguments.length - 1; i++) {
if (i > 0) parts[parts.length] = ",";
parts[parts.length] = arguments[i];
}
parts[parts.length] = "){\n";
parts[parts.length] = String(arguments[arguments.length - 1]);
parts[parts.length] = "\n})";
var res = compile(string.stringBuild(parts))();
return res;
}
public static compile(src = "", { globals = [], wrap = false }: { globals?: string[], wrap?: boolean } = {}) {
const parts = [];
if (wrap) parts[parts.length] = "return (function() {\n";
if (globals.length > 0) {
parts[parts.length] = "let {";
for (let i = 0; i < globals.length; i++) {
if (i > 0) parts[parts.length] = ",";
parts[parts.length] = globals[i];
}
parts[parts.length] = "} = arguments[0];";
}
parts[parts.length] = src;
if (wrap) parts[parts.length] = "\n})(arguments[0])";
const res = compile(string.stringBuild(parts));
return res;
}
}
func.setCallable(Function, true);
func.setConstructable(Function, true);
return Function as any as typeof Function & ((value?: unknown) => (...args: any[]) => any);
})();

15
src/lib/libs/json.ts Normal file
View File

@@ -0,0 +1,15 @@
import { json, object } from "./primordials";
export const JSON = {};
function method(name: string, func: Function) {
object.defineField(JSON, name, { c: true, e: false, w: true, v: func });
}
method("parse", function parse(val: string) {
return json.parse(val);
});
method("stringify", function stringify(val: string) {
return json.stringify(val);
});

128
src/lib/libs/map.ts Normal file
View File

@@ -0,0 +1,128 @@
import { Array } from "./array.ts";
import { func, map, symbol } from "./primordials.ts";
import { symbols } from "./utils.ts";
const mapKey: unique symbol = symbol.makeSymbol("Map.impl") as any;
export class Map<K, V> {
private [mapKey]: InstanceType<typeof map>;
public get size() {
return this[mapKey].size();
}
public get(key: K): V {
return this[mapKey].get(key);
}
public has(key: K): boolean {
return this[mapKey].has(key);
}
public set(key: K, val: V) {
this[mapKey].set(key, val);
return this;
}
public delete(key: K): boolean {
if (!this[mapKey].has(key)) return false;
else {
this[mapKey].delete(key);
return true;
}
}
public clear() {
this[mapKey].clear();
}
public keys(): K[] {
return this[mapKey].keys();
}
public values(): V[] {
const res = this[mapKey].keys();
for (let i = 0; i < res.length; i++) {
res[i] = this[mapKey].get(res[i]);
}
return res;
}
public entries(): [K, V][] {
const res = this[mapKey].keys();
for (let i = 0; i < res.length; i++) {
res[i] = [res[i], this[mapKey].get(res[i])];
}
return res;
}
public forEach(cb: Function, self?: any) {
const entries = this.entries();
for (let i = 0; i < entries.length; i++) {
func.invoke(cb, self, [entries[i][1], entries[i][0], this]);
}
}
public [symbols.iterator](): Iterator<[K, V]> {
return func.invoke(Array.prototype[symbols.iterator], this.entries(), []) as any;
}
public constructor(iterable?: Iterable<[K, V]>) {
const _map = this[mapKey] = new map();
if (iterable != null) {
if (Array.isArray(iterable)) {
for (let i = 0; i < iterable.length; i++) {
if (!(i in iterable)) continue;
_map.set(iterable[i][0], iterable[i][1]);
}
}
else {
const it = (iterable as any)[symbols.iterator]();
for (let val = it.next(); !val.done; val = it.next()) {
_map.set(val.value[0], val.value[1]);
}
}
}
}
}
export class WeakMap<K, V> {
private [mapKey]: InstanceType<typeof map>;
public get(key: K): V {
return this[mapKey].get(key);
}
public has(key: K): boolean {
return this[mapKey].has(key);
}
public set(key: K, val: V) {
this[mapKey].set(key, val);
return this;
}
public delete(key: K): boolean {
if (!this[mapKey].has(key)) return false;
else {
this[mapKey].delete(key);
return true;
}
}
public clear() {
this[mapKey].clear();
}
public constructor(iterable?: Iterable<[K, V]>) {
const _map = this[mapKey] = new map(true);
if (iterable != null) {
if (Array.isArray(iterable)) {
for (let i = 0; i < iterable.length; i++) {
if (!(i in iterable)) continue;
_map.set(iterable[i][0], iterable[i][1]);
}
}
else {
const it = (iterable as any)[symbols.iterator]();
for (let val = it.next(); !val.done; val = it.next()) {
_map.set(val.value[0], val.value[1]);
}
}
}
}
}
func.setCallable(Map, false);
func.setCallable(WeakMap, false);

60
src/lib/libs/math.ts Normal file
View File

@@ -0,0 +1,60 @@
import { number, object } from "./primordials";
export const Math = {};
function method(name: string, func: Function) {
object.defineField(Math, name, { c: true, e: false, w: true, v: func });
}
method("max", function max() {
let res = -number.Infinity;
for (let i = 0; i < arguments.length; i++) {
if (res < arguments[i]) res = arguments[i];
}
return res;
});
method("min", function min() {
let res = +number.Infinity;
for (let i = 0; i < arguments.length; i++) {
if (res > arguments[i]) res = arguments[i];
}
return res;
});
method("abs", function abs(val: number) {
val = +val;
if (val < 0) return -val;
else return val;
});
method("floor", function floor(val: number) {
val = val - 0;
if (number.isNaN(val)) return number.NaN;
let rem = val % 1;
if (rem < 0) rem += 1;
return val - rem;
});
method("ceil", function floor(val: number) {
val = val - 0;
if (number.isNaN(val)) return number.NaN;
let rem = val % 1;
if (rem === 0) return val;
if (rem < 0) rem += 1;
return val + (1 - rem);
});
method("pow", function pow(a: number, b: number) {
return number.pow(a, b);
});
method("log", function log(val: number) {
return number.log(val);
});

77
src/lib/libs/number.ts Normal file
View File

@@ -0,0 +1,77 @@
import { func, number, object } from "./primordials.ts";
import { unwrapThis, valueKey } from "./utils.ts";
export const Number = (() => {
class Number {
[valueKey]!: number;
public toString() {
return "" + unwrapThis(this, "number", Number, "Number.prototype.toString");
}
public valueOf() {
return unwrapThis(this, "number", Number, "Number.prototype.toString");
}
public constructor (value?: unknown) {
if (func.invokeType(arguments, this) === "call") {
if (arguments.length === 0) return 0 as any;
else return +(value as any) as any;
}
this[valueKey] = (Number as any)(value);
}
public static isFinite(value: number) {
value = unwrapThis(value, "number", Number, "Number.isFinite", "value");
if (value === undefined || value !== value) return false;
if (value === number.Infinity || value === -number.Infinity) return false;
return true;
}
public static isInteger(value: number) {
value = unwrapThis(value, "number", Number, "Number.isInteger", "value");
if (value === undefined) return false;
return number.parseInt(value) === value;
}
public static isNaN(value: number) {
return number.isNaN(value);
}
public static isSafeInteger(value: number) {
value = unwrapThis(value, "number", Number, "Number.isSafeInteger", "value");
if (value === undefined || number.parseInt(value) !== value) return false;
return value >= -9007199254740991 && value <= 9007199254740991;
}
public static parseFloat(value: unknown) {
if (typeof value === "number") return value;
else return number.parseFloat(value + "");
}
public static parseInt(value: unknown, radix = 10) {
radix = +radix;
if (number.isNaN(radix)) radix = 10;
if (typeof value === "number") return number.parseInt(value, radix);
else return number.parseInt(value + "", radix);
}
declare public static readonly EPSILON: number;
declare public static readonly MIN_SAFE_INTEGER: number;
declare public static readonly MAX_SAFE_INTEGER: number;
declare public static readonly POSITIVE_INFINITY: number;
declare public static readonly NEGATIVE_INFINITY: number;
declare public static readonly NaN: number;
declare public static readonly MAX_VALUE: number;
declare public static readonly MIN_VALUE: number;
}
object.defineField(Number, "EPSILON", { c: false, e: false, w: false, v: 2.220446049250313e-16 });
object.defineField(Number, "MIN_SAFE_INTEGER", { c: false, e: false, w: false, v: -9007199254740991 });
object.defineField(Number, "MAX_SAFE_INTEGER", { c: false, e: false, w: false, v: 9007199254740991 });
object.defineField(Number, "POSITIVE_INFINITY", { c: false, e: false, w: false, v: +number.Infinity });
object.defineField(Number, "NEGATIVE_INFINITY", { c: false, e: false, w: false, v: -number.Infinity });
object.defineField(Number, "NaN", { c: false, e: false, w: false, v: number.NaN });
object.defineField(Number, "MAX_VALUE", { c: false, e: false, w: false, v: 1.7976931348623157e+308 });
object.defineField(Number, "MIN_VALUE", { c: false, e: false, w: false, v: 5e-324 });
func.setCallable(Number, true);
func.setConstructable(Number, true);
return Number as any as typeof Number & ((value?: unknown) => number);
})();
export type Number = InstanceType<typeof Number>;

200
src/lib/libs/object.ts Normal file
View File

@@ -0,0 +1,200 @@
import { Boolean } from "./boolean.ts";
import { TypeError } from "./errors.ts";
import { Number } from "./number.ts";
import { func, object } from "./primordials.ts";
import { String } from "./string.ts";
import { symbols, valueKey } from "./utils.ts";
import { Symbol } from "./symbol.ts";
export const Object = (() => {
class Object {
public toString(this: unknown) {
if (this === undefined) return "[object Undefined]";
else if (this === null) return "[object Null]";
else if (typeof this === "object") {
if (symbols.toStringTag in this) return "[object " + (this as any)[symbols.toStringTag] + "]";
else if (object.isArray(this)) return "[object Array]";
else return "[object Object]";
}
else if (typeof this === "number" || this instanceof Number) return "[object Number]";
else if (typeof this === "symbol" || this instanceof Symbol) return "[object Symbol]";
else if (typeof this === "string" || this instanceof String) return "[object String]";
else if (typeof this === "boolean" || this instanceof Boolean) return "[object Boolean]";
else if (typeof this === "function") return "[object Function]";
}
public valueOf() {
return this;
}
public hasOwnProperty(key: string) {
return object.getOwnMember(this, key) != null;
}
public constructor (value?: unknown) {
if (typeof value === 'object' && value !== null) return value as any;
if (typeof value === 'string') return new String(value) as any;
if (typeof value === 'number') return new Number(value) as any;
if (typeof value === 'boolean') return new Boolean(value) as any;
if (typeof value === 'symbol') {
const res: Symbol = {} as any;
object.setPrototype(res, Symbol.prototype);
res[valueKey] = value;
return res as any;
}
return {} as any;
}
public static getOwnPropertyDescriptor(obj: object, key: any) {
return object.getOwnMember(obj, key);
}
public static getOwnPropertyNames(obj: object): string[] {
return object.getOwnMembers(obj, false);
}
public static getOwnPropertySymbols(obj: object): symbol[] {
return object.getOwnSymbolMembers(obj, false);
}
public static defineProperty(obj: object, key: string | symbol, desc: PropertyDescriptor) {
if (obj === null || typeof obj !== "function" && typeof obj !== "object") {
throw new TypeError("Object.defineProperty called on non-object");
}
if (desc === null || typeof desc !== "function" && typeof desc !== "object") {
throw new TypeError("Property description must be an object: " + desc);
}
const res: any = {};
if ("get" in desc || "set" in desc) {
if ("get" in desc) {
const get = desc.get;
if (get !== undefined && typeof get !== "function") throw new TypeError("Getter must be a function: " + get);
res.g = get;
}
if ("set" in desc) {
const set = desc.set;
if (set !== undefined && typeof set !== "function") throw new TypeError("Setter must be a function: " + set);
res.s = set;
}
if ("enumerable" in desc) res.e = !!desc.enumerable;
if ("configurable" in desc) res.e = !!desc.configurable;
if (!object.defineProperty(obj, key, res)) throw new TypeError("Cannot redefine property: " + String(key));
}
else {
if ("enumerable" in desc) res.e = !!desc.enumerable;
if ("configurable" in desc) res.e = !!desc.configurable;
if ("writable" in desc) res.w = !!desc.writable;
if ("value" in desc) res.v = desc.value;
if (!object.defineField(obj, key, res)) throw new TypeError("Cannot redefine property: " + String(key));
}
return obj;
}
public static defineProperties(obj: object, desc: PropertyDescriptorMap) {
const keys = object.getOwnMembers(desc, true) as ((keyof typeof obj) & string)[];
const symbols = object.getOwnSymbolMembers(desc, true) as ((keyof typeof obj) & symbol)[];
for (let i = 0; i < keys.length; i++) {
Object.defineProperty(obj, keys[i], desc[keys[i]]);
}
for (let i = 0; i < symbols.length; i++) {
Object.defineProperty(obj, symbols[i], desc[symbols[i]]);
}
return obj;
}
public static create(proto: object, desc?: PropertyDescriptorMap) {
let res = object.setPrototype({}, proto);
if (desc != null) this.defineProperties(res, desc);
return res;
}
public static assign(target: any) {
for (let i = 1; i < arguments.length; i++) {
const obj = arguments[i];
const keys = object.getOwnMembers(obj, false);
const symbols = object.getOwnSymbolMembers(obj, false);
for (let j = 0; j < keys.length; j++) {
target[keys[j]] = obj[keys[j]];
}
for (let j = 0; j < symbols.length; j++) {
target[symbols[j]] = obj[symbols[j]];
}
}
return target;
}
public static setPrototypeOf(obj: object, proto: object | null) {
object.setPrototype(obj, proto!);
}
public static getPrototypeOf(obj: object) {
return object.getPrototype(obj) || null;
}
public static keys(obj: any) {
const res: any[] = [];
const keys = object.getOwnMembers(obj, true);
const symbols = object.getOwnSymbolMembers(obj, true);
for (let i = 0; i < keys.length; i++) {
res[res.length] = keys[i];
}
for (let i = 0; i < symbols.length; i++) {
res[res.length] = symbols[i];
}
return res;
}
public static values(obj: any) {
const res: any[] = [];
const keys = object.getOwnMembers(obj, true);
const symbols = object.getOwnSymbolMembers(obj, true);
for (let i = 0; i < keys.length; i++) {
res[res.length] = obj[keys[i]];
}
for (let i = 0; i < symbols.length; i++) {
res[res.length] = obj[symbols[i]];
}
return res;
}
public static entries(obj: any) {
const res: [any, any][] = [];
const keys = object.getOwnMembers(obj, true);
const symbols = object.getOwnSymbolMembers(obj, true);
for (let i = 0; i < keys.length; i++) {
res[res.length] = [keys[i], obj[keys[i]]];
}
for (let i = 0; i < symbols.length; i++) {
res[res.length] = [symbols[i], obj[symbols[i]]];
}
return res;
}
public static preventExtensions(obj: object) {
object.preventExt(obj);
return obj;
}
public static seal(obj: object) {
object.seal(obj);
return obj;
}
public static freeze(obj: object) {
object.freeze(obj);
return obj;
}
}
object.setPrototype(Object.prototype, undefined);
func.setCallable(Object, true);
func.setConstructable(Object, true);
return Object as any as typeof Object & ((value?: unknown) => object);
})();
export type Object = InstanceType<typeof Object>;

View File

@@ -0,0 +1,5 @@
import { func, object } from "../primordials.ts";
export default function _callSuper(self, constr, args) {
return func.construct(object.getPrototype(constr), func.target(1), args || []);
}

View File

@@ -0,0 +1,5 @@
import { func } from "../primordials.ts";
export default function _classCallCheck() {
if (func.invokeTypeInfer() !== "new") throw new TypeError("Cannot call a class as a function");
}

View File

@@ -0,0 +1,35 @@
import { object } from "../primordials.ts";
function _defineProperties(target, arr) {
if (!arr) return;
for (var i = 0; i < arr.length; i++) {
var desc = arr[i];
var res;
var w, e, c;
c = desc.configurable;
if (c == null) c = true;
e = desc.enumerable;
if (e == null) e = false;
if ("value" in desc) {
w = desc.writable;
if (w == null) w = true;
if (desc.writable == null)
res = object.defineField(target, desc.key, { w: !!w, e: !!e, c: !!c, v: desc.value });
}
else {
res = object.defineProperty(target, desc.key, { e: !!e, c: !!c, g: desc.get, s: desc.set });
}
if (!res) throw "Couldn't set property";
}
}
export default function _createClass(clazz, instance, nonInstance) {
_defineProperties(clazz.prototype, instance);
_defineProperties(clazz, nonInstance);
return clazz;
}

View File

@@ -0,0 +1,7 @@
import { object } from "../primordials.ts";
export default function _defineProperty(obj, key, val) {
if (obj == null) return;
object.defineField(obj, key, { c: true, e: true, w: true, v: val });
return obj;
}

View File

@@ -0,0 +1,5 @@
import { object } from "../primordials.ts";
export default function _getPrototypeOf(obj) {
return object.getPrototype(obj) || null;
}

View File

@@ -0,0 +1,11 @@
import { object } from "../primordials.ts";
export default function _inherits(t, e) {
if (e == null) {
object.setPrototype(t.prototype, undefined);
}
else {
object.setPrototype(t.prototype, e.prototype);
object.setPrototype(t, e);
}
}

View File

@@ -0,0 +1,2 @@
export default function _possibleConstructorReturn() {
}

View File

@@ -0,0 +1,3 @@
export default function _readOnlyError(name) {
throw name;
}

View File

@@ -0,0 +1,5 @@
import { object } from "../primordials";
export default function _setPrototypeOf(obj, proto) {
object.setPrototype(obj, proto);
}

View File

@@ -0,0 +1,3 @@
export default function _typeof(val) {
return typeof val;
}

111
src/lib/libs/primordials.ts Normal file
View File

@@ -0,0 +1,111 @@
export interface SymbolPrimordials {
makeSymbol(name: string): symbol;
getSymbol(name: string): symbol;
getSymbolKey(symbol: symbol): string | undefined;
getSymbolDescription(symbol: symbol): string;
}
export interface NumberPrimordials {
NaN: number;
Infinity: number;
PI: number;
E: number;
parseInt(raw: string | number, radix?: number): number;
parseFloat(raw: string | number): number;
isNaN(num: number): boolean;
pow(a: number, b: number): number;
log(val: number): number;
}
export interface StringPrimordials {
stringBuild(parts: string[]): string;
fromCharCode(char: number): string;
fromCodePoint(char: number): string;
toCharCode(char: string): number;
toCodePoint(char: string, i: number): number;
indexOf(str: string, search: string, start: number, reverse?: boolean): number;
substring(str: string, start: number, end: number): string;
lower(str: string): string;
upper(str: string): string;
}
export interface ObjectPrimordials {
defineProperty(obj: object, key: string | number | symbol, conf: { g?: Function, s?: Function, e?: boolean, c?: boolean }): boolean;
defineField(obj: object, key: string | number | symbol, conf: { v?: any, e?: boolean, c?: boolean, w?: boolean }): boolean;
getOwnMember(obj: object, key: any): PropertyDescriptor | undefined;
getOwnMembers(obj: object, onlyEnumerable: boolean): string[];
getOwnSymbolMembers(obj: object, onlyEnumerable: boolean): symbol[];
getPrototype(obj: object): object | undefined;
setPrototype(obj: object, proto?: object): object;
preventExt(obj: object): void;
seal(obj: object): void;
freeze(obj: object): void;
isArray(obj: any): obj is any[];
subarray(arr: any[], start: number, end: number): any[];
memcpy(src: any[], dst: any[], srcI: number, dstI: number, n: number): void;
sort(arr: any[], cb: Function): any[];
}
export interface FunctionPrimordials {
invokeType(args: IArguments, self: any): "new" | "call";
invokeTypeInfer(): "new" | "call";
target(): Function | null | undefined;
setConstructable(func: Function, flag: boolean): void;
setCallable(func: Function, flag: boolean): void;
invoke(func: Function, self: any, args: any[]): any;
construct(func: Function, self: any, args: any[]): any;
}
export interface JSONPrimordials {
parse(data: string): any;
stringify(data: any): string;
}
export interface Primordials {
symbol: SymbolPrimordials;
number: NumberPrimordials;
string: StringPrimordials;
object: ObjectPrimordials;
function: FunctionPrimordials;
json: JSONPrimordials;
map: new (weak?: boolean) => {
get(key: any): any;
has(key: any): boolean;
set(key: any, val: any): void;
delete(key: any): void;
keys(): any[];
clear(): void;
size(): number;
};
regex: new (source: string, multiline?: boolean, noCase?: boolean, dotall?: boolean, unicode?: boolean, unicodeClass?: boolean) => {
exec(target: string, offset: number, indices: boolean): { matches: RegExpMatchArray, end: number } | null;
groupCount(): number;
};
compile(src: string): Function;
setGlobalPrototypes(prototype: Record<string, any>): void;
now(): number;
next(func: () => void): void;
schedule(func: () => void, delay: number): () => void;
}
globalThis.undefined = void 0;
export const target = (globalThis as any).target;
export const primordials: Primordials = (globalThis as any).primordials;
export const {
symbol,
number,
string,
object,
function: func,
json,
map,
regex,
setGlobalPrototypes,
compile,
now,
next,
schedule,
} = primordials;
export type regex = InstanceType<typeof regex>;

154
src/lib/libs/promise.ts Normal file
View File

@@ -0,0 +1,154 @@
import { func, next, object, symbol } from "./primordials.ts";
enum PromiseState {
Pending = "pend",
Fulfilled = "ful",
Rejected = "rej",
}
const pState: unique symbol = symbol.makeSymbol("Promise.state") as any;
const pValue: unique symbol = symbol.makeSymbol("Promise.value") as any;
const pFulHandles: unique symbol = symbol.makeSymbol("Promise.fulfillHandles") as any;
const pRejHandles: unique symbol = symbol.makeSymbol("Promise.rejectHandles") as any;
function makePromise<T>(): Promise<T> {
return object.setPrototype({
[pState]: PromiseState.Pending,
[pFulHandles]: [],
[pRejHandles]: [],
}, Promise.prototype) as Promise<T>;
}
function fulfill(self: Promise<any>, val: any) {
if (self[pState] !== PromiseState.Pending) return;
if (self === val) throw new Error("A promise may not be fulfilled with itself");
if (val != null && typeof val.then === "function") {
val.then(
(val: any) => fulfill(self, val),
(err: any) => reject(self, err),
);
}
else {
self[pValue] = val;
self[pState] = PromiseState.Fulfilled;
const handles = self[pFulHandles]!;
for (let i = 0; i < handles.length; i++) {
handles[i](val);
}
self[pFulHandles] = undefined;
self[pRejHandles] = undefined;
}
}
function reject(self: Promise<any>, val: any) {
if (self[pState] !== PromiseState.Pending) return;
if (self === val) throw new Error("A promise may not be rejected with itself");
if (val != null && typeof val.then === "function") {
val.then(
(val: any) => reject(self, val),
(err: any) => reject(self, err),
);
}
else {
self[pValue] = val;
self[pState] = PromiseState.Rejected;
const handles = self[pRejHandles]!;
for (let i = 0; i < handles.length; i++) {
handles[i](val);
}
self[pFulHandles] = undefined;
self[pRejHandles] = undefined;
}
}
function handle<T>(self: Promise<T>, ful?: (val: T) => void, rej?: (err: any) => void) {
if (self[pState] === PromiseState.Pending) {
if (ful != null) {
self[pFulHandles]![self[pFulHandles]!.length] = ful;
}
if (rej != null) {
self[pRejHandles]![self[pRejHandles]!.length] = rej;
}
}
else if (self[pState] === PromiseState.Fulfilled) {
if (ful != null) ful(self[pValue] as T);
}
else if (self[pState] === PromiseState.Rejected) {
if (rej != null) rej(self[pValue]);
}
}
export class Promise<T> {
public [pState]: PromiseState;
public [pValue]?: T | unknown;
public [pFulHandles]?: ((val: T) => void)[] = [];
public [pRejHandles]?: ((val: T) => void)[] = [];
public then<Res>(ful?: (val: T) => Res, rej?: (err: any) => Res) {
if (typeof ful !== "function") ful = undefined;
if (typeof rej !== "function") rej = undefined;
const promise = makePromise<Res>();
handle(this,
val => next(() => {
if (ful == null) fulfill(promise, val);
else {
try { fulfill(promise, ful(val)); }
catch (e) { reject(promise, e); }
}
}),
err => next(() => {
if (rej == null) reject(promise, err);
else {
try { fulfill(promise, rej(err)); }
catch (e) { reject(promise, e); }
}
}),
);
return promise;
}
public catch<Res>(rej?: (err: any) => Res) {
return this.then(undefined, rej);
}
public finally(fn?: () => void) {
if (typeof fn !== "function") return this["then"]();
return this.then(
v => {
fn();
return v;
},
v => {
fn();
throw v;
},
)
}
public constructor(fn: (fulfil: (val: T) => void, reject: (err: unknown) => void) => void) {
this[pState] = PromiseState.Pending;
fn(val => fulfill(this, val), err => reject(this, err));
}
public static resolve(val: any) {
const res = makePromise();
fulfill(res, val);
return res;
}
public static reject(val: any) {
const res = makePromise();
reject(res, val);
return res;
}
}
func.setCallable(Promise, false);

138
src/lib/libs/regex.ts Normal file
View File

@@ -0,0 +1,138 @@
import { func, regex, symbol } from "./primordials.ts";
import { String } from "./string.ts";
import { type ReplaceRange } from "./utils.ts";
import { applyReplaces } from "./utils.ts";
import { applySplits } from "./utils.ts";
import { symbols } from "./utils.ts";
const regexKey: unique symbol = symbol.makeSymbol("RegExp.impl") as any;
export class RegExp {
private [regexKey]!: InstanceType<typeof regex>;
public readonly source!: string;
public readonly flags!: string;
public lastIndex = 0;
public readonly indices!: boolean;
public readonly global!: boolean;
public readonly ignoreCase!: boolean;
public readonly multiline!: boolean;
public readonly dotall!: boolean;
public readonly unicode!: boolean;
public readonly unicodeSets!: boolean;
public readonly sticky!: boolean;
public constructor(source: any, flags = "") {
if (func.invokeType(arguments, this) === "call") return new RegExp(source, flags);
source = this.source = String(typeof source === "object" && "source" in source ? source.source : source);
flags = String(flags);
let indices = false;
let global = false;
let ignoreCase = false;
let multiline = false;
let dotall = false;
let unicode = false;
let unicodeSets = false;
let sticky = false;
for (let i = 0; i < flags.length; i++) {
switch (flags[i]) {
case "d": indices = true; break;
case "g": global = true; break;
case "i": ignoreCase = true; break;
case "m": multiline = true; break;
case "s": dotall = true; break;
case "u": unicode = true; break;
case "v": unicodeSets = true; break;
case "y": sticky = true; break;
}
}
flags = "";
if (indices) flags += "d";
if (global) flags += "g";
if (ignoreCase) flags += "i";
if (multiline) flags += "m";
if (dotall) flags += "s";
if (unicode) flags += "u";
if (unicodeSets) flags += "v";
if (sticky) flags += "y";
this.flags = flags;
this.indices = indices;
this.global = global;
this.ignoreCase = ignoreCase;
this.multiline = multiline;
this.dotall = dotall;
this.unicode = unicode;
this.unicodeSets = unicodeSets;
this.sticky = sticky;
this[regexKey] = new regex(source, multiline, ignoreCase, dotall, unicode, unicodeSets);
}
public exec(target: string) {
const useLast = this.global || this.sticky;
const start = useLast ? this.lastIndex : 0;
const match = this[regexKey].exec(target, start, this.indices);
if (match != null && !(this.sticky && match.matches.index !== start)) {
if (useLast) this.lastIndex = match.end;
return match.matches;
}
if (useLast) this.lastIndex = 0;
return null;
}
public test(target: string) {
return this.exec(target) != null;
}
public [symbols.split](target: string, limit?: number) {
return applySplits(target, limit, offset => {
const val = this[regexKey].exec(target, offset, false);
if (val == null) return undefined;
return { start: val.matches.index!, end: val.end };
});
}
public [symbols.replace](target: string, replacer: any) {
const matches: ReplaceRange[] = [];
const regex = this[regexKey];
if (this.global) {
let offset = 0;
while (true) {
const match = regex.exec(target, offset, false);
if (match == null) break;
const start = match.matches.index;
const end = match.end;
const arr: string[] = [];
for (let i = 0; i < match.matches.length; i++) {
arr[i] = match.matches[i];
}
matches[matches.length] = { start: match.matches.index!, end: match.end, matches: arr };
if (start === end) offset = start + 1;
else offset = end;
}
return applyReplaces(target, matches, replacer, regex.groupCount() + 1);
}
else {
const match = this.exec(target);
if (match != null) matches[0] = {
start: match.index!,
end: match.index! + match[0].length,
matches: match,
}
}
return applyReplaces(target, matches, replacer, regex.groupCount() + 1);
}
}

121
src/lib/libs/set.ts Normal file
View File

@@ -0,0 +1,121 @@
import { Array } from "./array.ts";
import { func, map, symbol } from "./primordials.ts";
import { symbols } from "./utils.ts";
const mapKey: unique symbol = symbol.makeSymbol("Set.impl") as any;
export class Set<T> {
private [mapKey]: InstanceType<typeof map>;
public get size() {
return this[mapKey].size();
}
public has(key: T): boolean {
return this[mapKey].has(key);
}
public add(val: T) {
this[mapKey].set(val, true);
return this;
}
public delete(val: T): boolean {
if (!this[mapKey].has(val)) return false;
else {
this[mapKey].delete(val);
return true;
}
}
public clear() {
this[mapKey].clear();
}
public keys(): T[] {
return this[mapKey].keys();
}
public values(): T[] {
return this[mapKey].keys();
}
public entries(): [T, T][] {
const res = this[mapKey].keys();
for (let i = 0; i < res.length; i++) {
res[i] = [res[i], res[i]];
}
return res;
}
public forEach(cb: Function, self?: any) {
const vals = this.values();
for (let i = 0; i < vals.length; i++) {
func.invoke(cb, self, [vals[i], vals[i], this]);
}
}
public [symbols.iterator](): Iterator<T> {
return func.invoke(Array.prototype[symbols.iterator], this.values(), []) as any;
}
public constructor(iterable?: Iterable<T>) {
const _map = this[mapKey] = new map();
if (iterable != null) {
if (Array.isArray(iterable)) {
for (let i = 0; i < iterable.length; i++) {
if (!(i in iterable)) continue;
_map.set(iterable[i], true);
}
}
else {
const it = (iterable as any)[symbols.iterator]();
for (let val = it.next(); !val.done; val = it.next()) {
_map.set(val.value, true);
}
}
}
}
}
export class WeakSet<T> {
private [mapKey]: InstanceType<typeof map>;
public has(key: T): boolean {
return this[mapKey].has(key);
}
public add(val: T) {
this[mapKey].set(val, true);
return this;
}
public delete(val: T): boolean {
if (!this[mapKey].has(val)) return false;
else {
this[mapKey].delete(val);
return true;
}
}
public clear() {
this[mapKey].clear();
}
public constructor(iterable?: Iterable<T>) {
const _map = this[mapKey] = new map(true);
if (iterable != null) {
if (Array.isArray(iterable)) {
for (let i = 0; i < iterable.length; i++) {
if (!(i in iterable)) continue;
_map.set(iterable[i], true);
}
}
else {
const it = (iterable as any)[symbols.iterator]();
for (let val = it.next(); !val.done; val = it.next()) {
_map.set(val.value, true);
}
}
}
}
}
func.setCallable(Set, false);
func.setCallable(WeakSet, false);

276
src/lib/libs/string.ts Normal file
View File

@@ -0,0 +1,276 @@
import { TypeError } from "./errors.ts";
import { func, number, regex, string } from "./primordials.ts";
import { RegExp } from "./regex.ts";
import { applyReplaces, applySplits, limitI, type ReplaceRange, symbols, unwrapThis, valueKey, wrapI } from "./utils.ts";
const trimStartRegex = new regex("^\\s+", false, false, false, false, false);
const trimEndRegex = new regex("\\s+$", false, false, false, false, false);
export const String = (() => {
class String {
[valueKey]!: string;
public at(index: number) {
throw "Not implemented :/";
return unwrapThis(this, "string", String, "String.prototype.at")[index];
}
public toString() {
return unwrapThis(this, "string", String, "String.prototype.toString");
}
public valueOf() {
return unwrapThis(this, "string", String, "String.prototype.valueOf");
}
public includes(search: string, offset = 0) {
const self = unwrapThis(this, "string", String, "String.prototype.indexOf");
return string.indexOf(self, (String as any)(search), +offset, false) >= 0;
}
public startsWith(search: string) {
const self = unwrapThis(this, "string", String, "String.prototype.indexOf");
if (self.length < search.length) return false;
return string.substring(self, 0, search.length) === search;
}
public endsWith(search: string) {
const self = unwrapThis(this, "string", String, "String.prototype.indexOf");
if (self.length < search.length) return false;
return string.substring(self, self.length - search.length, self.length) === search;
}
public indexOf(search: string, offset = 0) {
const self = unwrapThis(this, "string", String, "String.prototype.indexOf");
offset = +offset;
return string.indexOf(self, search, offset, false);
}
public lastIndexOf(search: string, offset = 0) {
const self = unwrapThis(this, "string", String, "String.prototype.lastIndexOf");
offset = +offset;
return string.indexOf(self, search, offset, true);
}
public trim() {
const self = unwrapThis(this, "string", String, "String.prototype.trim");
const start = trimStartRegex.exec(self, 0, false);
const end = trimEndRegex.exec(self, 0, false);
const startI = start == null ? 0 : start.end;
const endI = end == null ? self.length : end.matches.index!;
return string.substring(self, startI, endI);
}
public trimStart() {
const self = unwrapThis(this, "string", String, "String.prototype.trim");
const start = trimStartRegex.exec(self, 0, false);
const startI = start == null ? 0 : start.end;
return string.substring(self, startI, self.length);
}
public trimEnd() {
const self = unwrapThis(this, "string", String, "String.prototype.trim");
const end = trimEndRegex.exec(self, 0, false);
const endI = end == null ? self.length : end.matches.index!;
return string.substring(self, 0, endI);
}
public trimLeft() {
return func.invoke(String.prototype.trimStart, this, []);
}
public trimRight() {
return func.invoke(String.prototype.trimEnd, this, []);
}
public charAt(i: number) {
const self = unwrapThis(this, "string", String, "String.prototype.charAt");
return self[i];
}
public charCodeAt(i: number) {
const self = unwrapThis(this, "string", String, "String.prototype.charCodeAt");
return self[i] ? string.toCharCode(self[i]) : number.NaN;
}
public codePointAt(i: number) {
const self = unwrapThis(this, "string", String, "String.prototype.charCodeAt");
return i >= 0 && i < self.length ? string.toCodePoint(self, i) : number.NaN;
}
public split(val?: any, limit?: number) {
const self = unwrapThis(this, "string", String, "String.prototype.split");
if (val === undefined) return [self];
if (val !== null && typeof val === "object" && symbols.split in val) {
return val[symbols.split](self, limit);
}
val = (String as any)(val);
return applySplits(self, limit, offset => {
const start = string.indexOf(self, val, offset, false);
if (start < 0) return undefined;
else return { start, end: start + val.length };
});
}
public replace(val: any, replacer: any) {
const self = unwrapThis(this, "string", String, "String.prototype.replace");
if (val !== null && typeof val === "object" && symbols.replace in val) {
return val[symbols.replace](self, replacer);
}
else val = (String as any)(val);
const i = string.indexOf(self, val, 0);
return applyReplaces(self, [{ start: i, end: i + val.length, matches: [val] }], replacer, false);
}
public replaceAll(val: any, replacer: any) {
const self = unwrapThis(this, "string", String, "String.prototype.replaceAll");
if (val !== null && typeof val === "object" && symbols.replace in val) {
if (val instanceof RegExp && !val.global) throw new TypeError("replaceAll must be called with a global RegExp");
return val[symbols.replace](self, replacer);
}
else val = (String as any)(val);
let offset = 0;
const matches: ReplaceRange[] = [];
const add = val.length === 0 ? 1 : val.length;
while (true) {
const i = string.indexOf(self, val, offset);
if (i < 0) break;
matches[matches.length] = { start: i, end: i + val.length, matches: [val] };
if (val.length === 0)
offset = i + add;
}
return applyReplaces(self, matches, replacer, false);
}
public repeat(n: number) {
const self = unwrapThis(this, "string", String, "String.prototype.replaceAll");
const res: string[] = [];
for (let i = 0; i < n; i++) {
res[i] = self;
}
return string.stringBuild(res);
}
public slice(start = 0, end?: number) {
const self = unwrapThis(this, "string", String, "String.prototype.slice");
if (end === undefined) end = self.length;
start = limitI(wrapI(start, self.length), self.length);
end = limitI(wrapI(end, self.length), self.length);
if (end <= start) return "";
return string.substring(self, start, end);
}
public substring(start = 0, end?: number) {
const self = unwrapThis(this, "string", String, "String.prototype.substring");
if (end === undefined) end = self.length;
start = limitI(start, self.length);
end = limitI(end, self.length);
if (end <= start) return "";
return string.substring(self, start, end);
}
public substr(start = 0, count?: number) {
const self = unwrapThis(this, "string", String, "String.prototype.substr");
count = self.length - start;
start = limitI(start, self.length);
count = limitI(count, self.length - start);
if (count <= 0) return "";
return string.substring(self, start, count + start);
}
public concat() {
const self = unwrapThis(this, "string", String, "String.prototype.concat");
const parts = [self];
for (let i = 0; i < arguments.length; i++) {
parts[i + 1] = (String as any)(arguments[i]);
}
return string.stringBuild(parts);
}
public toLowerCase() {
const self = unwrapThis(this, "string", String, "String.prototype.toLowerCase");
return string.lower(self);
}
public toUpperCase() {
const self = unwrapThis(this, "string", String, "String.prototype.toLowerCase");
return string.upper(self);
}
public match(regex: RegExp) {
const self = unwrapThis(this, "string", String, "String.prototype.match");
if (!(regex instanceof RegExp)) throw new TypeError("Regexp expected for String.prototype.match");
if (regex.global) {
let matches: string[] | null = null;
while (true) {
const match = regex.exec(self);
if (match == null) break;
matches ||= [];
matches[matches.length] = match[0];
}
return matches;
}
else return regex.exec(self);
}
public [symbols.iterator]() {
var i = 0;
var arr: string | undefined = unwrapThis(this, "string", String, "String.prototype[Symbol.iterator]");
return {
next () {
if (arr == null) return { done: true, value: undefined };
if (i > arr.length) {
arr = undefined;
return { done: true, value: undefined };
}
else {
var val = arr[i++];
if (i >= arr.length) arr = undefined;
return { done: false, value: val };
}
},
[symbols.iterator]() { return this; }
};
}
public constructor (value?: unknown) {
if (func.invokeType(arguments, this) === "call") {
if (arguments.length === 0) return "" as any;
else if (typeof value === "symbol") return value.toString() as any;
else return (value as any) + "" as any;
}
this[valueKey] = (String as any)(value);
}
public static fromCharCode() {
const res: string[] = [];
res[arguments.length] = "";
for (let i = 0; i < arguments.length; i++) {
res[i] = string.fromCharCode(+arguments[i]);
}
return string.stringBuild(res);
}
public static fromCodePoint() {
const res: string[] = [];
res[arguments.length] = "";
for (var i = 0; i < arguments.length; i++) {
res[i] = string.fromCodePoint(+arguments[i]);
}
return string.stringBuild(res);
}
}
func.setCallable(String, true);
func.setConstructable(String, true);
return String as any as typeof String & ((value?: unknown) => string);
})();
export type String = InstanceType<typeof String>;

53
src/lib/libs/symbol.ts Normal file
View File

@@ -0,0 +1,53 @@
import { func, object, symbol } from "./primordials.ts";
import { symbols, unwrapThis, valueKey } from "./utils.ts";
export const Symbol = (() => {
class Symbol {
[valueKey]!: symbol;
get description() {
return symbol.getSymbolDescription(unwrapThis(this, "symbol", Symbol, "Symbol.prototype.description"));
}
public toString() {
return "Symbol(" + unwrapThis(this, "symbol", Symbol, "Symbol.prototype.toString").description + ")";
}
public valueOf() {
return unwrapThis(this, "symbol", Symbol, "Symbol.prototype.valueOf");
}
public constructor(name = "") {
return symbol.makeSymbol(name + "") as any;
}
public static for(name: string) {
return symbol.getSymbol(name + "");
}
declare public static readonly asyncIterator: unique symbol;
declare public static readonly iterator: unique symbol;
declare public static readonly match: unique symbol;
declare public static readonly matchAll: unique symbol;
declare public static readonly replace: unique symbol;
declare public static readonly search: unique symbol;
declare public static readonly split: unique symbol;
declare public static readonly toStringTag: unique symbol;
};
func.setCallable(Symbol, true);
func.setConstructable(Symbol, false);
object.defineField(Symbol, "asyncIterator", { c: false, e: false, w: false, v: symbols.asyncIterator });
object.defineField(Symbol, "iterator", { c: false, e: false, w: false, v: symbols.iterator });
object.defineField(Symbol, "match", { c: false, e: false, w: false, v: symbols.match });
object.defineField(Symbol, "matchAll", { c: false, e: false, w: false, v: symbols.matchAll });
object.defineField(Symbol, "replace", { c: false, e: false, w: false, v: symbols.replace });
object.defineField(Symbol, "search", { c: false, e: false, w: false, v: symbols.search });
object.defineField(Symbol, "split", { c: false, e: false, w: false, v: symbols.split });
object.defineField(Symbol, "toStringTag", { c: false, e: false, w: false, v: symbols.toStringTag });
object.defineField(Symbol, "isConcatSpreadable", { c: false, e: false, w: false, v: symbols.isConcatSpreadable });
return Symbol as any as typeof Symbol & ((name?: string) => ReturnType<SymbolConstructor>);
})();
export type Symbol = InstanceType<typeof Symbol>;

26
src/lib/libs/url.ts Normal file
View File

@@ -0,0 +1,26 @@
import { regex, string } from "./primordials";
function escaper(matcher: regex) {
return (text: string) => {
const parts: string[] = [];
let i = 0;
while (true) {
const match = matcher.exec(text, i, false);
if (match == null) break;
const char = match.matches[0];
const code = string.toCharCode(char);
parts[parts.length] = string.substring(text, i, match.matches.index!);
parts[parts.length] = "%" + code;
i = match.end;
}
parts[parts.length] = string.substring(text, i, text.length);
return string.stringBuild(parts);
};
}
export const encodeURI = escaper(new regex("[^A-Za-z0-9\\-+.!~*'()]"));
export const encodeURIComponent = escaper(new regex("[^A-Za-z0-9\\-+.!~*'();/?:@&=+$,#]"));

218
src/lib/libs/utils.ts Normal file
View File

@@ -0,0 +1,218 @@
import { func, string, symbol } from "./primordials.ts";
export const valueKey: unique symbol = symbol.makeSymbol("Primitive.value") as any;
export namespace symbols {
export const asyncIterator: unique symbol = symbol.makeSymbol("Symbol.asyncIterator") as any;
export const iterator: unique symbol = symbol.makeSymbol("Symbol.iterator") as any;
export const match: unique symbol = symbol.makeSymbol("Symbol.match") as any;
export const matchAll: unique symbol = symbol.makeSymbol("Symbol.matchAll") as any;
export const replace: unique symbol = symbol.makeSymbol("Symbol.replace") as any;
export const search: unique symbol = symbol.makeSymbol("Symbol.search") as any;
export const split: unique symbol = symbol.makeSymbol("Symbol.split") as any;
export const toStringTag: unique symbol = symbol.makeSymbol("Symbol.toStringTag") as any;
export const isConcatSpreadable: unique symbol = symbol.makeSymbol("Symbol.isConcatSpreadable") as any;
}
export interface TypeMap {
undefined: undefined;
boolean: boolean;
string: string;
number: number;
symbol: symbol;
object: null | object;
function: Function;
}
export function unwrapThis<T extends keyof TypeMap>(self: any, type: T, constr: Function, name: string, arg = "this", defaultVal?: TypeMap[T]): TypeMap[T] {
if (typeof self === type) return self;
if (self instanceof constr && valueKey in self) self = (self as any)[valueKey];
if (typeof self === type) return self;
if (defaultVal !== undefined) return defaultVal;
throw new TypeError(name + " requires that '" + arg + "' be a " + constr.name);
}
export function wrapI(i: number, length: number) {
if (i < 0) return (i + length) | 0;
else return i | 0;
}
export function limitI(i: number, max: number) {
i |= 0;
if (i < 0) return 0;
else if (i > max) return max;
else return i;
}
export type ReplaceRange = { start: number; end: number; matches: string[]; groups?: Record<string, string>; };
type ReplaceLiteral = (string | ((_: { groups: string[]; prev: () => string; next: () => string; }) => string))[];
function parseReplacer(replacer: string, groupN: number) {
const parts: ReplaceLiteral = [];
let lastI = 0;
let lastSlice = 0;
while (true) {
const i = string.indexOf(replacer, "$", lastI);
if (i < 0 || i + 1 >= replacer.length) break;
lastI = i + 1;
switch (replacer[i + 1]) {
case "$":
parts[parts.length] = string.substring(replacer, lastSlice, i);
parts[parts.length] = "$";
lastSlice = i + 2;
continue;
case "&":
parts[parts.length] = string.substring(replacer, lastSlice, i);
parts[parts.length] = ({ groups }) => groups[0];
lastSlice = i + 2;
continue;
case "`":
parts[parts.length] = string.substring(replacer, lastSlice, i);
parts[parts.length] = ({ prev }) => prev();
lastSlice = i + 2;
continue;
case "'":
parts[parts.length] = string.substring(replacer, lastSlice, i);
parts[parts.length] = ({ next }) => next();
lastSlice = i + 2;
continue;
}
let groupI = 0;
let hasGroup = false;
let consumedN = 1;
while (i + consumedN < replacer.length) {
const code = string.toCharCode(replacer[i + consumedN]);
if (code >= 48 && code <= 57) {
const newGroupI = groupI * 10 + code - 48;
if (newGroupI < 1 || newGroupI >= groupN) break;
groupI = newGroupI;
hasGroup = true;
}
consumedN++;
}
if (hasGroup) {
parts[parts.length] = string.substring(replacer, lastSlice, i);
parts[parts.length] = ({ groups }) => groups[groupI];
lastSlice = i + consumedN;
continue;
}
}
if (lastSlice === 0) return [replacer];
else parts[parts.length] = string.substring(replacer, lastSlice, replacer.length);
return parts;
}
function executeReplacer(text: string, match: ReplaceRange, literal: ReplaceLiteral, prevEnd?: number, nextStart?: number) {
const res = [];
for (let i = 0; i < literal.length; i++) {
const curr = literal[i];
if (typeof curr === "function") res[i] = curr({
groups: match.matches,
next: () => string.substring(text, prevEnd ?? 0, match.start),
prev: () => string.substring(text, match.end, nextStart ?? 0),
});
else res[i] = curr;
}
return string.stringBuild(res);
}
export function applyReplaces(text: string, ranges: ReplaceRange[], replace: any, groupN?: number | false) {
if (ranges.length === 0) return text;
const res: string[] = [];
let offset = 0;
if (groupN !== false && typeof replace === "string") {
if (groupN == null) {
for (let i = 0; i < ranges.length; i++) {
const prevEnd = i - 1 >= 0 ? ranges[i - 1].end : undefined;
const nextStart = i + 1 < ranges.length ? ranges[i + 1].start : undefined;
const range = ranges[i];
res[res.length] = string.substring(text, offset, range.start);
res[res.length] = executeReplacer(text, range, parseReplacer(replace, range.matches.length), prevEnd, nextStart);
offset = range.end;
}
res[res.length] = string.substring(text, offset, text.length);
}
else {
const literal = parseReplacer(replace, groupN);
for (let i = 0; i < ranges.length; i++) {
const prevEnd = i - 1 >= 0 ? ranges[i - 1].end : undefined;
const nextStart = i + 1 < ranges.length ? ranges[i + 1].start : undefined;
const range = ranges[i];
res[res.length] = string.substring(text, offset, range.start);
res[res.length] = executeReplacer(text, range, literal, prevEnd, nextStart);
offset = range.end;
}
res[res.length] = string.substring(text, offset, text.length);
}
return string.stringBuild(res);
}
if (typeof replace === "string") {
for (let i = 0; i < ranges.length; i++) {
const range = ranges[i];
res[res.length] = string.substring(text, offset, range.start);
res[res.length] = replace;
offset = range.end;
}
res[res.length] = string.substring(text, offset, text.length);
}
else {
for (let i = 0; i < ranges.length; i++) {
const range = ranges[i];
const args: any[] = range.matches;
args[args.length] = range.start;
args[args.length] = text;
args[args.length] = range.groups;
res[res.length] = string.substring(text, offset, range.start);
res[res.length] = func.invoke(replace, undefined, args);
offset = range.end;
}
res[res.length] = string.substring(text, offset, text.length);
}
return string.stringBuild(res);
}
export function applySplits(text: string, limit: number | undefined, next: (offset: number) => { start: number; end: number; } | undefined) {
let lastEnd = 0;
let lastEmpty = true;
let offset = 0;
const res: string[] = [];
while (true) {
if (limit != null && limit >= 0 && res.length >= limit) break;
const curr = next(offset);
if (curr == null) {
if (!lastEmpty || !res.length) res[res.length] = string.substring(text, lastEnd, text.length);
break;
}
const { start, end } = curr;
const empty = start === end;
if (offset > 0 || !empty) res[res.length] = string.substring(text, lastEnd, start);
lastEnd = end;
offset = empty ? end + 1 : end;
lastEmpty = empty;
}
return res;
}

View File

@@ -0,0 +1,7 @@
// import coffeescript from "./coffeescript.ts";
// import babel from "./babel.ts";
// register(v => coffeescript(babel(v)));
import typescript from "./typescript.ts";
register(typescript);

View File

@@ -0,0 +1,25 @@
import { SourceMap } from "./map.ts";
import { transform } from "@babel/standalone";
// import presetEnv from "@babel/preset-env";
export default function babel(next: Compiler): Compiler {
print("Loaded babel!");
return (filename, code, prevMap) => {
const res = transform(code, {
filename,
sourceMaps: true,
});
const map = SourceMap.parse({
file: "babel-internal://" + filename,
mappings: res.map!.mappings,
sources: [filename],
});
const compiled = next("babel-internal://" + filename, res.code!, SourceMap.chain(map, prevMap));
registerSource(filename, code);
return compiled;
};
}

View File

@@ -0,0 +1,28 @@
import { compile } from "coffeescript";
import { SourceMap } from "./map.ts";
export default function coffee(next: Compiler): Compiler {
print("Loaded coffeescript!");
return (filename, code, prevMap) => {
const {
js: result,
v3SourceMap: rawMap,
} = compile(code, {
filename,
sourceMap: true,
bare: true,
});
const map = SourceMap.parse({
file: "coffee-internal://" + filename,
mappings: JSON.parse(rawMap).mappings,
sources: [filename],
});
const compiled = next("coffee-internal://" + filename, result, SourceMap.chain(map, prevMap));
registerSource(filename, code);
return compiled;
};
}

182
src/lib/transpiler/map.ts Normal file
View File

@@ -0,0 +1,182 @@
const map: number[] = [];
let j = 0;
for (let i = 65; i <= 90; i++) map[i] = j++;
for (let i = 97; i <= 122; i++) map[i] = j++;
map[43] = j++;
map[47] = j++;
export type Location = readonly [file: string, line: number, start: number];
export function decodeVLQ(val: string): number[][][] {
const lines: number[][][] = [];
for (const line of val.split(";", -1)) {
const elements: number[][] = [];
for (const el of line.split(",", -1)) {
if (el.length === 0) elements.push([]);
else {
const list: number[] = [];
for (let i = 0; i < el.length;) {
let sign = 1;
let curr = map[el.charCodeAt(i++)];
let cont = (curr & 0x20) === 0x20;
if ((curr & 1) === 1) sign = -1;
let res = (curr & 0b11110) >> 1;
let n = 4;
for (; i < el.length && cont;) {
curr = map[el.charCodeAt(i++)];
cont = (curr & 0x20) == 0x20;
res |= (curr & 0b11111) << n;
n += 5;
if (!cont) break;
}
list.push(res * sign);
}
elements.push(list);
}
}
lines.push(elements);
}
return lines;
}
export namespace Location {
export function compare(a: Location, b: Location) {
const { 0: filenameA, 1: lineA, 2: startA } = a;
const { 0: filenameB, 1: lineB, 2: startB } = b;
if (filenameA < filenameB) return -1;
if (filenameA > filenameB) return 1;
const lineI = lineA - lineB;
if (lineI !== 0) return lineI;
return startA - startB;
}
export function comparePoints(a: Location, b: Location) {
const { 1: lineA, 2: startA } = a;
const { 1: lineB, 2: startB } = b;
const lineI = lineA - lineB;
if (lineI !== 0) return lineI;
return startA - startB;
}
}
export interface SourceMap {
(loc: Location): Location | undefined;
}
export class VLQSourceMap {
public constructor(
public readonly array: Map<string, [start: number, dst: Location][][]>,
) { }
public converter() {
return (src: Location) => {
const file = this.array.get(src[0]);
if (file == null) return src;
const line = file[src[1]];
if (line == null || line.length === 0) return undefined;
let a = 0;
let b = line.length;
while (true) {
const done = b - a <= 1;
const mid = (a + b) >> 1;
const el = line[mid];
const cmp = el[0] - src[1];
if (cmp < 0) {
if (done) {
if (b >= line.length) return undefined;
break;
}
a = mid;
}
else if (cmp > 0) {
if (done) {
if (a <= 0) return undefined;
break;
}
b = mid;
}
else return el[1];
}
return line[b][1];
};
}
public static parseVLQ(compiled: string, filenames: string[], raw: string): VLQSourceMap {
const mapping = decodeVLQ(raw);
const file: [start: number, dst: Location][][] = [];
const res = new Map<string, [start: number, dst: Location][][]>([[compiled, file]]);
let originalRow = 0;
let originalCol = 0;
let originalFile = 0;
const lastCols = new Set<number>();
for (let compiledRow = 0; compiledRow < mapping.length; compiledRow++) {
const line = file[compiledRow] ??= [];
let compiledCol = 0;
for (const rawSeg of mapping[compiledRow]) {
compiledCol += rawSeg.length > 0 ? rawSeg[0] : 0;
originalFile += rawSeg.length > 1 ? rawSeg[1] : 0;
originalRow += rawSeg.length > 2 ? rawSeg[2] : 0;
originalCol += rawSeg.length > 3 ? rawSeg[3] : 0;
if (!lastCols.has(compiledCol)) {
line[line.length] = [compiledCol, [filenames[originalFile], originalRow, originalCol]];
}
lastCols.add(compiledCol);
}
line.sort((a, b) => a[0] - b[0]);
}
return new VLQSourceMap(res);
}
public static parse(raw: string | { file: string, mappings: string, sources: string[] }) {
if (typeof raw === "string") raw = JSON.parse(raw) as { file: string, mappings: string, sources: string[] };
const compiled = raw.file;
const mapping = raw.mappings;
let filenames = raw.sources;
if (filenames.length === 0 || filenames.length === 1 && filenames[0] === "") filenames = [compiled];
return this.parseVLQ(compiled, filenames, mapping);
}
}
export namespace SourceMap {
export function parse(raw: string | { file: string, mappings: string, sources: string[] }) {
return VLQSourceMap.parse(raw).converter();
}
export function chain(...maps: SourceMap[]): SourceMap {
return loc => {
for (const el of maps) {
const tmp = el(loc);
if (tmp == null) return undefined;
else loc = tmp;
}
return loc;
};
}
}

View File

@@ -0,0 +1,10 @@
import { type SourceMap } from "./map.ts";
declare global {
type CompilerFactory = (next: Compiler) => Compiler;
type Compiler = (filename: string, src: string, mapper: SourceMap) => Function;
function print(...args: any[]): void;
function register(factory: CompilerFactory): void;
function registerSource(filename: string, src: string): void;
}

View File

@@ -0,0 +1,127 @@
import { createDocumentRegistry, createLanguageService, ModuleKind, ScriptSnapshot, ScriptTarget, type Diagnostic, type CompilerOptions, type IScriptSnapshot, flattenDiagnosticMessageText, CompilerHost, LanguageService } from "typescript";
import { SourceMap } from "./map.ts";
declare function getResource(name: string): string | undefined;
declare function print(...args: any[]): void;
declare function register(factory: CompilerFactory): void;
declare function registerSource(filename: string, src: string): void;
type CompilerFactory = (next: Compiler) => Compiler;
type Compiler = (filename: string, src: string, mapper: SourceMap) => Function;
const resources: Record<string, string | undefined> = {};
function resource(name: string) {
if (name in resources) return resources[name];
else return resources[name] = getResource(name);
}
export default function typescript(next: Compiler): Compiler {
const files: Record<string, IScriptSnapshot> = {};
const versions: Record<string, number> = {};
let declI = 0;
const settings: CompilerOptions = {
target: ScriptTarget.ES5,
module: ModuleKind.Preserve,
allowImportingTsExtensions: true,
verbatimModuleSyntax: true,
strict: false,
skipLibCheck: true,
forceConsistentCasingInFileNames: true,
declaration: true,
sourceMap: true,
downlevelIteration: true,
};
let service: LanguageService;
measure(() => {
service = createLanguageService({
getCurrentDirectory: () => "/",
getDefaultLibFileName: () => "/lib.d.ts",
getScriptFileNames: () => {
const res = ["/src.ts", "/lib.d.ts"];
for (let i = 0; i < declI; i++) res.push("/src." + i + ".d.ts");
return res;
},
getCompilationSettings: () => settings,
log: print,
fileExists: filename => filename in files || resource(filename) != null,
getScriptSnapshot: (filename) => {
if (filename in files) return files[filename];
else {
const src = resource(filename);
if (src == null) return undefined;
return files[filename] = ScriptSnapshot.fromString(src);
}
},
getScriptVersion: (filename) => String(versions[filename] || 0),
readFile: () => { throw "no"; },
writeFile: () => { throw "no"; },
}, createDocumentRegistry());
});
measure(() => {
service.getEmitOutput("/lib.d.ts");
});
print("Loaded typescript!");
return (filename, code, prevMap) => {
files["/src.ts"] = ScriptSnapshot.fromString(code);
versions["/src.ts"] ??= 0;
versions["/src.ts"]++;
const emit = service.getEmitOutput("/src.ts");
const diagnostics = new Array<Diagnostic>()
.concat(service.getSyntacticDiagnostics("/src.ts"))
.concat(service.getSemanticDiagnostics("/src.ts"))
.map(diagnostic => {
const message = flattenDiagnosticMessageText(diagnostic.messageText, "\n");
if (diagnostic.file != null) {
let file = diagnostic.file.fileName.substring(1);
if (file === "src.ts") file = filename;
if (diagnostic.start == null) return file;
const pos = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
return file + ":" + (pos.line + 1) + ":" + (pos.character + 1) + ": " + message;
}
else return message;
});
if (diagnostics.length > 0) {
throw new SyntaxError(diagnostics.join("\n"));
}
const outputs: Record<string, string> = {};
for (const el of emit.outputFiles) {
outputs[el.name] = el.text;
}
const rawMap = JSON.parse(outputs["/src.js.map"]);
const map = SourceMap.parse({
file: "ts-internal://" + filename,
mappings: rawMap.mappings,
sources: [filename],
});
const result = outputs["/src.js"];
const declaration = outputs["/src.d.ts"];
const compiled = next("ts-internal://" + filename, result, SourceMap.chain(map, prevMap));
registerSource(filename, code);
return function (this: any) {
const res = compiled.apply(this, arguments);
if (declaration !== '') files["/src." + declI++ + ".d.ts"] = ScriptSnapshot.fromString(declaration);
return res;
};
};
}

View File

@@ -1,58 +0,0 @@
package me.topchetoeu.jscript.common;
public class Buffer {
private byte[] data;
private int length;
public void write(int i, byte[] val) {
if (i + val.length > data.length) {
var newCap = i + val.length + 1;
if (newCap < data.length * 2) newCap = data.length * 2;
var tmp = new byte[newCap];
System.arraycopy(this.data, 0, tmp, 0, length);
this.data = tmp;
}
System.arraycopy(val, 0, data, i, val.length);
if (i + val.length > length) length = i + val.length;
}
public int read(int i, byte[] buff) {
int n = buff.length;
if (i + n > length) n = length - i;
System.arraycopy(data, i, buff, 0, n);
return n;
}
public void clear() {
data = new byte[128];
length = 0;
}
public void append(byte b) {
write(length, new byte[] { b });
}
public byte[] data() {
var res = new byte[length];
System.arraycopy(this.data, 0, res, 0, length);
return res;
}
public int length() {
return length;
}
public Buffer(byte[] data) {
this.data = new byte[data.length];
this.length = data.length;
System.arraycopy(data, 0, this.data, 0, data.length);
}
public Buffer(int capacity) {
this.data = new byte[capacity];
this.length = 0;
}
public Buffer() {
this.data = new byte[128];
this.length = 0;
}
}

View File

@@ -1,16 +1,16 @@
package me.topchetoeu.jscript.common; package me.topchetoeu.jscript.common;
public class FunctionBody { public class FunctionBody {
public final FunctionBody[] children; public final FunctionBody[] children;
public final Instruction[] instructions; public final Instruction[] instructions;
public final int localsN, capturablesN, capturesN, length; public final int localsN, capturablesN, capturesN, length;
public FunctionBody(int localsN, int capturablesN, int capturesN, int length, Instruction[] instructions, FunctionBody[] children) { public FunctionBody(int localsN, int capturablesN, int capturesN, int length, Instruction[] instructions, FunctionBody[] children) {
this.children = children; this.children = children;
this.length = length; this.length = length;
this.localsN = localsN; this.localsN = localsN;
this.capturablesN = capturablesN; this.capturablesN = capturablesN;
this.capturesN = capturesN; this.capturesN = capturesN;
this.instructions = instructions; this.instructions = instructions;
} }
} }

View File

@@ -7,499 +7,370 @@ import java.util.function.IntSupplier;
import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.common.parsing.Location;
public class Instruction { public class Instruction {
public static enum Type { public static enum Type {
RETURN(0x00), RETURN(0x00),
NOP(0x01), NOP(0x01),
THROW(0x02), THROW(0x02),
THROW_SYNTAX(0x03), THROW_SYNTAX(0x03),
DELETE(0x04), DELETE(0x04),
TRY_START(0x05), TRY_START(0x05),
TRY_END(0x06), TRY_END(0x06),
CALL(0x10), CALL(0x10),
@Deprecated CALL_NEW(0x12),
CALL_MEMBER(0x11), JMP_IF(0x18),
CALL_NEW(0x12), JMP_IFN(0x19),
CALL_SUPER(0x13), JMP(0x1A),
JMP_IF(0x18),
JMP_IFN(0x19),
JMP(0x1A),
PUSH_UNDEFINED(0x20), PUSH_UNDEFINED(0x20),
PUSH_NULL(0x21), PUSH_NULL(0x21),
PUSH_BOOL(0x22), PUSH_BOOL(0x22),
PUSH_NUMBER(0x23), PUSH_NUMBER(0x23),
PUSH_STRING(0x24), PUSH_STRING(0x24),
DUP(0x25), DUP(0x25),
DISCARD(0x26), DISCARD(0x26),
LOAD_FUNC(0x30), LOAD_FUNC(0x30),
LOAD_ARR(0x31), LOAD_ARR(0x31),
LOAD_OBJ(0x32), LOAD_OBJ(0x32),
LOAD_REGEX(0x33), LOAD_REGEX(0x33),
LOAD_GLOB(0x38), LOAD_GLOB(0x38),
LOAD_INTRINSICS(0x39), LOAD_INTRINSICS(0x39),
LOAD_ARGS(0x3A), LOAD_ARG(0x3A),
LOAD_REST_ARGS(0x3B), LOAD_ARGS_N(0x3B),
LOAD_CALLEE(0x3C), LOAD_ARGS(0x3C),
LOAD_THIS(0x3D), LOAD_CALLED(0x3D),
LOAD_ERROR(0x3E), LOAD_THIS(0x3E),
LOAD_ERROR(0x3F),
LOAD_VAR(0x40), LOAD_VAR(0x40),
LOAD_MEMBER(0x41), LOAD_MEMBER(0x41),
LOAD_MEMBER_INT(0x42), LOAD_MEMBER_INT(0x42),
LOAD_MEMBER_STR(0x43), LOAD_MEMBER_STR(0x43),
STORE_VAR(0x48), STORE_VAR(0x48),
STORE_MEMBER(0x49), STORE_MEMBER(0x49),
STORE_MEMBER_INT(0x4A), STORE_MEMBER_INT(0x4A),
STORE_MEMBER_STR(0x4B), STORE_MEMBER_STR(0x4B),
DEF_PROP(0x50), DEF_PROP(0x50),
DEF_FIELD(0x51), DEF_FIELD(0x51),
KEYS(0x52), KEYS(0x52),
TYPEOF(0x53), TYPEOF(0x53),
OPERATION(0x54), OPERATION(0x54),
EXTEND(0x55),
GLOB_GET(0x60), GLOB_GET(0x60),
GLOB_SET(0x61), GLOB_SET(0x61),
GLOB_DEF(0x62), GLOB_DEF(0x62);
// CAP_INIT(0x70), private static final HashMap<Integer, Type> types = new HashMap<>();
VAR_INIT(0x71), public final int numeric;
CAP_FREE(0x72),
VAR_FREE(0x73);
private static final HashMap<Integer, Type> types = new HashMap<>(); static {
public final int numeric; for (var val : Type.values()) types.put(val.numeric, val);
}
static { private Type(int numeric) {
for (var val : Type.values()) types.put(val.numeric, val); this.numeric = numeric;
} }
private Type(int numeric) { public static Type fromNumeric(int i) {
this.numeric = numeric; return types.get(i);
} }
}
public static enum BreakpointType {
/**
* A debugger should never stop at such instruction, unless a breakpoint has been set on it
*/
NONE,
/**
* Debuggers should pause at instructions marked with this breakpoint type
* after any step command
*/
STEP_OVER,
/**
* Debuggers should pause at instructions marked with this breakpoint type
* only after a step-in command
*/
STEP_IN;
public static Type fromNumeric(int i) { public boolean shouldStepIn() {
return types.get(i); return this != NONE;
} }
} public boolean shouldStepOver() {
public static enum BreakpointType { return this == STEP_OVER;
NONE, }
STEP_OVER, }
STEP_IN;
public boolean shouldStepIn() { public final Type type;
return this != NONE; public final Object[] params;
}
public boolean shouldStepOver() {
return this == STEP_OVER;
}
}
public final Type type; @SuppressWarnings("unchecked")
public final Object[] params; public <T> T get(int i) {
if (i >= params.length || i < 0) return null;
return (T)params[i];
}
@SuppressWarnings("unchecked") private Instruction(Type type, Object ...params) {
public <T> T get(int i) { this.type = type;
if (i >= params.length || i < 0) return null; this.params = params;
return (T)params[i]; }
}
@SuppressWarnings("unchecked")
public <T> T get(int i, T defaultVal) {
if (i >= params.length || i < 0) return defaultVal;
return (T)params[i];
}
public boolean match(Object ...args) {
if (args.length != params.length) return false;
for (int i = 0; i < args.length; i++) {
var a = params[i];
var b = args[i];
if (a == null || b == null) {
if (!(a == null && b == null)) return false;
}
if (!a.equals(b)) return false;
}
return true;
}
public boolean is(int i, Object arg) {
if (params.length <= i) return false;
return params[i].equals(arg);
}
// public void write(DataOutputStream writer) throws IOException { /**
// var rawType = type.numeric; * Signals the start of a protected context
* @param catchStart The point to witch to jump if an error has been caught
* @param finallyStart The point to witch to jump after either the try or catch bodies have exited
* @param end The point to which to jump after exiting the whole protected context
*/
public static Instruction tryStart(int catchStart, int finallyStart, int end) {
return new Instruction(Type.TRY_START, catchStart, finallyStart, end);
}
/**
* Signifies that the current protected section (try, catch or finally) has ended
*/
public static Instruction tryEnd() {
return new Instruction(Type.TRY_END);
}
/**
* Throws the top stack value
*/
public static Instruction throwInstr() {
return new Instruction(Type.THROW);
}
/**
* Converts the given exception to a runtime syntax error and throws it
*/
public static Instruction throwSyntax(SyntaxException err) {
return new Instruction(Type.THROW_SYNTAX, err.getMessage());
}
/**
* Converts the given exception to a runtime syntax error and throws it
*/
public static Instruction throwSyntax(String err) {
return new Instruction(Type.THROW_SYNTAX, err);
}
/**
* Converts the given exception to a runtime syntax error and throws it
*/
public static Instruction throwSyntax(Location loc, String err) {
return new Instruction(Type.THROW_SYNTAX, new SyntaxException(loc, err).getMessage());
}
/**
* Performs a JS object property deletion.
* Operands:
* 1. Object to manipulate
* 2. Key to delete
*/
public static Instruction delete() {
return new Instruction(Type.DELETE);
}
/**
* Returns the top stack value
*/
public static Instruction ret() {
return new Instruction(Type.RETURN);
}
/**
* A special NOP instruction telling any debugger to pause
*/
public static Instruction debug() {
return new Instruction(Type.NOP, "debug");
}
// switch (type) { /**
// case KEYS: * Does nothing. May be used for metadata or implementation-specific instructions that don't alter the behavior
// case PUSH_BOOL: */
// case STORE_MEMBER: public static Instruction nop(Object ...params) {
// case GLOB_SET: return new Instruction(Type.NOP, params);
// rawType |= (boolean)get(0) ? 128 : 0; break; }
// case TYPEOF: rawType |= params.length > 0 ? 128 : 0; break;
// default:
// }
// writer.writeByte(rawType); public static Instruction call(int argn, boolean hasSelf) {
return new Instruction(Type.CALL, argn, hasSelf);
}
public static Instruction callNew(int argn) {
return new Instruction(Type.CALL_NEW, argn);
}
// switch (type) { public static Instruction jmp(int offset) {
// case CALL: return new Instruction(Type.JMP, offset);
// case CALL_NEW: }
// case CALL_MEMBER: public static Instruction jmpIf(int offset) {
// writer.writeInt(get(0)); return new Instruction(Type.JMP_IF, offset);
// writer.writeUTF(get(1)); }
// break; public static Instruction jmpIfNot(int offset) {
// case DUP: writer.writeInt(get(0)); break; return new Instruction(Type.JMP_IFN, offset);
// case JMP: writer.writeInt(get(0)); break; }
// case JMP_IF: writer.writeInt(get(0)); break;
// case JMP_IFN: writer.writeInt(get(0)); break;
// case LOAD_ARR: writer.writeInt(get(0)); break;
// case LOAD_FUNC: {
// writer.writeInt(params.length - 1);
// for (var i = 0; i < params.length; i++) { public static IntFunction<Instruction> jmp(IntSupplier pos) {
// writer.writeInt(get(i + 1)); return i -> new Instruction(Type.JMP, pos.getAsInt() - i);
// } }
public static IntFunction<Instruction> jmpIf(IntSupplier pos) {
return i -> new Instruction(Type.JMP_IF, pos.getAsInt() - i);
}
public static IntFunction<Instruction> jmpIfNot(IntSupplier pos) {
return i -> new Instruction(Type.JMP_IFN, pos.getAsInt() - i);
}
// writer.writeInt(get(0)); public static Instruction pushUndefined() {
// writer.writeUTF(get(0)); return new Instruction(Type.PUSH_UNDEFINED);
// break; }
// } public static Instruction pushNull() {
// case LOAD_REGEX: writer.writeUTF(get(0)); break; return new Instruction(Type.PUSH_NULL);
// case LOAD_VAR: writer.writeInt(get(0)); break; }
// case GLOB_DEF: writer.writeUTF(get(0)); break; public static Instruction pushValue(boolean val) {
// case GLOB_GET: writer.writeUTF(get(0)); break; return new Instruction(Type.PUSH_BOOL, val);
// case GLOB_SET: }
// writer.writeUTF(get(0)); public static Instruction pushValue(double val) {
// break; return new Instruction(Type.PUSH_NUMBER, val);
// case OPERATION: writer.writeByte(((Operation)get(0)).numeric); break; }
// case PUSH_NUMBER: writer.writeDouble(get(0)); break; public static Instruction pushValue(String val) {
// case PUSH_STRING: writer.writeUTF(get(0)); break; return new Instruction(Type.PUSH_STRING, val);
// case STORE_VAR: writer.writeInt(get(0)); break; }
// case THROW_SYNTAX: writer.writeUTF(get(0));
// case TRY_START:
// writer.writeInt(get(0));
// writer.writeInt(get(1));
// writer.writeInt(get(2));
// break;
// case TYPEOF:
// if (params.length > 0) writer.writeUTF(get(0));
// break;
// default:
// }
// }
private Instruction(Type type, Object ...params) { public static Instruction globDef(String name) {
this.type = type; return new Instruction(Type.GLOB_DEF, name);
this.params = params; }
}
public static Instruction globGet(String name, boolean force) {
return new Instruction(Type.GLOB_GET, name, force);
}
public static Instruction globSet(String name, boolean keep, boolean define) {
return new Instruction(Type.GLOB_SET, name, keep, define);
}
// public static Instruction read(DataInputStream stream) throws IOException { public static Instruction loadVar(int i) {
// var rawType = stream.readUnsignedByte(); return new Instruction(Type.LOAD_VAR, i);
// var type = Type.fromNumeric(rawType & 127); }
// var flag = (rawType & 128) != 0; public static Instruction loadThis() {
return new Instruction(Type.LOAD_THIS);
}
/**
* Loads the given argument
* @param i The index of the argument to load. If -1, will get the index from the stack instead
*/
public static Instruction loadArg(int i) {
return new Instruction(Type.LOAD_ARG, i);
}
/**
* Pushes the amount of arguments to the stack
*/
public static Instruction loadArgsN() {
return new Instruction(Type.LOAD_ARGS_N);
}
/**
* Pushes the arguments object to the stack
*/
public static Instruction loadArgs() {
return new Instruction(Type.LOAD_ARGS);
}
/**
* Loads a reference to the function being called
*/
public static Instruction loadCalled() {
return new Instruction(Type.LOAD_CALLED);
}
public static Instruction loadGlob() {
return new Instruction(Type.LOAD_GLOB);
}
public static Instruction loadIntrinsics(String key) {
return new Instruction(Type.LOAD_INTRINSICS, key);
}
public static Instruction loadError() {
return new Instruction(Type.LOAD_ERROR);
}
public static Instruction loadMember() {
return new Instruction(Type.LOAD_MEMBER);
}
public static Instruction loadMember(int member) {
return new Instruction(Type.LOAD_MEMBER_INT, member);
}
public static Instruction loadMember(String member) {
return new Instruction(Type.LOAD_MEMBER_STR, member);
}
// switch (type) { public static Instruction loadRegex(String pattern, String flags) {
// case CALL: return call(stream.readInt(), stream.readUTF()); return new Instruction(Type.LOAD_REGEX, pattern, flags);
// case CALL_NEW: return callNew(stream.readInt(), stream.readUTF()); }
// case CALL_MEMBER: return callNew(stream.readInt(), stream.readUTF()); // TODO: make this capturing a concern of the compiler
// case DEF_PROP: return defProp(); public static Instruction loadFunc(int id, String name, int[] captures) {
// case DELETE: return delete(); var args = new Object[2 + captures.length];
// case DISCARD: return discard(); args[0] = id;
// case DUP: return dup(stream.readInt()); args[1] = name;
// case JMP: return jmp(stream.readInt()); for (var i = 0; i < captures.length; i++) args[i + 2] = captures[i];
// case JMP_IF: return jmpIf(stream.readInt()); return new Instruction(Type.LOAD_FUNC, args);
// case JMP_IFN: return jmpIfNot(stream.readInt()); }
// case KEYS: return keys(flag); public static Instruction loadObj() {
// case LOAD_ARR: return loadArr(stream.readInt()); return new Instruction(Type.LOAD_OBJ);
// case LOAD_FUNC: { }
// var captures = new int[stream.readInt()]; public static Instruction loadArr(int count) {
return new Instruction(Type.LOAD_ARR, count);
}
public static Instruction dup() {
return new Instruction(Type.DUP, 1, 0);
}
public static Instruction dup(int count, int offset) {
return new Instruction(Type.DUP, count, offset);
}
// for (var i = 0; i < captures.length; i++) { public static Instruction storeVar(int i, boolean keep, boolean initialize) {
// captures[i] = stream.readInt(); return new Instruction(Type.STORE_VAR, i, keep, initialize);
// } }
// return loadFunc(stream.readInt(), stream.readUTF(), captures); public static Instruction storeMember() {
// } return new Instruction(Type.STORE_MEMBER, false);
// case LOAD_GLOB: return loadGlob(); }
// case LOAD_MEMBER: return loadMember(); public static Instruction storeMember(boolean keep) {
// case LOAD_OBJ: return loadObj(); return new Instruction(Type.STORE_MEMBER, keep);
// case LOAD_REGEX: return loadRegex(stream.readUTF(), null); }
// case LOAD_VAR: return loadVar(stream.readInt());
// case GLOB_DEF: return globDef(stream.readUTF());
// case GLOB_GET: return globGet(stream.readUTF());
// case GLOB_SET: return globSet(stream.readUTF(), flag);
// case OPERATION: return operation(Operation.fromNumeric(stream.readUnsignedByte()));
// case PUSH_NULL: return pushNull();
// case PUSH_UNDEFINED: return pushUndefined();
// case PUSH_BOOL: return pushValue(flag);
// case PUSH_NUMBER: return pushValue(stream.readDouble());
// case PUSH_STRING: return pushValue(stream.readUTF());
// case RETURN: return ret();
// case STORE_MEMBER: return storeMember(flag);
// case STORE_VAR: return storeVar(stream.readInt(), flag);
// case THROW: return throwInstr();
// case THROW_SYNTAX: return throwSyntax(stream.readUTF());
// case TRY_END: return tryEnd();
// case TRY_START: return tryStart(stream.readInt(), stream.readInt(), stream.readInt(), stream.readInt());
// case TYPEOF: return flag ? typeof(stream.readUTF()) : typeof();
// case NOP:
// if (flag) return null;
// else return nop();
// default: return null;
// }
// }
public static Instruction tryStart(int catchStart, int finallyStart, int end) { public static Instruction storeMember(String key) {
return new Instruction(Type.TRY_START, catchStart, finallyStart, end); return new Instruction(Type.STORE_MEMBER_STR, key, false);
} }
public static Instruction tryEnd() { public static Instruction storeMember(String key, boolean keep) {
return new Instruction(Type.TRY_END); return new Instruction(Type.STORE_MEMBER_STR, key, keep);
} }
public static Instruction throwInstr() {
return new Instruction(Type.THROW);
}
public static Instruction throwSyntax(SyntaxException err) {
return new Instruction(Type.THROW_SYNTAX, err.getMessage());
}
public static Instruction throwSyntax(String err) {
return new Instruction(Type.THROW_SYNTAX, err);
}
public static Instruction throwSyntax(Location loc, String err) {
return new Instruction(Type.THROW_SYNTAX, new SyntaxException(loc, err).getMessage());
}
public static Instruction delete() {
return new Instruction(Type.DELETE);
}
public static Instruction ret() {
return new Instruction(Type.RETURN);
}
public static Instruction debug() {
return new Instruction(Type.NOP, "debug");
}
public static Instruction nop(Object ...params) { public static Instruction storeMember(int key) {
return new Instruction(Type.NOP, params); return new Instruction(Type.STORE_MEMBER_INT, key, false);
} }
public static Instruction storeMember(int key, boolean keep) {
return new Instruction(Type.STORE_MEMBER_INT, key, keep);
}
public static Instruction call(int argn, boolean hasSelf, String name) { public static Instruction discard() {
return new Instruction(Type.CALL, argn, hasSelf, name); return new Instruction(Type.DISCARD);
} }
public static Instruction call(int argn, boolean hasSelf) {
return call(argn, hasSelf, "");
}
@Deprecated
public static Instruction callMember(int argn, String name) {
return new Instruction(Type.CALL_MEMBER, argn, name);
}
@Deprecated
public static Instruction callMember(int argn) {
return new Instruction(Type.CALL_MEMBER, argn, "");
}
public static Instruction callNew(int argn, String name) {
return new Instruction(Type.CALL_NEW, argn, name);
}
public static Instruction callNew(int argn) {
return new Instruction(Type.CALL_NEW, argn, "");
}
public static Instruction callSuper(int argn) {
return new Instruction(Type.CALL_SUPER, argn);
}
public static Instruction jmp(int offset) { public static Instruction typeof() {
return new Instruction(Type.JMP, offset); return new Instruction(Type.TYPEOF);
} }
public static Instruction jmpIf(int offset) { public static Instruction typeof(String varName) {
return new Instruction(Type.JMP_IF, offset); return new Instruction(Type.TYPEOF, varName);
} }
public static Instruction jmpIfNot(int offset) {
return new Instruction(Type.JMP_IFN, offset);
}
public static IntFunction<Instruction> jmp(IntSupplier pos) { public static Instruction keys(boolean own, boolean onlyEnumerable) {
return i -> new Instruction(Type.JMP, pos.getAsInt() - i); return new Instruction(Type.KEYS, own, onlyEnumerable);
} }
public static IntFunction<Instruction> jmpIf(IntSupplier pos) {
return i -> new Instruction(Type.JMP_IF, pos.getAsInt() - i);
}
public static IntFunction<Instruction> jmpIfNot(IntSupplier pos) {
return i -> new Instruction(Type.JMP_IFN, pos.getAsInt() - i);
}
public static Instruction pushUndefined() { public static Instruction defProp(boolean setter) {
return new Instruction(Type.PUSH_UNDEFINED); return new Instruction(Type.DEF_PROP, setter);
} }
public static Instruction pushNull() { public static Instruction defField() {
return new Instruction(Type.PUSH_NULL); return new Instruction(Type.DEF_FIELD);
} }
public static Instruction pushValue(boolean val) {
return new Instruction(Type.PUSH_BOOL, val);
}
public static Instruction pushValue(double val) {
return new Instruction(Type.PUSH_NUMBER, val);
}
public static Instruction pushValue(String val) {
return new Instruction(Type.PUSH_STRING, val);
}
public static Instruction globDef(String name) { public static Instruction operation(Operation op) {
return new Instruction(Type.GLOB_DEF, name); return new Instruction(Type.OPERATION, op);
} }
public static Instruction globGet(String name, boolean force) {
return new Instruction(Type.GLOB_GET, name, force);
}
public static Instruction globSet(String name, boolean keep, boolean define) {
return new Instruction(Type.GLOB_SET, name, keep, define);
}
public static Instruction loadVar(int i) { @Override public String toString() {
return new Instruction(Type.LOAD_VAR, i); var res = type.toString();
}
public static Instruction loadThis() {
return new Instruction(Type.LOAD_THIS);
}
public static Instruction loadArgs(boolean real) {
return new Instruction(Type.LOAD_ARGS, real);
}
public static Instruction loadRestArgs(int offset) {
return new Instruction(Type.LOAD_REST_ARGS, offset);
}
public static Instruction loadCallee() {
return new Instruction(Type.LOAD_CALLEE);
}
public static Instruction loadGlob() {
return new Instruction(Type.LOAD_GLOB);
}
public static Instruction loadIntrinsics(String key) {
return new Instruction(Type.LOAD_INTRINSICS, key);
}
public static Instruction loadError() {
return new Instruction(Type.LOAD_ERROR);
}
public static Instruction loadMember() {
return new Instruction(Type.LOAD_MEMBER);
}
public static Instruction loadMember(int member) {
return new Instruction(Type.LOAD_MEMBER_INT, member);
}
public static Instruction loadMember(String member) {
return new Instruction(Type.LOAD_MEMBER_STR, member);
}
public static Instruction loadRegex(String pattern, String flags) { for (int i = 0; i < params.length; i++) {
return new Instruction(Type.LOAD_REGEX, pattern, flags); res += " " + params[i];
} }
// TODO: make this capturing a concern of the compiler
public static Instruction loadFunc(int id, boolean callable, boolean constructible, boolean captureThis, boolean noThis, String name, int[] captures) {
if (name == null) name = "";
var args = new Object[6 + captures.length]; return res;
args[0] = id; }
args[1] = name;
args[2] = callable;
args[3] = constructible;
args[4] = captureThis;
args[5] = noThis;
for (var i = 0; i < captures.length; i++) args[i + 6] = captures[i];
return new Instruction(Type.LOAD_FUNC, args);
}
public static Instruction loadObj() {
return new Instruction(Type.LOAD_OBJ);
}
public static Instruction loadArr(int count) {
return new Instruction(Type.LOAD_ARR, count);
}
public static Instruction dup() {
return new Instruction(Type.DUP, 1, 0);
}
public static Instruction dup(int count, int offset) {
return new Instruction(Type.DUP, count, offset);
}
// public static Instruction storeVar(int i) {
// return new Instruction(Type.STORE_VAR, i, false);
// }
public static Instruction storeVar(int i, boolean keep, boolean initialize) {
return new Instruction(Type.STORE_VAR, i, keep, initialize);
}
public static Instruction storeMember() {
return new Instruction(Type.STORE_MEMBER, false);
}
public static Instruction storeMember(boolean keep) {
return new Instruction(Type.STORE_MEMBER, keep);
}
public static Instruction storeMember(String key) {
return new Instruction(Type.STORE_MEMBER_STR, key, false);
}
public static Instruction storeMember(String key, boolean keep) {
return new Instruction(Type.STORE_MEMBER_STR, key, keep);
}
public static Instruction storeMember(int key) {
return new Instruction(Type.STORE_MEMBER_INT, key, false);
}
public static Instruction storeMember(int key, boolean keep) {
return new Instruction(Type.STORE_MEMBER_INT, key, keep);
}
public static Instruction discard() {
return new Instruction(Type.DISCARD);
}
public static Instruction typeof() {
return new Instruction(Type.TYPEOF);
}
public static Instruction typeof(String varName) {
return new Instruction(Type.TYPEOF, varName);
}
public static Instruction keys(boolean own, boolean onlyEnumerable) {
return new Instruction(Type.KEYS, own, onlyEnumerable);
}
public static Instruction defProp(boolean setter, boolean enumerable) {
return new Instruction(Type.DEF_PROP, setter, enumerable);
}
public static Instruction defField(boolean enumerable) {
return new Instruction(Type.DEF_FIELD, enumerable);
}
public static Instruction extend() {
return new Instruction(Type.EXTEND);
}
public static Instruction operation(Operation op) {
return new Instruction(Type.OPERATION, op);
}
public static Instruction capFree(int i) {
return new Instruction(Type.CAP_FREE, i);
}
public static Instruction varFree(int i) {
return new Instruction(Type.VAR_FREE, i);
}
public static Instruction varInit(int i, boolean force) {
return new Instruction(Type.VAR_INIT, i, force);
}
// public static Instruction stackAlloc(int start, int n) {
// return new Instruction(Type.STACK_ALLOC, start, start + n);
// }
// public static Instruction stackRealloc(int start, int n) {
// return new Instruction(Type.STACK_REALLOC, start, start + n);
// }
@Override public String toString() {
var res = type.toString();
for (int i = 0; i < params.length; i++) {
res += " " + params[i];
}
return res;
}
} }

View File

@@ -3,27 +3,27 @@ package me.topchetoeu.jscript.common;
import me.topchetoeu.jscript.common.json.JSON; import me.topchetoeu.jscript.common.json.JSON;
public class Metadata { public class Metadata {
private static final String VERSION; private static final String VERSION;
private static final String AUTHOR; private static final String AUTHOR;
private static final String NAME; private static final String NAME;
static { static {
var data = JSON.parse(null, Reading.resourceToString("metadata.json")).map(); var data = JSON.parse(null, Reading.resourceToString("metadata.json")).map();
VERSION = data.string("version"); VERSION = data.string("version");
AUTHOR = data.string("author"); AUTHOR = data.string("author");
NAME = data.string("name"); NAME = data.string("name");
} }
public static String version() { public static String version() {
if (VERSION.equals("$" + "{VERSION}")) return "1337-devel"; if (VERSION.equals("$" + "{VERSION}")) return "1337-devel";
else return VERSION; else return VERSION;
} }
public static String author() { public static String author() {
if (AUTHOR.equals("$" + "{AUTHOR}")) return "anonymous"; if (AUTHOR.equals("$" + "{AUTHOR}")) return "anonymous";
else return AUTHOR; else return AUTHOR;
} }
public static String name() { public static String name() {
if (NAME.equals("$" + "{NAME}")) return "some-product"; if (NAME.equals("$" + "{NAME}")) return "some-product";
else return NAME; else return NAME;
} }
} }

View File

@@ -3,52 +3,52 @@ package me.topchetoeu.jscript.common;
import java.util.HashMap; import java.util.HashMap;
public enum Operation { public enum Operation {
INSTANCEOF(1, 2), INSTANCEOF(1, 2),
IN(2, 2), IN(2, 2),
MULTIPLY(3, 2), MULTIPLY(3, 2),
DIVIDE(4, 2), DIVIDE(4, 2),
MODULO(5, 2), MODULO(5, 2),
ADD(6, 2), ADD(6, 2),
SUBTRACT(7, 2), SUBTRACT(7, 2),
USHIFT_RIGHT(8, 2), USHIFT_RIGHT(8, 2),
SHIFT_RIGHT(9, 2), SHIFT_RIGHT(9, 2),
SHIFT_LEFT(10, 2), SHIFT_LEFT(10, 2),
GREATER(11, 2), GREATER(11, 2),
LESS(12, 2), LESS(12, 2),
GREATER_EQUALS(13, 2), GREATER_EQUALS(13, 2),
LESS_EQUALS(14, 2), LESS_EQUALS(14, 2),
LOOSE_EQUALS(15, 2), LOOSE_EQUALS(15, 2),
LOOSE_NOT_EQUALS(16, 2), LOOSE_NOT_EQUALS(16, 2),
EQUALS(17, 2), EQUALS(17, 2),
NOT_EQUALS(18, 2), NOT_EQUALS(18, 2),
AND(19, 2), AND(19, 2),
OR(20, 2), OR(20, 2),
XOR(21, 2), XOR(21, 2),
NEG(23, 1), NEG(23, 1),
POS(24, 1), POS(24, 1),
NOT(25, 1), NOT(25, 1),
INVERSE(26, 1); INVERSE(26, 1);
private static final HashMap<Integer, Operation> operations = new HashMap<>(); private static final HashMap<Integer, Operation> operations = new HashMap<>();
static { static {
for (var val : Operation.values()) operations.put(val.numeric, val); for (var val : Operation.values()) operations.put(val.numeric, val);
} }
public final int numeric; public final int numeric;
public final int operands; public final int operands;
private Operation(int numeric, int n) { private Operation(int numeric, int n) {
this.numeric = numeric; this.numeric = numeric;
this.operands = n; this.operands = n;
} }
public static Operation fromNumeric(int i) { public static Operation fromNumeric(int i) {
return operations.get(i); return operations.get(i);
} }
} }

View File

@@ -10,76 +10,83 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
public class Reading { public class Reading {
private static final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); private static final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
public static synchronized String readline() throws IOException { public static synchronized String readline() throws IOException {
return reader.readLine(); return reader.readLine();
} }
public static byte[] streamToBytes(InputStream in) { public static byte[] streamToBytes(InputStream in) {
try { if (in == null) return null;
List<byte[]> bufs = null;
byte[] result = null;
int total = 0;
int n;
do { try {
var buf = new byte[8192]; List<byte[]> bufs = null;
int nread = 0; byte[] result = null;
int total = 0;
int n;
// read to EOF which may read more or less than buffer size do {
while ((n = in.read(buf, nread, buf.length - nread)) > 0) { var buf = new byte[8192];
nread += n; int nread = 0;
}
if (nread > 0) { // read to EOF which may read more or less than buffer size
if (Integer.MAX_VALUE - 8 - total < nread) throw new OutOfMemoryError("Required array size too large"); while ((n = in.read(buf, nread, buf.length - nread)) > 0) {
if (nread < buf.length) buf = Arrays.copyOfRange(buf, 0, nread); nread += n;
total += nread; }
if (result == null) result = buf; if (nread > 0) {
else { if (Integer.MAX_VALUE - 8 - total < nread) throw new OutOfMemoryError("Required array size too large");
if (bufs == null) { if (nread < buf.length) buf = Arrays.copyOfRange(buf, 0, nread);
bufs = new ArrayList<>(); total += nread;
bufs.add(result);
}
bufs.add(buf); if (result == null) result = buf;
} else {
} if (bufs == null) {
// if the last call to read returned -1 or the number of bytes bufs = new ArrayList<>();
// requested have been read then break bufs.add(result);
} while (n >= 0); }
if (bufs == null) { bufs.add(buf);
if (result == null) return new byte[0]; }
return result.length == total ? result : Arrays.copyOf(result, total); }
} // if the last call to read returned -1 or the number of bytes
// requested have been read then break
} while (n >= 0);
result = new byte[total]; if (bufs == null) {
if (result == null) return new byte[0];
return result.length == total ? result : Arrays.copyOf(result, total);
}
int offset = 0; result = new byte[total];
int remaining = total;
for (byte[] b : bufs) { int offset = 0;
int count = Math.min(b.length, remaining); int remaining = total;
System.arraycopy(b, 0, result, offset, count);
offset += count;
remaining -= count;
}
return result; for (byte[] b : bufs) {
} int count = Math.min(b.length, remaining);
catch (IOException e) { throw new UncheckedIOException(e); } System.arraycopy(b, 0, result, offset, count);
} offset += count;
public static String streamToString(InputStream in) { remaining -= count;
return new String(streamToBytes(in)); }
}
return result;
public static InputStream resourceToStream(String name) { }
return Reading.class.getResourceAsStream("/" + name); catch (IOException e) { throw new UncheckedIOException(e); }
} }
public static String resourceToString(String name) { public static String streamToString(InputStream in) {
return streamToString(resourceToStream(name)); var bytes = streamToBytes(in);
} if (bytes == null) return null;
else return new String(bytes);
}
public static InputStream resourceToStream(String name) {
return Reading.class.getResourceAsStream("/" + name);
}
public static String resourceToString(String name) {
return streamToString(resourceToStream(name));
}
public static byte[] resourceToBytes(String name) {
return streamToBytes(resourceToStream(name));
}
} }

View File

@@ -3,12 +3,12 @@ package me.topchetoeu.jscript.common;
import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.common.parsing.Location;
public class SyntaxException extends RuntimeException { public class SyntaxException extends RuntimeException {
public final Location loc; public final Location loc;
public final String msg; public final String msg;
public SyntaxException(Location loc, String msg) { public SyntaxException(Location loc, String msg) {
super(String.format("Syntax error %s: %s", loc, msg)); super(String.format("Syntax error %s: %s", loc, msg));
this.loc = loc; this.loc = loc;
this.msg = msg; this.msg = msg;
} }
} }

View File

@@ -3,185 +3,95 @@ package me.topchetoeu.jscript.common.environment;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Random;
import java.util.Set; import java.util.Set;
import java.util.function.Supplier; import java.util.function.Supplier;
public class Environment { public class Environment {
public final Environment parent; public final Environment parent;
private final Map<Key<Object>, Object> map = new HashMap<>(); private final Map<Key<Object>, Object> map = new HashMap<>();
private final Set<Key<Object>> hidden = new HashSet<>(); private final Set<Key<Object>> hidden = new HashSet<>();
private final Map<MultiKey<Object>, Set<Object>> multi = new HashMap<>(); @SuppressWarnings("unchecked")
private final Map<MultiKey<Object>, Set<Object>> multiHidden = new HashMap<>(); public <T> T get(Key<T> key) {
if (map.containsKey(key)) return (T)map.get(key);
else if (!hidden.contains(key) && parent != null) return parent.get(key);
else return null;
}
public boolean has(Key<?> key) {
if (map.containsKey(key)) return true;
else if (!hidden.contains(key) && parent != null) return parent.has(key);
else return false;
}
@SuppressWarnings("unchecked") public boolean hasNotNull(Key<?> key) {
private <T> Set<T> getAll(MultiKey<T> key, boolean forceClone) { return get(key) != null;
Set<T> parent = null, child = null; }
boolean cloned = false;
if (this.parent != null && !hidden.contains(key)) { public <T> T get(Key<T> key, T defaultVal) {
parent = this.parent.getAll(key, false); if (has(key)) return get(key);
if (parent.size() == 0) parent = null; else return defaultVal;
else if (multiHidden.containsKey(key)) { }
parent = new HashSet<>(parent); public <T> T getWith(Key<T> key, Supplier<T> defaultVal) {
parent.removeAll(multiHidden.get(key)); if (has(key)) return get(key);
cloned = true; else return defaultVal.get();
} }
}
if (multi.containsKey(key)) {
child = (Set<T>)multi.get(key);
if (child.size() == 0) child = null;
}
if (!forceClone) { @SuppressWarnings("unchecked")
if (parent == null && child == null) return new HashSet<>(); public <T> Environment add(Key<T> key, T val) {
if (parent == null && child != null) return child; map.put((Key<Object>)key, val);
if (parent != null && child == null) return parent; hidden.remove(key);
} return this;
}
public Environment add(Key<Void> key) {
return add(key, null);
}
@SuppressWarnings("all")
public Environment addAll(Map<Key<?>, ?> map, boolean iterableAsMulti) {
map.putAll((Map)map);
hidden.removeAll(map.keySet());
return this;
}
public Environment addAll(Map<Key<?>, ?> map) {
return addAll(map, true);
}
if (!cloned) parent = new HashSet<>(); @SuppressWarnings("unchecked")
parent.addAll(child); public Environment remove(Key<?> key) {
return parent; map.remove(key);
} hidden.add((Key<Object>)key);
private <T> T getMulti(MultiKey<T> key) { return this;
return key.of(getAll(key, false)); }
}
private boolean hasMulti(MultiKey<?> key) {
return getAll(key, false).size() > 0;
}
@SuppressWarnings("all") public <T> T init(Key<T> key, T val) {
private <T> Environment addMulti(MultiKey<T> key, T value) { if (!has(key)) this.add(key, val);
if (!multi.containsKey(key)) { return val;
if (hidden.contains(key)) { }
multiHidden.put((MultiKey)key, (Set)parent.getAll(key, true)); public <T> T initFrom(Key<T> key, Supplier<T> val) {
hidden.remove(key); if (!has(key)) {
} var res = val.get();
this.add(key, res);
return res;
}
else return get(key);
}
multi.put((MultiKey)key, new HashSet<>()); public Environment child() {
} return new Environment(this);
}
multi.get(key).add(value); public Environment(Environment parent) {
return this; this.parent = parent;
} }
public Environment() {
this.parent = null;
}
@SuppressWarnings("unchecked") public static Environment wrap(Environment env) {
public <T> T get(Key<T> key) { if (env == null) return empty();
if (key instanceof MultiKey) return getMulti((MultiKey<T>)key); else return env;
}
if (map.containsKey(key)) return (T)map.get(key); public static Environment empty() {
else if (!hidden.contains(key) && parent != null) return parent.get(key); return new Environment();
else return null; }
}
public boolean has(Key<?> key) {
if (key instanceof MultiKey) return hasMulti((MultiKey<?>)key);
if (map.containsKey(key)) return true;
else if (!hidden.contains(key) && parent != null) return parent.has(key);
else return false;
}
public boolean hasNotNull(Key<?> key) {
return get(key) != null;
}
public <T> T get(Key<T> key, T defaultVal) {
if (has(key)) return get(key);
else return defaultVal;
}
public <T> T getWith(Key<T> key, Supplier<T> defaultVal) {
if (has(key)) return get(key);
else return defaultVal.get();
}
@SuppressWarnings("unchecked")
public <T> Environment add(Key<T> key, T val) {
if (key instanceof MultiKey) return add(key, val);
map.put((Key<Object>)key, val);
hidden.remove(key);
return this;
}
public Environment add(Key<Void> key) {
return add(key, null);
}
@SuppressWarnings("all")
public Environment addAll(Map<Key<?>, ?> map, boolean iterableAsMulti) {
for (var pair : map.entrySet()) {
if (iterableAsMulti && pair.getKey() instanceof MultiKey && pair.getValue() instanceof Iterable) {
for (var val : (Iterable<?>)pair.getValue()) {
addMulti((MultiKey<Object>)pair.getKey(), val);
}
}
else add((Key<Object>)pair.getKey(), pair.getValue());
}
map.putAll((Map)map);
hidden.removeAll(map.keySet());
return this;
}
public Environment addAll(Map<Key<?>, ?> map) {
return addAll(map, true);
}
@SuppressWarnings("unchecked")
public Environment remove(Key<?> key) {
map.remove(key);
multi.remove(key);
multiHidden.remove(key);
hidden.add((Key<Object>)key);
return this;
}
@SuppressWarnings("all")
public <T> Environment remove(MultiKey<T> key, T val) {
if (multi.containsKey(key)) {
multi.get(key).remove(val);
multiHidden.get(key).add(val);
if (multi.get(key).size() == 0) {
multi.remove(key);
multiHidden.remove(key);
hidden.add((Key)key);
}
}
return this;
}
public <T> T init(Key<T> key, T val) {
if (!has(key)) this.add(key, val);
return val;
}
public <T> T initFrom(Key<T> key, Supplier<T> val) {
if (!has(key)) {
var res = val.get();
this.add(key, res);
return res;
}
else return get(key);
}
public Environment child() {
return new Environment(this);
}
public Environment(Environment parent) {
this.parent = parent;
}
public Environment() {
this.parent = null;
}
public static Environment wrap(Environment env) {
if (env == null) return empty();
else return env;
}
public static Environment empty() {
return new Environment();
}
public static int nextId() {
return new Random().nextInt();
}
} }

View File

@@ -1,7 +1,3 @@
package me.topchetoeu.jscript.common.environment; package me.topchetoeu.jscript.common.environment;
public interface Key<T> { public final class Key<T> { }
public static <T> Key<T> of() {
return new Key<>() { };
}
}

View File

@@ -1,7 +0,0 @@
package me.topchetoeu.jscript.common.environment;
import java.util.Set;
public interface MultiKey<T> extends Key<T> {
public T of(Set<T> values);
}

View File

@@ -6,167 +6,168 @@ import java.util.stream.Collectors;
import me.topchetoeu.jscript.common.SyntaxException; import me.topchetoeu.jscript.common.SyntaxException;
import me.topchetoeu.jscript.common.parsing.Filename; import me.topchetoeu.jscript.common.parsing.Filename;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.common.parsing.Source;
public class JSON { public class JSON {
public static ParseRes<JSONElement> parseString(Source src, int i) { public static ParseRes<JSONElement> parseString(Source src, int i) {
var res = Parsing.parseString(src, i); var res = Parsing.parseString(src, i);
if (!res.isSuccess()) return res.chainError(); if (!res.isSuccess()) return res.chainError();
return ParseRes.res(JSONElement.string(res.result), res.n); return ParseRes.res(JSONElement.string(res.result), res.n);
} }
public static ParseRes<JSONElement> parseNumber(Source src, int i) { public static ParseRes<JSONElement> parseNumber(Source src, int i) {
var res = Parsing.parseNumber(src, i, true); var res = Parsing.parseNumber(src, i, true);
if (!res.isSuccess()) return res.chainError(); if (!res.isSuccess()) return res.chainError();
else return ParseRes.res(JSONElement.number(res.result), res.n); else return ParseRes.res(JSONElement.number(res.result), res.n);
} }
public static ParseRes<JSONElement> parseLiteral(Source src, int i) { public static ParseRes<JSONElement> parseLiteral(Source src, int i) {
var id = Parsing.parseIdentifier(src, i); var id = Parsing.parseIdentifier(src, i);
if (!id.isSuccess()) return ParseRes.failed(); if (!id.isSuccess()) return ParseRes.failed();
else if (id.result.equals("true")) return ParseRes.res(JSONElement.bool(true), id.n); else if (id.result.equals("true")) return ParseRes.res(JSONElement.bool(true), id.n);
else if (id.result.equals("false")) return ParseRes.res(JSONElement.bool(false), id.n); else if (id.result.equals("false")) return ParseRes.res(JSONElement.bool(false), id.n);
else if (id.result.equals("null")) return ParseRes.res(JSONElement.NULL, id.n); else if (id.result.equals("null")) return ParseRes.res(JSONElement.NULL, id.n);
else return ParseRes.failed(); else return ParseRes.failed();
} }
public static ParseRes<JSONElement> parseValue(Source src, int i) { public static ParseRes<JSONElement> parseValue(Source src, int i) {
return ParseRes.first(src, i, return ParseRes.first(src, i,
JSON::parseString, JSON::parseString,
JSON::parseNumber, JSON::parseNumber,
JSON::parseLiteral, JSON::parseLiteral,
JSON::parseMap, JSON::parseMap,
JSON::parseList JSON::parseList
); );
} }
public static ParseRes<JSONMap> parseMap(Source src, int i) { public static ParseRes<JSONElement> parseMap(Source src, int i) {
var n = Parsing.skipEmpty(src, i); var n = Parsing.skipEmpty(src, i);
if (!src.is(i + n, "{")) return ParseRes.failed(); if (!src.is(i + n, "{")) return ParseRes.failed();
n++; n++;
var values = new JSONMap(); var values = new JSONMap();
if (src.is(i + n, "}")) return ParseRes.res(new JSONMap(new HashMap<>()), n + 1); if (src.is(i + n, "}")) return ParseRes.res(JSONElement.map(new JSONMap(new HashMap<>())), n + 1);
while (true) { while (true) {
var name = parseString(src, i + n); var name = parseString(src, i + n);
if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected an index"); if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected an index");
n += name.n; n += name.n;
n += Parsing.skipEmpty(src, i + n); n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, ":")) return name.chainError(src.loc(i + n), "Expected a colon"); if (!src.is(i + n, ":")) return name.chainError(src.loc(i + n), "Expected a colon");
n++; n++;
var res = parseValue(src, i + n); var res = parseValue(src, i + n);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a list element"); if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a list element");
values.put(name.result.toString(), res.result); values.put(name.result.toString(), res.result);
n += res.n; n += res.n;
n += Parsing.skipEmpty(src, i + n); n += Parsing.skipEmpty(src, i + n);
if (src.is(i + n, ",")) n++; if (src.is(i + n, ",")) n++;
else if (src.is(i + n, "}")) { else if (src.is(i + n, "}")) {
n++; n++;
break; break;
} }
} }
return ParseRes.res(values, n); return ParseRes.res(JSONElement.map(values), n);
} }
public static ParseRes<JSONList> parseList(Source src, int i) { public static ParseRes<JSONElement> parseList(Source src, int i) {
var n = Parsing.skipEmpty(src, i); var n = Parsing.skipEmpty(src, i);
if (!src.is(i + n++, "[]")) return ParseRes.failed(); if (!src.is(i + n++, "[")) return ParseRes.failed();
var values = new JSONList(); var values = new JSONList();
if (src.is(i + n, "]")) return ParseRes.res(new JSONList(), n + 1); if (src.is(i + n, "]")) return ParseRes.res(JSONElement.list(new JSONList()), n + 1);
while (true) { while (true) {
var res = parseValue(src, i + n); var res = parseValue(src, i + n);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a list element"); if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a list element");
values.add(res.result); values.add(res.result);
n += res.n; n += res.n;
n += Parsing.skipEmpty(src, i + n); n += Parsing.skipEmpty(src, i + n);
if (src.is(i + n, ",")) n++; if (src.is(i + n, ",")) n++;
else if (src.is(i + n, "]")) { else if (src.is(i + n, "]")) {
n++; n++;
break; break;
} }
} }
return ParseRes.res(values, n); return ParseRes.res(JSONElement.list(values), n);
} }
public static JSONElement parse(Filename filename, String raw) { public static JSONElement parse(Filename filename, String raw) {
if (filename == null) filename = new Filename("jscript", "json"); if (filename == null) filename = new Filename("jscript", "json");
var res = parseValue(new Source(null, filename, raw), 0); var res = parseValue(new Source(null, filename, raw), 0);
if (res.isFailed()) throw new SyntaxException(null, "Invalid JSON given."); if (res.isFailed()) throw new SyntaxException(Location.of(filename, 0, 0), "Invalid JSON given");
else if (res.isError()) throw new SyntaxException(null, res.error); else if (res.isError()) throw new SyntaxException(res.errorLocation, res.error);
else return JSONElement.of(res.result); else return JSONElement.of(res.result);
} }
public static String stringify(JSONElement el) { public static String stringify(JSONElement el) {
if (el.isNumber()) { if (el.isNumber()) {
var d = el.number(); var d = el.number();
if (d == Double.NEGATIVE_INFINITY) return "-Infinity"; if (d == Double.NEGATIVE_INFINITY) return "-Infinity";
if (d == Double.POSITIVE_INFINITY) return "Infinity"; if (d == Double.POSITIVE_INFINITY) return "Infinity";
if (Double.isNaN(d)) return "NaN"; if (Double.isNaN(d)) return "NaN";
return BigDecimal.valueOf(d).stripTrailingZeros().toPlainString(); return BigDecimal.valueOf(d).stripTrailingZeros().toPlainString();
} }
if (el.isBoolean()) return el.bool() ? "true" : "false"; if (el.isBoolean()) return el.bool() ? "true" : "false";
if (el.isNull()) return "null"; if (el.isNull()) return "null";
if (el.isString()) { if (el.isString()) {
var res = new StringBuilder("\""); var res = new StringBuilder("\"");
var alphabet = "0123456789ABCDEF".toCharArray(); var alphabet = "0123456789ABCDEF".toCharArray();
for (var c : el.string().toCharArray()) { for (var c : el.string().toCharArray()) {
if (c < 32 || c >= 127) { if (c < 32 || c >= 127) {
res res
.append("\\u") .append("\\u")
.append(alphabet[(c >> 12) & 0xF]) .append(alphabet[(c >> 12) & 0xF])
.append(alphabet[(c >> 8) & 0xF]) .append(alphabet[(c >> 8) & 0xF])
.append(alphabet[(c >> 4) & 0xF]) .append(alphabet[(c >> 4) & 0xF])
.append(alphabet[(c >> 0) & 0xF]); .append(alphabet[(c >> 0) & 0xF]);
} }
else if (c == '\\') else if (c == '\\')
res.append("\\\\"); res.append("\\\\");
else if (c == '"') else if (c == '"')
res.append("\\\""); res.append("\\\"");
else res.append(c); else res.append(c);
} }
return res.append('"').toString(); return res.append('"').toString();
} }
if (el.isList()) { if (el.isList()) {
var res = new StringBuilder().append("["); var res = new StringBuilder().append("[");
for (int i = 0; i < el.list().size(); i++) { for (int i = 0; i < el.list().size(); i++) {
if (i != 0) res.append(","); if (i != 0) res.append(",");
res.append(stringify(el.list().get(i))); res.append(stringify(el.list().get(i)));
} }
res.append("]"); res.append("]");
return res.toString(); return res.toString();
} }
if (el.isMap()) { if (el.isMap()) {
var res = new StringBuilder().append("{"); var res = new StringBuilder().append("{");
var entries = el.map().entrySet().stream().collect(Collectors.toList()); var entries = el.map().entrySet().stream().collect(Collectors.toList());
for (int i = 0; i < entries.size(); i++) { for (int i = 0; i < entries.size(); i++) {
if (i != 0) res.append(","); if (i != 0) res.append(",");
res.append(stringify(JSONElement.string(entries.get(i).getKey()))); res.append(stringify(JSONElement.string(entries.get(i).getKey())));
res.append(":"); res.append(":");
res.append(stringify(entries.get(i).getValue())); res.append(stringify(entries.get(i).getValue()));
} }
res.append("}"); res.append("}");
return res.toString(); return res.toString();
} }
return null; return null;
} }
public static String stringify(JSONMap map) { public static String stringify(JSONMap map) {
return stringify(JSONElement.of(map)); return stringify(JSONElement.of(map));
} }
public static String stringify(JSONList list) { public static String stringify(JSONList list) {
return stringify(JSONElement.of(list)); return stringify(JSONElement.of(list));
} }
} }

View File

@@ -1,87 +1,88 @@
package me.topchetoeu.jscript.common.json; package me.topchetoeu.jscript.common.json;
public class JSONElement { public class JSONElement {
public static enum Type { public static enum Type {
STRING, STRING,
NUMBER, NUMBER,
BOOLEAN, BOOLEAN,
NULL, NULL,
LIST, LIST,
MAP, MAP,
} }
public static final JSONElement NULL = new JSONElement(Type.NULL, null); public static final JSONElement NULL = new JSONElement(Type.NULL, null);
public static JSONElement map(JSONMap val) { public static JSONElement map(JSONMap val) {
return new JSONElement(Type.MAP, val); return new JSONElement(Type.MAP, val);
} }
public static JSONElement list(JSONList val) { public static JSONElement list(JSONList val) {
return new JSONElement(Type.LIST, val); return new JSONElement(Type.LIST, val);
} }
public static JSONElement string(String val) { public static JSONElement string(String val) {
return new JSONElement(Type.STRING, val); return new JSONElement(Type.STRING, val);
} }
public static JSONElement number(double val) { public static JSONElement number(double val) {
return new JSONElement(Type.NUMBER, val); return new JSONElement(Type.NUMBER, val);
} }
public static JSONElement bool(boolean val) { public static JSONElement bool(boolean val) {
return new JSONElement(Type.BOOLEAN, val); return new JSONElement(Type.BOOLEAN, val);
} }
public static JSONElement of(Object val) { public static JSONElement of(Object val) {
if (val instanceof JSONMap) return map((JSONMap)val); if (val instanceof JSONElement el) return el;
else if (val instanceof JSONList) return list((JSONList)val); else if (val instanceof JSONMap map) return map(map);
else if (val instanceof String) return string((String)val); else if (val instanceof JSONList list) return list(list);
else if (val instanceof Boolean) return bool((Boolean)val); else if (val instanceof String str) return string(str);
else if (val instanceof Number) return number(((Number)val).doubleValue()); else if (val instanceof Boolean bool) return bool(bool);
else if (val == null) return NULL; else if (val instanceof Number num) return number(num.doubleValue());
else throw new IllegalArgumentException("val must be: String, Boolean, Number, JSONList or JSONMap."); else if (val == null) return NULL;
} else throw new IllegalArgumentException("val must be: String, Boolean, Number, JSONList or JSONMap");
}
public final Type type; public final Type type;
private final Object value; private final Object value;
public boolean isMap() { return type == Type.MAP; } public boolean isMap() { return type == Type.MAP; }
public boolean isList() { return type == Type.LIST; } public boolean isList() { return type == Type.LIST; }
public boolean isString() { return type == Type.STRING; } public boolean isString() { return type == Type.STRING; }
public boolean isNumber() { return type == Type.NUMBER; } public boolean isNumber() { return type == Type.NUMBER; }
public boolean isBoolean() { return type == Type.BOOLEAN; } public boolean isBoolean() { return type == Type.BOOLEAN; }
public boolean isNull() { return type == Type.NULL; } public boolean isNull() { return type == Type.NULL; }
public JSONMap map() { public JSONMap map() {
if (!isMap()) throw new IllegalStateException("Element is not a map."); if (!isMap()) throw new IllegalStateException("Element is not a map");
return (JSONMap)value; return (JSONMap)value;
} }
public JSONList list() { public JSONList list() {
if (!isList()) throw new IllegalStateException("Element is not a map."); if (!isList()) throw new IllegalStateException("Element is not a map");
return (JSONList)value; return (JSONList)value;
} }
public String string() { public String string() {
if (!isString()) throw new IllegalStateException("Element is not a string."); if (!isString()) throw new IllegalStateException("Element is not a string");
return (String)value; return (String)value;
} }
public double number() { public double number() {
if (!isNumber()) throw new IllegalStateException("Element is not a number."); if (!isNumber()) throw new IllegalStateException("Element is not a number");
return (double)value; return (double)value;
} }
public boolean bool() { public boolean bool() {
if (!isBoolean()) throw new IllegalStateException("Element is not a boolean."); if (!isBoolean()) throw new IllegalStateException("Element is not a boolean");
return (boolean)value; return (boolean)value;
} }
@Override public String toString() { @Override public String toString() {
if (isMap()) return "{...}"; if (isMap()) return "{...}";
if (isList()) return "[...]"; if (isList()) return "[...]";
if (isString()) return (String)value; if (isString()) return (String)value;
if (isString()) return (String)value; if (isString()) return (String)value;
if (isNumber()) return (double)value + ""; if (isNumber()) return (double)value + "";
if (isBoolean()) return (boolean)value + ""; if (isBoolean()) return (boolean)value + "";
if (isNull()) return "null"; if (isNull()) return "null";
return ""; return "";
} }
private JSONElement(Type type, Object val) { private JSONElement(Type type, Object val) {
this.type = type; this.type = type;
this.value = val; this.value = val;
} }
} }

View File

@@ -6,21 +6,21 @@ import java.util.Collection;
import java.util.Map; import java.util.Map;
public class JSONList extends ArrayList<JSONElement> { public class JSONList extends ArrayList<JSONElement> {
public JSONList() {} public JSONList() {}
public JSONList(JSONElement ...els) { public JSONList(JSONElement ...els) {
super(Arrays.asList(els)); super(Arrays.asList(els));
} }
public JSONList(Collection<JSONElement> els) { public JSONList(Collection<JSONElement> els) {
super(els); super(els);
} }
public JSONList addNull() { this.add(JSONElement.NULL); return this; } public JSONList addNull() { this.add(JSONElement.NULL); return this; }
public JSONList add(String val) { this.add(JSONElement.of(val)); return this; } public JSONList add(String val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(double val) { this.add(JSONElement.of(val)); return this; } public JSONList add(double val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(boolean val) { this.add(JSONElement.of(val)); return this; } public JSONList add(boolean val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(Map<String, JSONElement> val) { this.add(JSONElement.of(val)); return this; } public JSONList add(Map<String, JSONElement> val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(Collection<JSONElement> val) { this.add(JSONElement.of(val)); return this; } public JSONList add(Collection<JSONElement> val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(JSONMap val) { this.add(JSONElement.of(val)); return this; } public JSONList add(JSONMap val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(JSONList val) { this.add(JSONElement.of(val)); return this; } public JSONList add(JSONList val) { this.add(JSONElement.of(val)); return this; }
} }

View File

@@ -6,133 +6,131 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
public class JSONMap implements Map<String, JSONElement> { public class JSONMap implements Map<String, JSONElement> {
private Map<String, JSONElement> elements = new HashMap<>(); private Map<String, JSONElement> elements = new HashMap<>();
public JSONElement get(String path) { public JSONElement get(String path) {
var curr = this; JSONElement curr = JSONElement.map(this);
var segs = path.split("\\.");
var i = 0;
while (true) { for (var seg : path.split("\\.")) {
var tmp = curr.elements.get(segs[i++]); if (!curr.isMap()) return null;
if (i == segs.length) return tmp; curr = curr.map().elements.get(seg);
if (!tmp.isMap()) return null; }
curr = tmp.map();
}
}
public boolean isMap(String path) { return curr;
var el = get(path); }
return el != null && el.isMap();
}
public boolean isList(String path) {
var el = get(path);
return el != null && el.isList();
}
public boolean isString(String path) {
var el = get(path);
return el != null && el.isString();
}
public boolean isNumber(String path) {
var el = get(path);
return el != null && el.isNumber();
}
public boolean isBoolean(String path) {
var el = get(path);
return el != null && el.isBoolean();
}
public boolean isNull(String path) {
var el = get(path);
return el != null && el.isNull();
}
public boolean contains(String path) {
return get(path) != null;
}
public JSONMap map(String path) { public boolean isMap(String path) {
var el = get(path); var el = get(path);
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path)); return el != null && el.isMap();
return el.map(); }
} public boolean isList(String path) {
public JSONMap map(String path, JSONMap defaultVal) { var el = get(path);
var el = get(path); return el != null && el.isList();
if (el == null) return defaultVal; }
if (el.isMap()) return el.map(); public boolean isString(String path) {
return defaultVal; var el = get(path);
} return el != null && el.isString();
}
public boolean isNumber(String path) {
var el = get(path);
return el != null && el.isNumber();
}
public boolean isBoolean(String path) {
var el = get(path);
return el != null && el.isBoolean();
}
public boolean isNull(String path) {
var el = get(path);
return el != null && el.isNull();
}
public boolean contains(String path) {
return get(path) != null;
}
public JSONList list(String path) { public JSONMap map(String path) {
var el = get(path); var el = get(path);
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path)); if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist", path));
return el.list(); return el.map();
} }
public JSONList list(String path, JSONList defaultVal) { public JSONMap map(String path, JSONMap defaultVal) {
var el = get(path); var el = get(path);
if (el == null) return defaultVal; if (el == null) return defaultVal;
if (el.isList()) return el.list(); if (el.isMap()) return el.map();
return defaultVal; return defaultVal;
} }
public String string(String path) { public JSONList list(String path) {
var el = get(path); var el = get(path);
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path)); if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist", path));
return el.string(); return el.list();
} }
public String string(String path, String defaultVal) { public JSONList list(String path, JSONList defaultVal) {
var el = get(path); var el = get(path);
if (el == null) return defaultVal; if (el == null) return defaultVal;
if (el.isString()) return el.string(); if (el.isList()) return el.list();
return defaultVal; return defaultVal;
} }
public double number(String path) { public String string(String path) {
var el = get(path); var el = get(path);
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path)); if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist", path));
return el.number(); return el.string();
} }
public double number(String path, double defaultVal) { public String string(String path, String defaultVal) {
var el = get(path); var el = get(path);
if (el == null) return defaultVal; if (el == null) return defaultVal;
if (el.isNumber()) return el.number(); if (el.isString()) return el.string();
return defaultVal; return defaultVal;
} }
public boolean bool(String path) { public double number(String path) {
var el = get(path); var el = get(path);
if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path)); if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist", path));
return el.bool(); return el.number();
} }
public boolean bool(String path, boolean defaultVal) { public double number(String path, double defaultVal) {
var el = get(path); var el = get(path);
if (el == null) return defaultVal; if (el == null) return defaultVal;
if (el.isBoolean()) return el.bool(); if (el.isNumber()) return el.number();
return defaultVal; return defaultVal;
} }
public JSONMap setNull(String key) { elements.put(key, JSONElement.NULL); return this; } public boolean bool(String path) {
public JSONMap set(String key, String val) { elements.put(key, JSONElement.of(val)); return this; } var el = get(path);
public JSONMap set(String key, double val) { elements.put(key, JSONElement.of(val)); return this; } if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist", path));
public JSONMap set(String key, boolean val) { elements.put(key, JSONElement.of(val)); return this; } return el.bool();
public JSONMap set(String key, Map<String, JSONElement> val) { elements.put(key, JSONElement.of(val)); return this; } }
public JSONMap set(String key, Collection<JSONElement> val) { elements.put(key, JSONElement.of(val)); return this; } public boolean bool(String path, boolean defaultVal) {
var el = get(path);
if (el == null) return defaultVal;
if (el.isBoolean()) return el.bool();
return defaultVal;
}
@Override public int size() { return elements.size(); } public JSONMap setNull(String key) { elements.put(key, JSONElement.NULL); return this; }
@Override public boolean isEmpty() { return elements.isEmpty(); } public JSONMap set(String key, String val) { elements.put(key, JSONElement.of(val)); return this; }
@Override public boolean containsKey(Object key) { return elements.containsKey(key); } public JSONMap set(String key, double val) { elements.put(key, JSONElement.of(val)); return this; }
@Override public boolean containsValue(Object value) { return elements.containsValue(value); } public JSONMap set(String key, boolean val) { elements.put(key, JSONElement.of(val)); return this; }
@Override public JSONElement get(Object key) { return elements.get(key); } public JSONMap set(String key, Map<String, JSONElement> val) { elements.put(key, JSONElement.of(val)); return this; }
@Override public JSONElement put(String key, JSONElement value) { return elements.put(key, value); } public JSONMap set(String key, Collection<JSONElement> val) { elements.put(key, JSONElement.of(val)); return this; }
@Override public JSONElement remove(Object key) { return elements.remove(key); }
@Override public void putAll(Map<? extends String, ? extends JSONElement> m) { elements.putAll(m); }
@Override public void clear() { elements.clear(); } @Override public int size() { return elements.size(); }
@Override public boolean isEmpty() { return elements.isEmpty(); }
@Override public boolean containsKey(Object key) { return elements.containsKey(key); }
@Override public boolean containsValue(Object value) { return elements.containsValue(value); }
@Override public JSONElement get(Object key) { return elements.get(key); }
@Override public JSONElement put(String key, JSONElement value) { return elements.put(key, value); }
@Override public JSONElement remove(Object key) { return elements.remove(key); }
@Override public void putAll(Map<? extends String, ? extends JSONElement> m) { elements.putAll(m); }
@Override public Set<String> keySet() { return elements.keySet(); } @Override public void clear() { elements.clear(); }
@Override public Collection<JSONElement> values() { return elements.values(); }
@Override public Set<Entry<String, JSONElement>> entrySet() { return elements.entrySet(); }
public JSONMap() { } @Override public Set<String> keySet() { return elements.keySet(); }
public JSONMap(Map<String, JSONElement> els) { @Override public Collection<JSONElement> values() { return elements.values(); }
this.elements = new HashMap<>(els); @Override public Set<Entry<String, JSONElement>> entrySet() { return elements.entrySet(); }
}
public JSONMap() { }
public JSONMap(Map<String, JSONElement> els) {
this.elements = new HashMap<>(els);
}
} }

View File

@@ -1,8 +0,0 @@
package me.topchetoeu.jscript.common.mapping;
public enum ConvertType {
Exact,
Lower,
Upper,
Both,
}

View File

@@ -9,184 +9,185 @@ import java.util.NavigableSet;
import java.util.Objects; import java.util.Objects;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.function.Function;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Filename; import me.topchetoeu.jscript.common.parsing.Filename;
import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.compilation.scope.Scope;
public class FunctionMap { public class FunctionMap {
public static class FunctionMapBuilder { public static class FunctionMapBuilder {
private final TreeMap<Integer, Location> sourceMap = new TreeMap<>(); private final TreeMap<Integer, Location> sourceMap = new TreeMap<>();
private final HashMap<Location, BreakpointType> breakpoints = new HashMap<>(); private final HashMap<Location, BreakpointType> breakpoints = new HashMap<>();
public Location toLocation(int pc) { public Location toLocation(int pc) {
return sourceMap.headMap(pc, true).firstEntry().getValue(); return sourceMap.headMap(pc, true).firstEntry().getValue();
} }
public FunctionMapBuilder setDebug(Location loc, BreakpointType type) { public FunctionMapBuilder setDebug(Location loc, BreakpointType type) {
if (loc == null || type == null || type == BreakpointType.NONE) return this; if (loc == null || type == null || type == BreakpointType.NONE) return this;
breakpoints.put(loc, type); breakpoints.put(loc, type);
return this; return this;
} }
public FunctionMapBuilder setLocation(int i, Location loc) { public FunctionMapBuilder setLocation(int i, Location loc) {
if (loc == null || i < 0) return this; if (loc == null || i < 0) return this;
sourceMap.put(i, loc); sourceMap.put(i, loc);
return this; return this;
} }
public FunctionMapBuilder setLocationAndDebug(int i, Location loc, BreakpointType type) { public FunctionMapBuilder setLocationAndDebug(int i, Location loc, BreakpointType type) {
setDebug(loc, type); setDebug(loc, type);
setLocation(i, loc); setLocation(i, loc);
return this; return this;
} }
public Location first() { public Location first() {
if (sourceMap.size() == 0) return null; if (sourceMap.size() == 0) return null;
return sourceMap.firstEntry().getValue(); return sourceMap.firstEntry().getValue();
} }
public Location last() { public Location last() {
if (sourceMap.size() == 0) return null; if (sourceMap.size() == 0) return null;
return sourceMap.lastEntry().getValue(); return sourceMap.lastEntry().getValue();
} }
public FunctionMap build(String[] localNames, String[] captureNames) { public FunctionMapBuilder map(Function<Location, Location> mapper) {
return new FunctionMap(sourceMap, breakpoints, localNames, captureNames); var newSourceMaps = new HashMap<Integer, Location>();
} var newBreakpoints = new HashMap<Location, BreakpointType>();
public FunctionMap build(Scope scope) {
return new FunctionMap(sourceMap, breakpoints, new String[0], new String[0]);
}
public FunctionMap build() {
return new FunctionMap(sourceMap, breakpoints, new String[0], new String[0]);
}
private FunctionMapBuilder() { } for (var key : sourceMap.keySet()) {
} var mapped = mapper.apply(sourceMap.get(key));
if (mapped == null) continue;
newSourceMaps.put(key, mapped);
}
for (var key : breakpoints.keySet()) {
var mapped = mapper.apply(key);
if (mapped == null) continue;
newBreakpoints.put(mapped, breakpoints.get(key));
}
public static final FunctionMap EMPTY = new FunctionMap(); sourceMap.clear();
sourceMap.putAll(newSourceMaps);
private final HashMap<Integer, BreakpointType> bps = new HashMap<>(); breakpoints.clear();
private final HashMap<Filename, TreeSet<Location>> bpLocs = new HashMap<>(); breakpoints.putAll(newBreakpoints);
private final TreeMap<Integer, Location> pcToLoc = new TreeMap<>(); return this;
}
public final String[] localNames, captureNames; public FunctionMap build(String[] localNames, String[] capturableNames, String[] captureNames) {
return new FunctionMap(sourceMap, breakpoints, localNames, capturableNames, captureNames);
}
public FunctionMap build(Function<Location, Location> mapper) {
return new FunctionMap(sourceMap, breakpoints, new String[0], new String[0], new String[0]);
}
public Location toLocation(int pc, boolean approxiamte) { private FunctionMapBuilder() { }
if (pcToLoc.size() == 0 || pc < 0 || pc > pcToLoc.lastKey()) return null; }
var res = pcToLoc.get(pc);
if (!approxiamte || res != null) return res;
var entry = pcToLoc.headMap(pc, true).lastEntry();
if (entry == null) return null;
else return entry.getValue();
}
public Location toLocation(int pc) {
return toLocation(pc, false);
}
public BreakpointType getBreakpoint(int pc) { public static final FunctionMap EMPTY = new FunctionMap();
return bps.getOrDefault(pc, BreakpointType.NONE);
}
public Location correctBreakpoint(Location loc) {
var set = bpLocs.get(loc.filename());
if (set == null) return null;
else return set.ceiling(loc);
}
public List<Location> correctBreakpoint(Pattern filename, int line, int column) {
var candidates = new HashMap<Filename, TreeSet<Location>>();
for (var name : bpLocs.keySet()) { private final HashMap<Integer, BreakpointType> bps = new HashMap<>();
if (filename.matcher(name.toString()).matches()) { private final HashMap<Filename, TreeSet<Location>> bpLocs = new HashMap<>();
candidates.put(name, bpLocs.get(name));
}
}
var res = new ArrayList<Location>(candidates.size()); private final TreeMap<Integer, Location> pcToLoc = new TreeMap<>();
for (var candidate : candidates.entrySet()) {
var val = correctBreakpoint(Location.of(candidate.getKey(), line, column));
if (val == null) continue;
res.add(val);
}
return res; public final String[] localNames, capturableNames, captureNames;
}
public List<Location> breakpoints(Location start, Location end) {
if (!Objects.equals(start.filename(), end.filename())) return Arrays.asList();
NavigableSet<Location> set = bpLocs.get(start.filename());
if (set == null) return Arrays.asList();
if (start != null) set = set.tailSet(start, true); public Location toLocation(int pc, boolean approxiamte) {
if (end != null) set = set.headSet(end, true); if (pcToLoc.size() == 0 || pc < 0 || pc > pcToLoc.lastKey()) return null;
var res = pcToLoc.get(pc);
if (!approxiamte || res != null) return res;
var entry = pcToLoc.headMap(pc, true).lastEntry();
if (entry == null) return null;
else return entry.getValue();
}
public Location toLocation(int pc) {
return toLocation(pc, false);
}
return set.stream().collect(Collectors.toList()); public BreakpointType getBreakpoint(int pc) {
} return bps.getOrDefault(pc, BreakpointType.NONE);
}
public Location correctBreakpoint(Location loc) {
var set = bpLocs.get(loc.filename());
if (set == null) return null;
else return set.ceiling(loc);
}
public List<Location> correctBreakpoint(Pattern filename, int line, int column) {
var candidates = new HashMap<Filename, TreeSet<Location>>();
public Location start() { for (var name : bpLocs.keySet()) {
if (pcToLoc.size() == 0) return null; if (filename.matcher(name.toString()).matches()) {
return pcToLoc.firstEntry().getValue(); candidates.put(name, bpLocs.get(name));
} }
public Location end() { }
if (pcToLoc.size() == 0) return null;
return pcToLoc.lastEntry().getValue();
}
// public static FunctionMap apply(FunctionMap funcMap, SourceMap map) { var res = new ArrayList<Location>(candidates.size());
// var res = new FunctionMap(Map.of(), Map.of(), funcMap.localNames, funcMap.captureNames); for (var candidate : candidates.entrySet()) {
var val = correctBreakpoint(Location.of(candidate.getKey(), line, column));
if (val == null) continue;
res.add(val);
}
// for (var el : funcMap.pcToLoc.entrySet()) { return res;
// res.pcToLoc.put(el.getKey(), map.toCompiled(el.getValue())); }
// } public List<Location> breakpoints(Location start, Location end) {
if (!Objects.equals(start.filename(), end.filename())) return Arrays.asList();
NavigableSet<Location> set = bpLocs.get(start.filename());
if (set == null) return Arrays.asList();
// res.bps.putAll(bps); if (start != null) set = set.tailSet(start, true);
if (end != null) set = set.headSet(end, true);
// for (var el : bpLocs.entrySet()) { return set.stream().collect(Collectors.toList());
// for (var loc : el.getValue()) { }
// loc = map.toCompiled(loc);
// if (loc == null) continue;
// if (!res.bpLocs.containsKey(loc.filename())) res.bpLocs.put(loc.filename(), new TreeSet<>()); public Location start() {
// res.bpLocs.get(loc.filename()).add(loc); if (pcToLoc.size() == 0) return null;
// } return pcToLoc.firstEntry().getValue();
// } }
public Location end() {
if (pcToLoc.size() == 0) return null;
return pcToLoc.lastEntry().getValue();
}
// return res; public FunctionMap clone() {
// } var res = new FunctionMap(new HashMap<>(), new HashMap<>(), localNames, capturableNames, captureNames);
res.pcToLoc.putAll(this.pcToLoc);
res.bps.putAll(bps);
res.bpLocs.putAll(bpLocs);
res.pcToLoc.putAll(pcToLoc);
return res;
}
public FunctionMap clone() { public FunctionMap(Map<Integer, Location> map, Map<Location, BreakpointType> breakpoints, String[] localNames, String[] capturableNames, String[] captureNames) {
var res = new FunctionMap(new HashMap<>(), new HashMap<>(), localNames, captureNames); var locToPc = new HashMap<Location, Integer>();
res.pcToLoc.putAll(this.pcToLoc);
res.bps.putAll(bps);
res.bpLocs.putAll(bpLocs);
res.pcToLoc.putAll(pcToLoc);
return res;
}
public FunctionMap(Map<Integer, Location> map, Map<Location, BreakpointType> breakpoints, String[] localNames, String[] captureNames) { for (var el : map.entrySet()) {
var locToPc = new HashMap<Location, Integer>(); pcToLoc.put(el.getKey(), el.getValue());
locToPc.putIfAbsent(el.getValue(), el.getKey());
}
for (var el : map.entrySet()) { for (var el : breakpoints.entrySet()) {
pcToLoc.put(el.getKey(), el.getValue()); if (el.getValue() == null || el.getValue() == BreakpointType.NONE) continue;
locToPc.putIfAbsent(el.getValue(), el.getKey()); bps.put(locToPc.get(el.getKey()), el.getValue());
}
for (var el : breakpoints.entrySet()) { if (!bpLocs.containsKey(el.getKey().filename())) bpLocs.put(el.getKey().filename(), new TreeSet<>());
if (el.getValue() == null || el.getValue() == BreakpointType.NONE) continue; bpLocs.get(el.getKey().filename()).add(el.getKey());
bps.put(locToPc.get(el.getKey()), el.getValue()); }
if (!bpLocs.containsKey(el.getKey().filename())) bpLocs.put(el.getKey().filename(), new TreeSet<>()); this.localNames = localNames;
bpLocs.get(el.getKey().filename()).add(el.getKey()); this.captureNames = captureNames;
} this.capturableNames = capturableNames;
}
private FunctionMap() {
localNames = new String[0];
captureNames = new String[0];
capturableNames = new String[0];
}
this.localNames = localNames; public static FunctionMapBuilder builder() {
this.captureNames = captureNames; return new FunctionMapBuilder();
} }
private FunctionMap() {
localNames = new String[0];
captureNames = new String[0];
}
public static FunctionMapBuilder builder() {
return new FunctionMapBuilder();
}
} }

View File

@@ -3,57 +3,51 @@ package me.topchetoeu.jscript.common.parsing;
import java.io.File; import java.io.File;
public class Filename { public class Filename {
public final String protocol; public final String protocol;
public final String path; public final String path;
@Override public String toString() { @Override public String toString() {
return protocol + "://" + path; return protocol + "://" + path;
} }
@Override public int hashCode() { @Override public int hashCode() {
final int prime = 31; final int prime = 31;
int result = 1; int result = 1;
result = prime * result + protocol.hashCode(); result = prime * result + protocol.hashCode();
result = prime * result + path.hashCode(); result = prime * result + path.hashCode();
return result; return result;
} }
@Override public boolean equals(Object obj) { @Override public boolean equals(Object obj) {
if (this == obj) return true; if (this == obj) return true;
if (obj == null) return false; if (obj == null) return false;
if (getClass() != obj.getClass()) return false; if (getClass() != obj.getClass()) return false;
var other = (Filename) obj; var other = (Filename) obj;
if (protocol == null) { if (protocol == null) {
if (other.protocol != null) return false; if (other.protocol != null) return false;
} }
else if (!protocol.equals(other.protocol)) return false; else if (!protocol.equals(other.protocol)) return false;
if (path == null) { if (path == null) {
if (other.path != null) return false; if (other.path != null) return false;
} }
else if (!path.equals(other.path)) return false; else if (!path.equals(other.path)) return false;
return true; return true;
} }
public Filename(String protocol, String path) { public Filename(String protocol, String path) {
path = path.trim(); path = path.trim();
protocol = protocol.trim(); protocol = protocol.trim();
this.protocol = protocol; this.protocol = protocol;
this.path = path; this.path = path;
} }
public static Filename parse(String val) { public static Filename parse(String val) {
var i = val.indexOf("://"); var i = val.indexOf("://");
if (i >= 0) return new Filename(val.substring(0, i).trim(), val.substring(i + 3).trim()); if (i >= 0) return new Filename(val.substring(0, i).trim(), val.substring(i + 3).trim());
else return new Filename("file", val.trim()); else return new Filename("file", val.trim());
} }
// public static Path normalize(String path) { public static Filename fromFile(File file) {
// // File file = new File("/" + path.trim().replace("\\", "/")); return new Filename("file", file.getAbsolutePath());
// // String normalizedPath = new File("/" + path.trim().replace("\\", "/")).getAbsolutePath().replaceFirst("^/", "").replace("\\", "/"); }
// // return normalizedPath;
// return Path.of(Path.of("/" + path.trim().replace("\\", "/")).normalize().toString().substring(1));
// }
public static Filename fromFile(File file) {
return new Filename("file", file.getAbsolutePath());
}
} }

View File

@@ -4,105 +4,108 @@ import java.util.ArrayList;
import java.util.Objects; import java.util.Objects;
public abstract class Location implements Comparable<Location> { public abstract class Location implements Comparable<Location> {
public static final Location INTERNAL = Location.of(new Filename("jscript", "native"), -1, -1); public static final Location INTERNAL = Location.of(new Filename("jscript", "native"), -1, -1);
public abstract int line(); public abstract int line();
public abstract int start(); public abstract int start();
public abstract Filename filename(); public abstract Filename filename();
public final String toString() { public final String toString() {
var res = new ArrayList<String>(); var res = new ArrayList<String>();
if (filename() != null) res.add(filename().toString()); if (filename() != null) res.add(filename().toString());
if (line() >= 0) res.add(line() + 1 + ""); if (line() >= 0) res.add(line() + 1 + "");
if (start() >= 0) res.add(start() + 1 + ""); if (start() >= 0) res.add(start() + 1 + "");
return String.join(":", res); return String.join(":", res);
} }
public final Location add(int n) { public final Location add(int n) {
var self = this; var self = this;
return new Location() { return new Location() {
@Override public Filename filename() { return self.filename(); } @Override public Filename filename() { return self.filename(); }
@Override public int start() { return self.start() + n; } @Override public int start() { return self.start() + n; }
@Override public int line() { return self.line(); } @Override public int line() { return self.line(); }
}; };
} }
public final Location nextLine() { public final Location nextLine() {
var self = this; return changeLine(1);
}
public final Location changeLine(int offset) {
var self = this;
return new Location() { return new Location() {
@Override public Filename filename() { return self.filename(); } @Override public Filename filename() { return self.filename(); }
@Override public int start() { return 0; } @Override public int start() { return 0; }
@Override public int line() { return self.line() + 1; } @Override public int line() { return self.line() + offset; }
}; };
} }
@Override public int hashCode() { @Override public int hashCode() {
return Objects.hash(line(), start(), filename()); return Objects.hash(line(), start(), filename());
} }
@Override public boolean equals(Object obj) { @Override public boolean equals(Object obj) {
if (this == obj) return true; if (this == obj) return true;
if (!(obj instanceof Location)) return false; if (!(obj instanceof Location)) return false;
var other = (Location)obj; var other = (Location)obj;
if (!Objects.equals(this.start(), other.start())) return false; if (!Objects.equals(this.start(), other.start())) return false;
if (!Objects.equals(this.line(), other.line())) return false; if (!Objects.equals(this.line(), other.line())) return false;
if (!Objects.equals(this.filename(), other.filename())) return false; if (!Objects.equals(this.filename(), other.filename())) return false;
return true; return true;
} }
@Override public int compareTo(Location other) { @Override public int compareTo(Location other) {
int a = filename().toString().compareTo(other.filename().toString()); int a = filename().toString().compareTo(other.filename().toString());
int b = Integer.compare(line(), other.line()); int b = Integer.compare(line(), other.line());
int c = Integer.compare(start(), other.start()); int c = Integer.compare(start(), other.start());
if (a != 0) return a; if (a != 0) return a;
if (b != 0) return b; if (b != 0) return b;
return c; return c;
} }
public static Location of(Filename filename, int line, int start) { public static Location of(Filename filename, int line, int start) {
return new Location() { return new Location() {
@Override public Filename filename() { return filename; } @Override public Filename filename() { return filename; }
@Override public int start() { return start; } @Override public int start() { return start; }
@Override public int line() { return line; } @Override public int line() { return line; }
}; };
} }
public static Location of(String raw) { public static Location of(String raw) {
var i0 = raw.lastIndexOf(':'); var i0 = raw.lastIndexOf(':');
if (i0 < 0) return Location.of(Filename.parse(raw), -1, -1); if (i0 < 0) return Location.of(Filename.parse(raw), -1, -1);
var i1 = raw.lastIndexOf(':', i0); var i1 = raw.lastIndexOf(':', i0);
if (i0 < 0) { if (i0 < 0) {
try { try {
return Location.of(Filename.parse(raw.substring(0, i0)), Integer.parseInt(raw.substring(i0 + 1)), -1); return Location.of(Filename.parse(raw.substring(0, i0)), Integer.parseInt(raw.substring(i0 + 1)), -1);
} }
catch (NumberFormatException e) { catch (NumberFormatException e) {
return Location.of(Filename.parse(raw), -1, -1); return Location.of(Filename.parse(raw), -1, -1);
} }
} }
int start, line; int start, line;
try { try {
start = Integer.parseInt(raw.substring(i1 + 1)); start = Integer.parseInt(raw.substring(i1 + 1));
} }
catch (NumberFormatException e) { catch (NumberFormatException e) {
return Location.of(Filename.parse(raw), -1, -1); return Location.of(Filename.parse(raw), -1, -1);
} }
try { try {
line = Integer.parseInt(raw.substring(i0 + 1, i1)); line = Integer.parseInt(raw.substring(i0 + 1, i1));
} }
catch (NumberFormatException e) { catch (NumberFormatException e) {
return Location.of(Filename.parse(raw.substring(i1 + 1)), start, -1); return Location.of(Filename.parse(raw.substring(i1 + 1)), start, -1);
} }
return Location.of(Filename.parse(raw.substring(0, i0)), start, line); return Location.of(Filename.parse(raw.substring(0, i0)), start, line);
} }
} }

View File

@@ -1,80 +1,80 @@
package me.topchetoeu.jscript.common.parsing; package me.topchetoeu.jscript.common.parsing;
public class ParseRes<T> { public class ParseRes<T> {
public static enum State { public static enum State {
SUCCESS, SUCCESS,
FAILED, FAILED,
ERROR; ERROR;
public boolean isSuccess() { return this == SUCCESS; } public boolean isSuccess() { return this == SUCCESS; }
public boolean isFailed() { return this == FAILED; } public boolean isFailed() { return this == FAILED; }
public boolean isError() { return this == ERROR; } public boolean isError() { return this == ERROR; }
} }
public final ParseRes.State state; public final ParseRes.State state;
public final Location errorLocation; public final Location errorLocation;
public final String error; public final String error;
public final T result; public final T result;
public final int n; public final int n;
private ParseRes(ParseRes.State state, Location errorLocation, String error, T result, int readN) { private ParseRes(ParseRes.State state, Location errorLocation, String error, T result, int readN) {
this.result = result; this.result = result;
this.n = readN; this.n = readN;
this.state = state; this.state = state;
this.error = error; this.error = error;
this.errorLocation = errorLocation; this.errorLocation = errorLocation;
} }
public ParseRes<T> setN(int i) { public ParseRes<T> setN(int i) {
if (!state.isSuccess()) return this; if (!state.isSuccess()) return this;
return new ParseRes<>(state, null, null, result, i); return new ParseRes<>(state, null, null, result, i);
} }
public ParseRes<T> addN(int n) { public ParseRes<T> addN(int n) {
if (!state.isSuccess()) return this; if (!state.isSuccess()) return this;
return new ParseRes<>(state, null, null, result, this.n + n); return new ParseRes<>(state, null, null, result, this.n + n);
} }
public <T2> ParseRes<T2> chainError() { public <T2> ParseRes<T2> chainError() {
if (isSuccess()) throw new RuntimeException("Can't transform a ParseRes that hasn't failed."); if (isSuccess()) throw new RuntimeException("Can't transform a ParseRes that hasn't failed");
return new ParseRes<>(state, errorLocation, error, null, 0); return new ParseRes<>(state, errorLocation, error, null, 0);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T2> ParseRes<T2> chainError(ParseRes<?> other) { public <T2> ParseRes<T2> chainError(ParseRes<?> other) {
if (!this.isError()) return other.chainError(); if (!this.isError()) return other.chainError();
return (ParseRes<T2>) this; return (ParseRes<T2>) this;
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T2> ParseRes<T2> chainError(Location loc, String error) { public <T2> ParseRes<T2> chainError(Location loc, String error) {
if (!this.isError()) return new ParseRes<>(State.ERROR, loc, error, null, 0); if (!this.isError()) return new ParseRes<>(State.ERROR, loc, error, null, 0);
return (ParseRes<T2>) this; return (ParseRes<T2>) this;
} }
public boolean isSuccess() { return state.isSuccess(); } public boolean isSuccess() { return state.isSuccess(); }
public boolean isFailed() { return state.isFailed(); } public boolean isFailed() { return state.isFailed(); }
public boolean isError() { return state.isError(); } public boolean isError() { return state.isError(); }
public static <T> ParseRes<T> failed() { public static <T> ParseRes<T> failed() {
return new ParseRes<T>(State.FAILED, null, null, null, 0); return new ParseRes<T>(State.FAILED, null, null, null, 0);
} }
public static <T> ParseRes<T> error(Location loc, String error) { public static <T> ParseRes<T> error(Location loc, String error) {
// TODO: differentiate definitive and probable errors // TODO: differentiate definitive and probable errors
return new ParseRes<>(State.ERROR, loc, error, null, 0); return new ParseRes<>(State.ERROR, loc, error, null, 0);
} }
public static <T> ParseRes<T> res(T val, int i) { public static <T> ParseRes<T> res(T val, int i) {
return new ParseRes<>(State.SUCCESS, null, null, val, i); return new ParseRes<>(State.SUCCESS, null, null, val, i);
} }
@SafeVarargs @SafeVarargs
@SuppressWarnings("all") @SuppressWarnings("all")
public static <T> ParseRes<T> first(Source src, int i, Parser ...parsers) { public static <T> ParseRes<T> first(Source src, int i, Parser ...parsers) {
int n = Parsing.skipEmpty(src, i); int n = Parsing.skipEmpty(src, i);
ParseRes<T> error = ParseRes.failed(); ParseRes<T> error = ParseRes.failed();
for (var parser : parsers) { for (var parser : parsers) {
var res = parser.parse(src, i + n); var res = parser.parse(src, i + n);
if (res.isSuccess()) return res.addN(n); if (res.isSuccess()) return res.addN(n);
if (res.isError() && error.isFailed()) error = res.chainError(); if (res.isError() && error.isFailed()) error = res.chainError();
} }
return error; return error;
} }
} }

View File

@@ -1,5 +1,5 @@
package me.topchetoeu.jscript.common.parsing; package me.topchetoeu.jscript.common.parsing;
public interface Parser<T> { public interface Parser<T> {
public ParseRes<T> parse(Source src, int i); public ParseRes<T> parse(Source src, int i);
} }

View File

@@ -4,417 +4,449 @@ import me.topchetoeu.jscript.common.SyntaxException;
import me.topchetoeu.jscript.compilation.values.constants.NumberNode; import me.topchetoeu.jscript.compilation.values.constants.NumberNode;
public class Parsing { public class Parsing {
public static boolean isDigit(Character c) { public static boolean isDigit(Character c) {
return c != null && c >= '0' && c <= '9'; return c != null && c >= '0' && c <= '9';
} }
public static boolean isAny(char c, String alphabet) { public static boolean isAny(char c, String alphabet) {
return alphabet.contains(Character.toString(c)); return alphabet.contains(Character.toString(c));
} }
public static int fromHex(char c) { public static int fromHex(char c) {
if (c >= 'A' && c <= 'F') return c - 'A' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10;
if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= '0' && c <= '9') return c - '0'; if (c >= '0' && c <= '9') return c - '0';
return -1; return -1;
} }
public static int skipEmpty(Source src, int i) { public static int skipEmpty(Source src, int i) {
return skipEmpty(src, i, true); return skipEmpty(src, i, true);
} }
public static int skipEmpty(Source src, int i, boolean noComments) { public static int skipEmpty(Source src, int i, boolean noComments) {
int n = 0; int n = 0;
if (i == 0 && src.is(0, "#!")) { if (i == 0 && src.is(0, "#!")) {
while (!src.is(n, '\n')) n++; while (!src.is(n, '\n')) n++;
n++; n++;
} }
var isSingle = false; var isSingle = false;
var isMulti = false; var isMulti = false;
while (i + n < src.size()) { while (i + n < src.size()) {
if (isSingle) { if (isSingle) {
if (src.is(i + n, '\n')) { if (src.is(i + n, '\n')) {
n++; n++;
isSingle = false; isSingle = false;
} }
else n++; else n++;
} }
else if (isMulti) { else if (isMulti) {
if (src.is(i + n, "*/")) { if (src.is(i + n, "*/")) {
n += 2; n += 2;
isMulti = false; isMulti = false;
} }
else n++; else n++;
} }
else if (src.is(i + n, "//")) { else if (src.is(i + n, "//")) {
n += 2; n += 2;
isSingle = true; isSingle = true;
} }
else if (src.is(i + n, "/*")) { else if (src.is(i + n, "/*")) {
n += 2; n += 2;
isMulti = true; isMulti = true;
} }
else if (src.is(i + n, Character::isWhitespace)) { else if (src.is(i + n, Character::isWhitespace)) {
n++; n++;
} }
else break; else break;
} }
return n; return n;
} }
public static ParseRes<Character> parseChar(Source src, int i) { public static ParseRes<Character> parseChar(Source src, int i) {
int n = 0; int n = 0;
if (src.is(i + n, '\\')) { if (src.is(i + n, '\\')) {
n++; n++;
char c = src.at(i + n++); char c = src.at(i + n++);
if (c == 'b') return ParseRes.res('\b', n); if (c == 'b') return ParseRes.res('\b', n);
else if (c == 't') return ParseRes.res('\t', n); else if (c == 't') return ParseRes.res('\t', n);
else if (c == 'n') return ParseRes.res('\n', n); else if (c == 'n') return ParseRes.res('\n', n);
else if (c == 'f') return ParseRes.res('\f', n); else if (c == 'f') return ParseRes.res('\f', n);
else if (c == 'r') return ParseRes.res('\r', n); else if (c == 'r') return ParseRes.res('\r', n);
else if (c == '0') { else if (c == '0') {
if (src.is(i + n, Parsing::isDigit)) return ParseRes.error(src.loc(i), "Octal escape sequences are not allowed"); if (src.is(i + n, Parsing::isDigit)) return ParseRes.error(src.loc(i), "Octal escape sequences are not allowed");
else return ParseRes.res('\0', n); else return ParseRes.res('\0', n);
} }
else if (c >= '1' && c <= '9') return ParseRes.error(src.loc(i), "Octal escape sequences are not allowed"); else if (c >= '1' && c <= '9') return ParseRes.error(src.loc(i), "Octal escape sequences are not allowed");
else if (c == 'x') { else if (c == 'x') {
var newC = 0; var newC = 0;
for (var j = 0; j < 2; j++) { for (var j = 0; j < 2; j++) {
if (i + n >= src.size()) return ParseRes.error(src.loc(i), "Invalid hexadecimal escape sequence."); if (i + n >= src.size()) return ParseRes.error(src.loc(i), "Invalid hexadecimal escape sequence");
int val = fromHex(src.at(i + n)); int val = fromHex(src.at(i + n));
if (val == -1) throw new SyntaxException(src.loc(i + n), "Invalid hexadecimal escape sequence."); if (val == -1) throw new SyntaxException(src.loc(i + n), "Invalid hexadecimal escape sequence");
n++; n++;
newC = (newC << 4) | val; newC = (newC << 4) | val;
} }
return ParseRes.res((char)newC, n); return ParseRes.res((char)newC, n);
} }
else if (c == 'u') { else if (c == 'u') {
var newC = 0; var newC = 0;
for (var j = 0; j < 4; j++) { for (var j = 0; j < 4; j++) {
if (i + n >= src.size()) return ParseRes.error(src.loc(i), "Invalid Unicode escape sequence"); if (i + n >= src.size()) return ParseRes.error(src.loc(i), "Invalid Unicode escape sequence");
int val = fromHex(src.at(i + n)); int val = fromHex(src.at(i + n));
if (val == -1) throw new SyntaxException(src.loc(i + n), "Invalid Unicode escape sequence"); if (val == -1) throw new SyntaxException(src.loc(i + n), "Invalid Unicode escape sequence");
n++; n++;
newC = (newC << 4) | val; newC = (newC << 4) | val;
} }
return ParseRes.res((char)newC, n); return ParseRes.res((char)newC, n);
} }
else if (c == '\n') return ParseRes.res(null, n); else if (c == '\n') return ParseRes.res(null, n);
} else n--;
}
return ParseRes.res(src.at(i + n), n + 1);
} return ParseRes.res(src.at(i + n), n + 1);
}
public static ParseRes<String> parseIdentifier(Source src, int i) {
var n = skipEmpty(src, i); public static ParseRes<String> parseIdentifier(Source src, int i) {
var res = new StringBuilder(); var n = skipEmpty(src, i);
var first = true; var res = new StringBuilder();
var first = true;
while (true) {
if (i + n > src.size()) break; while (true) {
char c = src.at(i + n, '\0'); if (i + n > src.size()) break;
char c = src.at(i + n, '\0');
if (first && Parsing.isDigit(c)) break;
if (!Character.isLetterOrDigit(c) && c != '_' && c != '$') break; if (first && Parsing.isDigit(c)) break;
res.append(c); if (!Character.isLetterOrDigit(c) && c != '_' && c != '$') break;
n++; res.append(c);
first = false; n++;
} first = false;
}
if (res.length() <= 0) return ParseRes.failed();
else return ParseRes.res(res.toString(), n); if (res.length() <= 0) return ParseRes.failed();
} else return ParseRes.res(res.toString(), n);
public static ParseRes<String> parseIdentifier(Source src, int i, String test) { }
var n = skipEmpty(src, i); public static ParseRes<String> parseIdentifier(Source src, int i, String test) {
var res = new StringBuilder(); var n = skipEmpty(src, i);
var first = true; var res = new StringBuilder();
var first = true;
while (true) {
if (i + n > src.size()) break; while (true) {
char c = src.at(i + n, '\0'); if (i + n > src.size()) break;
char c = src.at(i + n, '\0');
if (first && Parsing.isDigit(c)) break;
if (!Character.isLetterOrDigit(c) && c != '_' && c != '$') break; if (first && Parsing.isDigit(c)) break;
res.append(c); if (!Character.isLetterOrDigit(c) && c != '_' && c != '$') break;
n++; res.append(c);
first = false; n++;
} first = false;
}
if (res.length() <= 0) return ParseRes.failed();
else if (test == null || res.toString().equals(test)) return ParseRes.res(res.toString(), n); if (res.length() <= 0) return ParseRes.failed();
else return ParseRes.failed(); else if (test == null || res.toString().equals(test)) return ParseRes.res(res.toString(), n);
} else return ParseRes.failed();
public static boolean isIdentifier(Source src, int i, String test) { }
return parseIdentifier(src, i, test).isSuccess(); public static boolean isIdentifier(Source src, int i, String test) {
} return parseIdentifier(src, i, test).isSuccess();
}
public static ParseRes<String> parseOperator(Source src, int i, String op) {
var n = skipEmpty(src, i); public static ParseRes<String> parseOperator(Source src, int i, String op) {
var n = skipEmpty(src, i);
if (src.is(i + n, op)) return ParseRes.res(op, n + op.length());
else return ParseRes.failed(); if (src.is(i + n, op)) return ParseRes.res(op, n + op.length());
} else return ParseRes.failed();
}
private static ParseRes<Double> parseHex(Source src, int i) {
int n = 0; private static ParseRes<Double> parseHex(Source src, int i) {
double res = 0; int n = 0;
double res = 0;
while (true) {
int digit = Parsing.fromHex(src.at(i + n, '\0')); while (true) {
if (digit < 0) { int digit = Parsing.fromHex(src.at(i + n, '\0'));
if (n <= 0) return ParseRes.failed(); if (digit < 0) {
else return ParseRes.res(res, n); if (n <= 0) return ParseRes.failed();
} else return ParseRes.res(res, n);
n++; }
n++;
res *= 16;
res += digit; res *= 16;
} res += digit;
} }
private static ParseRes<Double> parseOct(Source src, int i) { }
int n = 0; private static ParseRes<Double> parseOct(Source src, int i) {
double res = 0; int n = 0;
double res = 0;
while (true) {
int digit = src.at(i + n, '\0') - '0'; while (true) {
if (digit < 0 || digit > 9) break; int digit = src.at(i + n, '\0') - '0';
if (digit > 7) return ParseRes.error(src.loc(i + n), "Digits in octal literals must be from 0 to 7, encountered " + digit); if (digit < 0 || digit > 9) break;
if (digit > 7) return ParseRes.error(src.loc(i + n), "Digits in octal literals must be from 0 to 7, encountered " + digit);
if (digit < 0) {
if (n <= 0) return ParseRes.failed(); if (digit < 0) {
else return ParseRes.res(res, n); if (n <= 0) return ParseRes.failed();
} else return ParseRes.res(res, n);
n++; }
n++;
res *= 8;
res += digit; res *= 8;
} res += digit;
}
return ParseRes.res(res, n);
} return ParseRes.res(res, n);
}
public static ParseRes<String> parseString(Source src, int i) { private static ParseRes<Double> parseBin(Source src, int i) {
var n = skipEmpty(src, i); int n = 0;
double res = 0;
char quote;
while (true) {
if (src.is(i + n, '\'')) quote = '\''; int digit = src.at(i + n, '\0') - '0';
else if (src.is(i + n, '"')) quote = '"'; if (digit < 0 || digit > 9) break;
else return ParseRes.failed(); if (digit > 1) return ParseRes.error(src.loc(i + n), "Digits in binary literals must be from 0 to 1, encountered " + digit);
n++;
if (digit < 0) {
var res = new StringBuilder(); if (n <= 0) return ParseRes.failed();
else return ParseRes.res(res, n);
while (true) { }
if (i + n >= src.size()) return ParseRes.error(src.loc(i + n), "Unterminated string literal"); n++;
if (src.is(i + n, quote)) {
n++; res *= 2;
return ParseRes.res(res.toString(), n); res += digit;
} }
var charRes = parseChar(src, i + n); return ParseRes.res(res, n);
if (!charRes.isSuccess()) return charRes.chainError(src.loc(i + n), "Invalid character"); }
n += charRes.n;
public static ParseRes<String> parseString(Source src, int i) {
if (charRes.result != null) res.append(charRes.result); var n = skipEmpty(src, i);
}
} char quote;
public static ParseRes<Double> parseNumber(Source src, int i, boolean withMinus) {
var n = skipEmpty(src, i); if (src.is(i + n, '\'')) quote = '\'';
else if (src.is(i + n, '"')) quote = '"';
double whole = 0; else return ParseRes.failed();
double fract = 0; n++;
long exponent = 0;
boolean parsedAny = false; var res = new StringBuilder();
boolean negative = false;
while (true) {
if (withMinus && src.is(i + n, "-")) { if (i + n >= src.size()) return ParseRes.error(src.loc(i + n), "Unterminated string literal");
negative = true; if (src.is(i + n, quote)) {
n++; n++;
} return ParseRes.res(res.toString(), n);
}
if (src.is(i + n, "0x") || src.is(i + n, "0X")) {
n += 2; var charRes = parseChar(src, i + n);
if (!charRes.isSuccess()) return charRes.chainError(src.loc(i + n), "Invalid character");
var res = parseHex(src, i + n); n += charRes.n;
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete hexadecimal literal");
n += res.n; if (charRes.result != null) res.append(charRes.result);
}
if (negative) return ParseRes.res(-res.result, n); }
else return ParseRes.res(res.result, n); public static ParseRes<Double> parseNumber(Source src, int i, boolean withMinus) {
} var n = skipEmpty(src, i);
else if (src.is(i + n, "0o") || src.is(i + n, "0O")) {
n += 2; double whole = 0;
double fract = 0;
var res = parseOct(src, i + n); long exponent = 0;
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete octal literal"); boolean parsedAny = false;
n += res.n; boolean negative = false;
if (negative) return ParseRes.res(-res.result, n); if (withMinus && src.is(i + n, "-")) {
else return ParseRes.res(res.result, n); negative = true;
} n++;
else if (src.is(i + n, '0')) { }
n++;
parsedAny = true; if (src.is(i + n, "0x") || src.is(i + n, "0X")) {
if (src.is(i + n, Parsing::isDigit)) return ParseRes.error(src.loc(i + n), "Decimals with leading zeroes are not allowed"); n += 2;
}
var res = parseHex(src, i + n);
while (src.is(i + n, Parsing::isDigit)) { if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete hexadecimal literal");
parsedAny = true; n += res.n;
whole *= 10;
whole += src.at(i + n++) - '0'; if (negative) return ParseRes.res(-res.result, n);
} else return ParseRes.res(res.result, n);
}
if (src.is(i + n, '.')) { else if (src.is(i + n, "0o") || src.is(i + n, "0O")) {
parsedAny = true; n += 2;
n++;
var res = parseOct(src, i + n);
while (src.is(i + n, Parsing::isDigit)) { if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete octal literal");
fract += src.at(i + n++) - '0'; n += res.n;
fract /= 10;
} if (negative) return ParseRes.res(-res.result, n);
} else return ParseRes.res(res.result, n);
}
if (src.is(i + n, 'e') || src.is(i + n, 'E')) { else if (src.is(i + n, "0b") || src.is(i + n, "0B")) {
n++; n += 2;
parsedAny = true;
boolean expNegative = false; var res = parseBin(src, i + n);
boolean parsedE = false; if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete binary literal");
n += res.n;
if (src.is(i + n, '-')) {
expNegative = true; if (negative) return ParseRes.res(-res.result, n);
n++; else return ParseRes.res(res.result, n);
} }
else if (src.is(i + n, '+')) n++; else if (src.is(i + n, '0')) {
n++;
while (src.is(i + n, Parsing::isDigit)) { parsedAny = true;
parsedE = true; if (src.is(i + n, Parsing::isDigit)) return ParseRes.error(src.loc(i + n), "Decimals with leading zeroes are not allowed");
exponent *= 10; }
if (expNegative) exponent -= src.at(i + n++) - '0'; while (src.is(i + n, Parsing::isDigit)) {
else exponent += src.at(i + n++) - '0'; parsedAny = true;
} whole *= 10;
whole += src.at(i + n++) - '0';
if (!parsedE) return ParseRes.error(src.loc(i + n), "Incomplete number exponent"); }
}
if (src.is(i + n, '.')) {
if (!parsedAny) { parsedAny = true;
if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus"); n++;
return ParseRes.failed();
} while (src.is(i + n, Parsing::isDigit)) {
else if (negative) return ParseRes.res(-(whole + fract) * NumberNode.power(10, exponent), n); fract += src.at(i + n++) - '0';
else return ParseRes.res((whole + fract) * NumberNode.power(10, exponent), n); fract /= 10;
} }
public static ParseRes<Double> parseFloat(Source src, int i, boolean withMinus) { }
var n = skipEmpty(src, i);
if (parsedAny && src.is(i + n, 'e') || src.is(i + n, 'E')) {
double whole = 0; n++;
double fract = 0; boolean expNegative = false;
long exponent = 0; boolean parsedE = false;
boolean parsedAny = false;
boolean negative = false; if (src.is(i + n, '-')) {
expNegative = true;
if (withMinus && src.is(i + n, "-")) { n++;
negative = true; }
n++; else if (src.is(i + n, '+')) n++;
}
while (src.is(i + n, Parsing::isDigit)) {
while (src.is(i + n, Parsing::isDigit)) { parsedE = true;
parsedAny = true; exponent *= 10;
whole *= 10;
whole += src.at(i + n++) - '0'; if (expNegative) exponent -= src.at(i + n++) - '0';
} else exponent += src.at(i + n++) - '0';
}
if (src.is(i + n, '.')) {
parsedAny = true; if (!parsedE) return ParseRes.error(src.loc(i + n), "Incomplete number exponent");
n++; }
while (src.is(i + n, Parsing::isDigit)) { if (!parsedAny) {
fract += src.at(i + n++) - '0'; if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus");
fract /= 10; return ParseRes.failed();
} }
} else if (negative) return ParseRes.res(-(whole + fract) * NumberNode.power(10, exponent), n);
else return ParseRes.res((whole + fract) * NumberNode.power(10, exponent), n);
if (src.is(i + n, 'e') || src.is(i + n, 'E')) { }
n++; public static ParseRes<Double> parseFloat(Source src, int i, boolean withMinus) {
parsedAny = true; var n = skipEmpty(src, i);
boolean expNegative = false;
boolean parsedE = false; double whole = 0;
double fract = 0;
if (src.is(i + n, '-')) { long exponent = 0;
expNegative = true; boolean parsedAny = false;
n++; boolean negative = false;
}
else if (src.is(i + n, '+')) n++; if (withMinus && src.is(i + n, "-")) {
negative = true;
while (src.is(i + n, Parsing::isDigit)) { n++;
parsedE = true; }
exponent *= 10;
while (src.is(i + n, Parsing::isDigit)) {
if (expNegative) exponent -= src.at(i + n++) - '0'; parsedAny = true;
else exponent += src.at(i + n++) - '0'; whole *= 10;
} whole += src.at(i + n++) - '0';
}
if (!parsedE) return ParseRes.error(src.loc(i + n), "Incomplete number exponent");
} if (src.is(i + n, '.')) {
parsedAny = true;
if (!parsedAny) { n++;
if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus");
return ParseRes.failed(); while (src.is(i + n, Parsing::isDigit)) {
} fract += src.at(i + n++) - '0';
else if (negative) return ParseRes.res(-(whole + fract) * NumberNode.power(10, exponent), n); fract /= 10;
else return ParseRes.res((whole + fract) * NumberNode.power(10, exponent), n); }
} }
public static ParseRes<Double> parseInt(Source src, int i, String alphabet, boolean withMinus) {
var n = skipEmpty(src, i); if (src.is(i + n, 'e') || src.is(i + n, 'E')) {
n++;
double result = 0; parsedAny = true;
boolean parsedAny = false; boolean expNegative = false;
boolean negative = false; boolean parsedE = false;
if (withMinus && src.is(i + n, "-")) { if (src.is(i + n, '-')) {
negative = true; expNegative = true;
n++; n++;
} }
else if (src.is(i + n, '+')) n++;
if (alphabet == null && src.is(i + n, "0x") || src.is(i + n, "0X")) {
n += 2; while (src.is(i + n, Parsing::isDigit)) {
parsedE = true;
var res = parseHex(src, i); exponent *= 10;
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete hexadecimal literal");
n += res.n; if (expNegative) exponent -= src.at(i + n++) - '0';
else exponent += src.at(i + n++) - '0';
if (negative) return ParseRes.res(-res.result, n); }
else return ParseRes.res(res.result, n);
} if (!parsedE) return ParseRes.error(src.loc(i + n), "Incomplete number exponent");
}
while (true) {
var digit = alphabet.indexOf(Character.toLowerCase(src.at(i + n))); if (!parsedAny) {
if (digit < 0) break; if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus");
return ParseRes.failed();
parsedAny = true; }
result += digit; else if (negative) return ParseRes.res(-(whole + fract) * NumberNode.power(10, exponent), n);
result *= alphabet.length(); else return ParseRes.res((whole + fract) * NumberNode.power(10, exponent), n);
} }
public static ParseRes<Double> parseInt(Source src, int i, String alphabet, boolean withMinus) {
if (!parsedAny) { var n = skipEmpty(src, i);
if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus");
return ParseRes.failed(); double result = 0;
} boolean parsedAny = false;
else if (negative) return ParseRes.res(-result, n); boolean negative = false;
else return ParseRes.res(-result, n);
} if (withMinus && src.is(i + n, "-")) {
negative = true;
n++;
}
if (alphabet == null && src.is(i + n, "0x") || src.is(i + n, "0X")) {
n += 2;
var res = parseHex(src, i);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete hexadecimal literal");
n += res.n;
if (negative) return ParseRes.res(-res.result, n);
else return ParseRes.res(res.result, n);
}
while (true) {
var digit = alphabet.indexOf(Character.toLowerCase(src.at(i + n, '\0')));
if (digit < 0) break;
parsedAny = true;
result *= alphabet.length();
result += digit;
n++;
}
if (!parsedAny) {
if (negative) return ParseRes.error(src.loc(i + n), "Expected number immediatly after minus");
return ParseRes.failed();
}
else if (negative) return ParseRes.res(-result, n);
else return ParseRes.res(result, n);
}
} }

View File

@@ -5,71 +5,71 @@ import java.util.function.Predicate;
import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.common.environment.Environment;
public class Source { public class Source {
public final Environment env; public final Environment env;
public final Filename filename; public final Filename filename;
public final String src; public final String src;
private int[] lineStarts; private int[] lineStarts;
public Location loc(int offset) { public Location loc(int offset) {
return new SourceLocation(filename, lineStarts, offset); return new SourceLocation(filename, lineStarts, offset);
} }
public boolean is(int i, char c) { public boolean is(int i, char c) {
return i >= 0 && i < src.length() && src.charAt(i) == c; return i >= 0 && i < src.length() && src.charAt(i) == c;
} }
public boolean is(int i, String src) { public boolean is(int i, String src) {
if (i < 0 || i + src.length() > size()) return false; if (i < 0 || i + src.length() > size()) return false;
for (int j = 0; j < src.length(); j++) { for (int j = 0; j < src.length(); j++) {
if (at(i + j) != src.charAt(j)) return false; if (at(i + j) != src.charAt(j)) return false;
} }
return true; return true;
} }
public boolean is(int i, Predicate<Character> predicate) { public boolean is(int i, Predicate<Character> predicate) {
if (i < 0 || i >= src.length()) return false; if (i < 0 || i >= src.length()) return false;
return predicate.test(at(i)); return predicate.test(at(i));
} }
public char at(int i) { public char at(int i) {
return src.charAt(i); return src.charAt(i);
} }
public char at(int i, char defaultVal) { public char at(int i, char defaultVal) {
if (i < 0 || i >= src.length()) return defaultVal; if (i < 0 || i >= src.length()) return defaultVal;
else return src.charAt(i); else return src.charAt(i);
} }
public int size() { public int size() {
return src.length(); return src.length();
} }
public String slice(int start, int end) { public String slice(int start, int end) {
return src.substring(start, end); return src.substring(start, end);
} }
public Source(Environment env, Filename filename, String src) { public Source(Environment env, Filename filename, String src) {
if (env == null) this.env = new Environment(); if (env == null) this.env = new Environment();
else this.env = env; else this.env = env;
this.filename = filename; this.filename = filename;
this.src = src; this.src = src;
int n = 1; int n = 1;
lineStarts = new int[16]; lineStarts = new int[16];
lineStarts[0] = 0; lineStarts[0] = 0;
for (int i = src.indexOf("\n"); i > 0; i = src.indexOf("\n", i + 1)) { for (int i = src.indexOf("\n"); i > 0; i = src.indexOf("\n", i + 1)) {
if (n >= lineStarts.length) { if (n >= lineStarts.length) {
var newArr = new int[lineStarts.length * 2]; var newArr = new int[lineStarts.length * 2];
System.arraycopy(lineStarts, 0, newArr, 0, n); System.arraycopy(lineStarts, 0, newArr, 0, n);
lineStarts = newArr; lineStarts = newArr;
} }
lineStarts[n++] = i + 1; lineStarts[n++] = i + 1;
} }
var newArr = new int[n]; var newArr = new int[n];
System.arraycopy(lineStarts, 0, newArr, 0, n); System.arraycopy(lineStarts, 0, newArr, 0, n);
lineStarts = newArr; lineStarts = newArr;
} }
public Source(String src) { public Source(String src) {
this(null, null, src); this(null, null, src);
} }
} }

View File

@@ -3,64 +3,64 @@ package me.topchetoeu.jscript.common.parsing;
import java.util.Objects; import java.util.Objects;
public class SourceLocation extends Location { public class SourceLocation extends Location {
private int[] lineStarts; private int[] lineStarts;
private int line; private int line;
private int start; private int start;
private final Filename filename; private final Filename filename;
private final int offset; private final int offset;
private void update() { private void update() {
if (lineStarts == null) return; if (lineStarts == null) return;
int a = 0; int a = 0;
int b = lineStarts.length; int b = lineStarts.length;
while (true) { while (true) {
if (a + 1 >= b) break; if (a + 1 >= b) break;
var mid = -((-a - b) >> 1); var mid = -((-a - b) >> 1);
var el = lineStarts[mid]; var el = lineStarts[mid];
if (el < offset) a = mid; if (el < offset) a = mid;
else if (el > offset) b = mid; else if (el > offset) b = mid;
else { else {
this.line = mid; this.line = mid;
this.start = 0; this.start = 0;
this.lineStarts = null; this.lineStarts = null;
return; return;
} }
} }
this.line = a; this.line = a;
this.start = offset - lineStarts[a]; this.start = offset - lineStarts[a];
this.lineStarts = null; this.lineStarts = null;
return; return;
} }
@Override public Filename filename() { return filename; } @Override public Filename filename() { return filename; }
@Override public int line() { @Override public int line() {
update(); update();
return line; return line;
} }
@Override public int start() { @Override public int start() {
update(); update();
return start; return start;
} }
@Override public int hashCode() { @Override public int hashCode() {
return Objects.hash(offset); return Objects.hash(offset);
} }
@Override public int compareTo(Location other) { @Override public int compareTo(Location other) {
if (other instanceof SourceLocation srcLoc) return Integer.compare(offset, srcLoc.offset); if (other instanceof SourceLocation srcLoc) return Integer.compare(offset, srcLoc.offset);
else return super.compareTo(other); else return super.compareTo(other);
} }
@Override public boolean equals(Object obj) { @Override public boolean equals(Object obj) {
if (obj instanceof SourceLocation other) return this.offset == other.offset; if (obj instanceof SourceLocation other) return this.offset == other.offset;
else return super.equals(obj); else return super.equals(obj);
} }
public SourceLocation(Filename filename, int[] lineStarts, int offset) { public SourceLocation(Filename filename, int[] lineStarts, int offset) {
this.filename = filename; this.filename = filename;
this.lineStarts = lineStarts; this.lineStarts = lineStarts;
this.offset = offset; this.offset = offset;
} }
} }

View File

@@ -1,244 +0,0 @@
package me.topchetoeu.jscript.compilation;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.SyntaxException;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.environment.Environment;
import me.topchetoeu.jscript.common.environment.Key;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.members.FieldMemberNode;
import me.topchetoeu.jscript.compilation.members.Member;
import me.topchetoeu.jscript.compilation.members.MethodMemberNode;
import me.topchetoeu.jscript.compilation.members.PropertyMemberNode;
import me.topchetoeu.jscript.compilation.scope.FunctionScope;
public abstract class ClassNode extends FunctionNode {
public static final class ClassBody {
public final List<Member> staticMembers;
public final List<FieldMemberNode> protoFields;
public final List<Member> protoMembers;
public final Parameters constructorParameters;
public final CompoundNode constructorBody;
public final Node superExpr;
public final boolean hasConstr;
public ClassBody(
List<Member> staticMembers, List<FieldMemberNode> protoFields, List<Member> protoMembers,
Parameters constructorParameters, CompoundNode constructorBody,
Node superExpr, boolean hasConstr
) {
this.staticMembers = staticMembers;
this.protoFields = protoFields;
this.protoMembers = protoMembers;
this.constructorParameters = constructorParameters;
this.constructorBody = constructorBody;
this.superExpr = superExpr;
this.hasConstr = hasConstr;
}
}
public static final Key<Environment> CLASS_ROOT = Key.of();
public static final Key<Consumer<CompileResult>> SUPER = Key.of();
public static final Key<Consumer<CompileResult>> SUPER_PROTO = Key.of();
public static final Key<Consumer<CompileResult>> SUPER_CONSTR = Key.of();
public static final Key<Consumer<CompileResult>> ON_SUPER_CALL = Key.of();
public final ClassBody body;
public final String name;
@Override public String name() { return name; }
public void compileStatic(CompileResult target) {
for (var member : body.staticMembers) {
member.compile(target, true, false);
}
}
public void compilePrototype(CompileResult target) {
if (body.protoMembers.size() > 0) {
target.add(Instruction.dup());
target.add(Instruction.loadMember("prototype"));
for (var i = 0; i < body.protoMembers.size() - 1; i++) {
body.protoMembers.get(i).compile(target, true, false);
}
body.protoMembers.get(body.protoMembers.size() - 1).compile(target, false, false);
}
}
private void compileFieldInits(CompileResult target) {
for (var member : body.protoFields) {
target.add(Instruction.loadThis());
member.compile(target, false, true);
}
}
@Override protected void compilePreBody(CompileResult target) {
if (target.env.hasNotNull(SUPER_PROTO)) {
if (!body.hasConstr) {
throw new SyntaxException(loc(), "Default constructors in derived classes not supported");
// compileFieldInits(target);
}
}
else compileFieldInits(target);
}
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
if (body.superExpr == null) {
var id = target.addChild(compileBody(target, name, null));
target.add(_i -> Instruction.loadFunc(id, false, true, false, false, name, captures(id, target)));
compileStatic(target);
compilePrototype(target);
}
else {
var subtarget = target.subtarget().rootEnvironment(JavaScript.COMPILE_ROOT);
subtarget.scope.singleEntry = true;
subtarget.beginScope();
var protoVar = target.scope.defineTemp();
var constrVar = target.scope.defineTemp();
subtarget.env.add(SUPER_PROTO, t -> {
var i = t.scope.get(protoVar, false);
t.add(_i -> i.index().toGet());
});
subtarget.env.add(SUPER_CONSTR, t -> {
var i = t.scope.get(constrVar, false);
t.add(_i -> i.index().toGet());
});
var staticTarget = subtarget.subEnvironment();
staticTarget.env.add(SUPER, subtarget.env.get(SUPER_CONSTR));
staticTarget.env.add(CLASS_ROOT, staticTarget.env);
var protoTarget = subtarget.subEnvironment();
protoTarget.env.add(SUPER, subtarget.env.get(SUPER_PROTO));
protoTarget.env.add(CLASS_ROOT, protoTarget.env);
var constrEnv = subtarget.env.child();
constrEnv.add(SUPER, subtarget.env.get(SUPER_PROTO));
constrEnv.add(ON_SUPER_CALL, this::compileFieldInits);
constrEnv.add(CLASS_ROOT, constrEnv);
var id = target.addChild(compileBody(constrEnv, new FunctionScope(subtarget.scope), false, name, null));
target.add(_i -> Instruction.loadFunc(id, false, true, false, true, name, captures(id, target)));
body.superExpr.compile(target, true);
target.add(Instruction.extend());
target.add(Instruction.dup(1, 0));
target.add(Instruction.loadMember("prototype"));
target.add(_i -> protoVar.index().toInit());
target.add(_i -> constrVar.index().toInit());
compileStatic(staticTarget);
compilePrototype(protoTarget);
subtarget.endScope();
}
}
public ClassNode(Location loc, Location end, String name, ClassBody body) {
super(loc, end, body.constructorParameters, body.constructorBody);
this.name = name;
this.body = body;
}
public static ParseRes<Member> parseMember(Source src, int i) {
return ParseRes.first(src, i,
PropertyMemberNode::parse,
FieldMemberNode::parseClass,
MethodMemberNode::parse
);
}
public static ParseRes<ClassBody> parseBody(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
ParseRes<Node> superExpr = ParseRes.failed();
if (Parsing.isIdentifier(src, i + n, "extends")) {
n += 7;
superExpr = JavaScript.parseExpression(src, i + n, 14);
if (!superExpr.isSuccess()) return superExpr.chainError(src.loc(i + n), "Expected an expression after 'extends'");
n += superExpr.n;
n += Parsing.skipEmpty(src, i + n);
}
if (!src.is(i + n, "{")) return ParseRes.error(src.loc(i + n), "Expected a class body");
n++;
n += Parsing.skipEmpty(src, i + n);
var fields = new LinkedList<FieldMemberNode>();
var members = new LinkedList<Member>();
var statics = new LinkedList<Member>();
var params = new Parameters(new ArrayList<>());
var body = new CompoundNode(loc, false);
var hasConstr = false;
if (src.is(i + n, "}")) {
n++;
return ParseRes.res(new ClassBody(statics, fields, members, params, body, superExpr.result, false), n);
}
while (true) {
ParseRes<Member> prop = parseMember(src, i + n);
if (prop.isSuccess()) {
n += prop.n;
if (prop.result instanceof FieldMemberNode field) fields.add(field);
else if (prop.result instanceof MethodMemberNode method && method.name().equals("constructor")) {
if (hasConstr) return ParseRes.error(loc, "A class may only have one constructor");
params = method.params;
body = method.body;
hasConstr = true;
}
else members.add(prop.result);
}
else if (Parsing.isIdentifier(src, i + n, "static")) {
n += 6;
var staticProp = parseMember(src, i + n);
if (!staticProp.isSuccess()) {
if (prop.isError()) return prop.chainError();
else return staticProp.chainError(src.loc(i + n), "Expected a member after 'static' keyword");
}
n += staticProp.n;
statics.add(staticProp.result);
}
else {
var end = JavaScript.parseStatementEnd(src, i + n);
if (end.isSuccess()) n += end.n;
else return ParseRes.error(src.loc(i + n), "Expected a member, end of statement or a closing colon");
}
n += Parsing.skipEmpty(src, i + n);
if (src.is(i + n, "}")) {
n++;
break;
}
// else return ParseRes.error(src.loc(i + n), "Expected a comma or a closing brace.");
}
return ParseRes.res(new ClassBody(statics, fields, members, params, body, superExpr.result, hasConstr), n);
}
// public FunctionStatementNode(Location loc, Location end, Parameters params, CompoundNode body, String name) {
// super(loc, end, params, body);
// this.name = name;
// }
}

View File

@@ -1,40 +0,0 @@
package me.topchetoeu.jscript.compilation;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
public class ClassStatementNode extends ClassNode {
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
super.compile(target, pollute, name, bp);
var i = target.scope.define(DeclarationType.LET, name(), loc());
target.add(_i -> i.index().toInit());
if (pollute) target.add(Instruction.pushUndefined());
}
public ClassStatementNode(Location loc, Location end, String name, ClassBody body) {
super(loc, end, name, body);
}
public static ParseRes<ClassStatementNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!Parsing.isIdentifier(src, i + n, "class")) return ParseRes.failed();
n += 5;
var name = Parsing.parseIdentifier(src, i + n);
if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a class name");
n += name.n;
var body = parseBody(src, i + n);
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a class body");
n += body.n;
return ParseRes.res(new ClassStatementNode(loc, src.loc(i + n), name.result, body.result), n);
}
}

View File

@@ -1,10 +1,11 @@
package me.topchetoeu.jscript.compilation; package me.topchetoeu.jscript.compilation;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.function.Consumer;
import java.util.function.IntFunction;
import me.topchetoeu.jscript.common.FunctionBody; import me.topchetoeu.jscript.common.FunctionBody;
import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Instruction;
@@ -14,166 +15,127 @@ import me.topchetoeu.jscript.common.environment.Key;
import me.topchetoeu.jscript.common.mapping.FunctionMap; import me.topchetoeu.jscript.common.mapping.FunctionMap;
import me.topchetoeu.jscript.common.mapping.FunctionMap.FunctionMapBuilder; import me.topchetoeu.jscript.common.mapping.FunctionMap.FunctionMapBuilder;
import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.compilation.scope.Scope; import me.topchetoeu.jscript.compilation.control.TryNode;
import me.topchetoeu.jscript.compilation.scope.FunctionScope;
import me.topchetoeu.jscript.compilation.scope.Variable;
public final class CompileResult { public final class CompileResult {
public static final class ChildData { public static final Key<Void> DEBUG_LOG = new Key<>();
public final int id;
public final CompileResult result;
public ChildData(int id, CompileResult result) { public final List<Instruction> instructions;
this.result = result; public final List<CompileResult> children;
this.id = id; public final Map<FunctionNode, CompileResult> childrenMap = new HashMap<>();
} public final Map<FunctionNode, Integer> childrenIndices = new HashMap<>();
} public final FunctionMapBuilder map;
public final Environment env;
public int length;
public final FunctionScope scope;
public final Map<TryNode, Variable> catchBindings = new HashMap<>();
public final List<IntFunction<Instruction>> instructions; public int temp() {
public final List<CompileResult> children; instructions.add(null);
public final FunctionMapBuilder map; return instructions.size() - 1;
public final Environment env; }
public int length;
public Runnable buildTask = () -> {
throw new IllegalStateException("Compile result is not ready to be built");
};
public final Scope scope;
public int temp() { public CompileResult add(Instruction instr) {
instructions.add(null); instructions.add(instr);
return instructions.size() - 1; return this;
} }
public CompileResult set(int i, Instruction instr) {
instructions.set(i, instr);
return this;
}
public CompileResult add(Instruction instr) { public int size() { return instructions.size(); }
instructions.add(i -> instr);
return this;
}
public CompileResult add(IntFunction<Instruction> instr) {
instructions.add(instr);
return this;
}
public CompileResult set(int i, Instruction instr) {
instructions.set(i, _i -> instr);
return this;
}
public CompileResult set(int i, IntFunction<Instruction>instr) {
instructions.set(i, instr);
return this;
}
public int size() { return instructions.size(); }
public void setDebug(Location loc, BreakpointType type) { public void setDebug(Location loc, BreakpointType type) {
map.setDebug(loc, type); map.setDebug(loc, type);
} }
public void setLocation(int i, Location loc) { public void setLocation(int i, Location loc) {
map.setLocation(i, loc); map.setLocation(i, loc);
} }
public void setLocationAndDebug(int i, Location loc, BreakpointType type) { public void setLocationAndDebug(int i, Location loc, BreakpointType type) {
map.setLocationAndDebug(i, loc, type); map.setLocationAndDebug(i, loc, type);
} }
public void setDebug(BreakpointType type) { public void setDebug(BreakpointType type) {
setDebug(map.last(), type); setDebug(map.last(), type);
} }
public void setLocation(Location type) { public void setLocation(Location type) {
setLocation(instructions.size() - 1, type); setLocation(instructions.size() - 1, type);
} }
public void setLocationAndDebug(Location loc, BreakpointType type) { public void setLocationAndDebug(Location loc, BreakpointType type) {
setLocationAndDebug(instructions.size() - 1, loc, type); setLocationAndDebug(instructions.size() - 1, loc, type);
} }
public void beginScope() { public CompileResult addChild(FunctionNode node, CompileResult res) {
// for (var cap : scope.capturables()) { this.children.add(res);
// add(_i -> Instruction.capInit(cap.index().index)); this.childrenMap.put(node, res);
// } this.childrenIndices.put(node, this.children.size() - 1);
} return res;
public void reallocScope() { }
for (var cap : scope.capturables()) {
add(_i -> cap.index().toGet());
add(_i -> Instruction.capFree(cap.index().index));
add(_i -> cap.index().toInit());
}
scope.end(); public Instruction[] instructions() {
} return instructions.toArray(new Instruction[0]);
public void endScope() { }
for (var cap : scope.capturables()) {
add(_i -> Instruction.capFree(cap.index().index));
}
for (var var : scope.locals()) {
add(_i -> Instruction.varFree(var.index().index));
}
scope.end(); public FunctionMap map(Function<Location, Location> mapper) {
} return map.map(mapper).build(scope.localNames(), scope.capturableNames(), scope.captureNames());
}
public FunctionMap map() {
return map.build(scope.localNames(), scope.capturableNames(), scope.captureNames());
}
public FunctionBody body() {
var builtChildren = new FunctionBody[children.size()];
for (var i = 0; i < children.size(); i++) builtChildren[i] = children.get(i).body();
public int addChild(CompileResult res) { var instrRes = instructions();
this.children.add(res);
return this.children.size() - 1;
}
public Instruction[] instructions() { if (env.has(DEBUG_LOG)) {
var res = new Instruction[instructions.size()]; System.out.println("================= BODY =================");
var i = 0; System.out.println("LOCALS: " + scope.localsCount());
for (var suppl : instructions) { System.out.println("CAPTURABLES: " + scope.capturablesCount());
res[i] = suppl.apply(i); System.out.println("CAPTURES: " + scope.capturesCount());
i++;
}
return res;
}
public FunctionMap map() { for (var instr : instrRes) System.out.println(instr);
return map.build(scope); }
}
public FunctionBody body() {
var builtChildren = new FunctionBody[children.size()];
for (var i = 0; i < children.size(); i++) builtChildren[i] = children.get(i).body(); return new FunctionBody(
scope.localsCount(), scope.capturablesCount(), scope.capturesCount(),
length, instrRes, builtChildren
);
}
var instrRes = new Instruction[instructions.size()]; public CompileResult subtarget() {
var i = 0; return new CompileResult(env, new FunctionScope(scope), this);
}
for (var suppl : instructions) { public CompileResult setEnvironment(Environment env) {
instrRes[i] = suppl.apply(i); return new CompileResult(env, scope, this);
// System.out.println(instrRes[i]); }
i++; /**
} * Returns a compile result with a child of the environment that relates to the given key.
* In essence, this is used to create a compile result which is back at the root environment of the compilation
*/
public CompileResult rootEnvironment(Key<Environment> env) {
return new CompileResult(this.env.get(env).child(), scope, this);
}
public CompileResult subEnvironment() {
return new CompileResult(env.child(), scope, this);
}
return new FunctionBody( public CompileResult(Environment env, FunctionScope scope, int length) {
scope.localsCount(), scope.capturablesCount(), scope.capturesCount(), this.scope = scope;
length, instrRes, builtChildren this.instructions = new ArrayList<>();
); this.children = new LinkedList<>();
} this.map = FunctionMap.builder();
this.env = env;
public CompileResult subtarget() { this.length = length;
return new CompileResult(env, new Scope(scope), this); }
} private CompileResult(Environment env, FunctionScope scope, CompileResult parent) {
this.scope = scope;
public CompileResult setEnvironment(Environment env) { this.instructions = parent.instructions;
return new CompileResult(env, scope, this); this.children = parent.children;
} this.map = parent.map;
/** this.env = env;
* Returns a compile result with a child of the environment that relates to the given key. }
* In essence, this is used to create a compile result which is back at the root environment of the compilation
*/
public CompileResult rootEnvironment(Key<Environment> env) {
return new CompileResult(this.env.get(env).child(), scope, this);
}
public CompileResult subEnvironment() {
return new CompileResult(env.child(), scope, this);
}
public CompileResult(Environment env, Scope scope, int length, Consumer<CompileResult> task) {
this.scope = scope;
this.instructions = new ArrayList<>();
this.children = new LinkedList<>();
this.map = FunctionMap.builder();
this.env = env;
this.length = length;
this.buildTask = () -> task.accept(this);
}
private CompileResult(Environment env, Scope scope, CompileResult parent) {
this.scope = scope;
this.instructions = parent.instructions;
this.children = parent.children;
this.map = parent.map;
this.env = env;
}
} }

View File

@@ -13,117 +13,100 @@ import me.topchetoeu.jscript.common.parsing.Source;
public class CompoundNode extends Node { public class CompoundNode extends Node {
public final Node[] statements; public final Node[] statements;
public boolean hasScope; public Location end;
public Location end;
@Override public void resolve(CompileResult target) { @Override public void resolve(CompileResult target) {
for (var stm : statements) stm.resolve(target); for (var stm : statements) stm.resolve(target);
} }
@Override public void compileFunctions(CompileResult target) {
for (var stm : statements) stm.compileFunctions(target);
}
public void compile(CompileResult target, boolean pollute, boolean singleEntry, BreakpointType type) { public void compile(CompileResult target, boolean pollute, BreakpointType type) {
List<Node> statements = new ArrayList<Node>(); List<Node> statements = new ArrayList<Node>();
var subtarget = hasScope ? target.subtarget() : target; for (var stm : this.statements) {
if (hasScope) subtarget.beginScope(); if (stm instanceof FunctionStatementNode func) {
func.compile(target, false);
}
else statements.add(stm);
}
for (var stm : this.statements) { var polluted = false;
if (stm instanceof FunctionStatementNode func) {
func.compile(subtarget, false);
}
else statements.add(stm);
}
var polluted = false; for (var i = 0; i < statements.size(); i++) {
var stm = statements.get(i);
for (var i = 0; i < statements.size(); i++) { if (i != statements.size() - 1) stm.compile(target, false, BreakpointType.STEP_OVER);
var stm = statements.get(i); else stm.compile(target, polluted = pollute, BreakpointType.STEP_OVER);
}
if (i != statements.size() - 1) stm.compile(subtarget, false, BreakpointType.STEP_OVER); if (!polluted && pollute) {
else stm.compile(subtarget, polluted = pollute, BreakpointType.STEP_OVER); target.add(Instruction.pushUndefined());
} }
}
if (hasScope) subtarget.endScope(); public CompoundNode setEnd(Location loc) {
this.end = loc;
return this;
}
if (!polluted && pollute) { public CompoundNode(Location loc, Node ...statements) {
target.add(Instruction.pushUndefined()); super(loc);
} this.statements = statements;
} }
@Override public void compile(CompileResult target, boolean pollute, BreakpointType type) { public static ParseRes<CompoundNode> parseComma(Source src, int i, Node prev, int precedence) {
compile(target, pollute, true, type); if (precedence > 1) return ParseRes.failed();
}
public CompoundNode setEnd(Location loc) { var n = Parsing.skipEmpty(src, i);
this.end = loc; var loc = src.loc(i + n);
return this;
}
public CompoundNode(Location loc, boolean hasScope, Node ...statements) { if (!src.is(i + n, ",")) return ParseRes.failed();
super(loc); n++;
this.hasScope = hasScope;
this.statements = statements;
}
public static void compileMultiEntry(Node node, CompileResult target, boolean pollute, BreakpointType type) { var curr = JavaScript.parseExpression(src, i + n, 2);
if (node instanceof CompoundNode comp) { if (!curr.isSuccess()) return curr.chainError(src.loc(i + n), "Expected a value after the comma");
comp.compile(target, pollute, false, type); n += curr.n;
}
else {
node.compile(target, pollute, type);
}
}
public static ParseRes<CompoundNode> parseComma(Source src, int i, Node prev, int precedence) { if (prev instanceof CompoundNode comp) {
if (precedence > 1) return ParseRes.failed(); var children = new ArrayList<Node>();
children.addAll(Arrays.asList(comp.statements));
children.add(curr.result);
var n = Parsing.skipEmpty(src, i); return ParseRes.res(new CompoundNode(loc, children.toArray(new Node[0])), n);
var loc = src.loc(i + n); }
else return ParseRes.res(new CompoundNode(loc, prev, curr.result), n);
}
public static ParseRes<CompoundNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!src.is(i + n, ",")) return ParseRes.failed(); if (!src.is(i + n, "{")) return ParseRes.failed();
n++; n++;
var curr = JavaScript.parseExpression(src, i + n, 2); var statements = new ArrayList<Node>();
if (!curr.isSuccess()) return curr.chainError(src.loc(i + n), "Expected a value after the comma");
n += curr.n;
if (prev instanceof CompoundNode comp) { while (true) {
var children = new ArrayList<Node>(); n += Parsing.skipEmpty(src, i + n);
children.addAll(Arrays.asList(comp.statements));
children.add(curr.result);
return ParseRes.res(new CompoundNode(loc, comp.hasScope, children.toArray(new Node[0])), n); if (src.is(i + n, "}")) {
} n++;
else return ParseRes.res(new CompoundNode(loc, false, prev, curr.result), n); break;
} }
public static ParseRes<CompoundNode> parse(Source src, int i) { if (src.is(i + n, ";")) {
var n = Parsing.skipEmpty(src, i); n++;
var loc = src.loc(i + n); continue;
}
if (!src.is(i + n, "{")) return ParseRes.failed(); var res = JavaScript.parseStatement(src, i + n);
n++; if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a statement");
n += res.n;
var statements = new ArrayList<Node>(); statements.add(res.result);
}
while (true) { return ParseRes.res(new CompoundNode(loc, statements.toArray(new Node[0])).setEnd(src.loc(i + n - 1)), n);
n += Parsing.skipEmpty(src, i + n); }
if (src.is(i + n, "}")) {
n++;
break;
}
if (src.is(i + n, ";")) {
n++;
continue;
}
var res = JavaScript.parseStatement(src, i + n);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a statement");
n += res.n;
statements.add(res.result);
}
return ParseRes.res(new CompoundNode(loc, true, statements.toArray(new Node[0])).setEnd(src.loc(i + n - 1)), n);
}
} }

View File

@@ -3,17 +3,17 @@ package me.topchetoeu.jscript.compilation;
import java.util.function.IntSupplier; import java.util.function.IntSupplier;
public final class DeferredIntSupplier implements IntSupplier { public final class DeferredIntSupplier implements IntSupplier {
private int value; private int value;
private boolean set; private boolean set;
public void set(int val) { public void set(int val) {
if (set) throw new RuntimeException("A deferred int supplier may be set only once"); if (set) throw new RuntimeException("A deferred int supplier may be set only once");
value = val; value = val;
set = true; set = true;
} }
@Override public int getAsInt() { @Override public int getAsInt() {
if (!set) throw new RuntimeException("Deferred int supplier accessed too early"); if (!set) throw new RuntimeException("Deferred int supplier accessed too early");
return value; return value;
} }
} }

View File

@@ -1,78 +0,0 @@
package me.topchetoeu.jscript.compilation;
import java.util.Arrays;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.environment.Environment;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.control.ReturnNode;
import me.topchetoeu.jscript.compilation.patterns.Pattern;
public class FunctionArrowNode extends FunctionNode {
@Override public String name() { return null; }
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
var id = target.addChild(compileBody(target, name, null));
target.add(_i -> Instruction.loadFunc(id, true, false, true, false, null, captures(id, target)));
}
@Override protected Environment rootEnv(Environment env) {
return env.getWith(ClassNode.CLASS_ROOT, () -> super.rootEnv(env));
}
public FunctionArrowNode(Location loc, Location end, Parameters params, Node body) {
super(loc, end, params, expToBody(body));
}
private static final CompoundNode expToBody(Node node) {
if (node instanceof CompoundNode res) return res;
else return new CompoundNode(node.loc(), false, new ReturnNode(node.loc(), node));
}
public static ParseRes<FunctionArrowNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
Parameters params;
if (src.is(i + n, "(")) {
var paramsRes = Parameters.parseParameters(src, i + n);
if (!paramsRes.isSuccess()) return paramsRes.chainError();
n += paramsRes.n;
n += Parsing.skipEmpty(src, i + n);
params = paramsRes.result;
}
else {
var singleParam = Pattern.parse(src, i + n, true);
if (!singleParam.isSuccess()) return ParseRes.failed();
n += singleParam.n;
n += Parsing.skipEmpty(src, i + n);
params = new Parameters(Arrays.asList(singleParam.result));
}
if (!src.is(i + n, "=>")) return ParseRes.failed();
n += 2;
n += Parsing.skipEmpty(src, i + n);
if (src.is(i + n, "{")) {
var body = CompoundNode.parse(src, i + n);
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compount statement after '=>'");
n += body.n;
return ParseRes.res(new FunctionArrowNode(loc, src.loc(i + n - 1), params, body.result), n);
}
else {
var body = JavaScript.parseExpression(src, i + n, 2);
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compount statement after '=>'");
n += body.n;
return ParseRes.res(new FunctionArrowNode(loc, src.loc(i + n - 1), params, body.result), n);
}
}
}

View File

@@ -1,5 +1,7 @@
package me.topchetoeu.jscript.compilation; package me.topchetoeu.jscript.compilation;
import java.util.List;
import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.common.environment.Environment;
@@ -7,126 +9,117 @@ import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
import me.topchetoeu.jscript.compilation.scope.FunctionScope; import me.topchetoeu.jscript.compilation.scope.FunctionScope;
import me.topchetoeu.jscript.compilation.scope.Variable; import me.topchetoeu.jscript.compilation.values.VariableNode;
public abstract class FunctionNode extends Node { public abstract class FunctionNode extends Node {
public final CompoundNode body; public final CompoundNode body;
public final Parameters params; public final List<VariableNode> params;
public final Location end; public final Location end;
public abstract String name(); public abstract String name();
public final String name(String fallback) {
return this.name() != null ? this.name() : fallback;
}
protected final int[] captures(CompileResult target) {
return target.childrenMap.get(this).scope.getCaptureIndices();
}
protected final int[] captures(int id, CompileResult target) { protected final Environment rootEnv(Environment env) {
return ((FunctionScope)target.children.get(id).scope).getCaptureIndices(); return env.get(JavaScript.COMPILE_ROOT);
} }
protected void compilePreBody(CompileResult target) { } @Override public void resolve(CompileResult target) { }
protected Environment rootEnv(Environment env) { public final CompileResult compileBody(Environment env, FunctionScope scope, boolean lastReturn, String selfName) {
return env.get(JavaScript.COMPILE_ROOT); var target = new CompileResult(env, scope, params.size());
} var i = 0;
public final CompileResult compileBody(Environment env, FunctionScope scope, boolean lastReturn, String _name, String selfName) { body.resolve(target);
var name = this.name() != null ? this.name() : _name;
return new CompileResult(env, scope, params.params.size(), target -> { for (var param : params) scope.define(param.name);
compilePreBody(target);
if (params.params.size() > 0) { var hasSelf = false;
target.add(Instruction.loadArgs(true));
if (params.params.size() > 1) target.add(Instruction.dup(params.params.size() - 1, 0));
var i = 0;
for (var param : params.params) { if (selfName != null && !scope.has(selfName, false)) {
target.add(Instruction.loadMember(i++)); hasSelf = true;
param.destruct(target, DeclarationType.VAR, true); scope.define(selfName);
} }
}
if (params.rest != null) { body.compileFunctions(target);
target.add(Instruction.loadRestArgs(params.params.size()));
params.rest.destruct(target, DeclarationType.VAR, true);
}
if (selfName != null && !scope.has(name, false)) { for (var param : params) {
var i = scope.defineSpecial(new Variable(selfName, true), end); target.add(Instruction.loadArg(i++)).setLocation(param.loc());
target.add(scope.define(param.name).index().toSet(false)).setLocation(param.loc());
}
if (hasSelf) {
target.add(Instruction.loadCalled());
target.add(scope.define(selfName).index().toSet(false));
}
target.add(Instruction.loadCallee()); body.compile(target, lastReturn, BreakpointType.NONE);
target.add(_i -> i.index().toInit());
}
body.resolve(target); return target;
body.compile(target, lastReturn, BreakpointType.NONE); }
public final CompileResult compileBody(CompileResult parent, String selfName) {
return compileBody(rootEnv(parent.env).child(), new FunctionScope(parent.scope), false, selfName);
}
scope.end(); public abstract void compile(CompileResult target, boolean pollute, String name, BreakpointType bp);
public void compile(CompileResult target, boolean pollute, String name) {
compile(target, pollute, name, BreakpointType.NONE);
}
@Override public void compile(CompileResult target, boolean pollute, BreakpointType bp) {
compile(target, pollute, (String)null, bp);
}
@Override public void compile(CompileResult target, boolean pollute) {
compile(target, pollute, (String)null, BreakpointType.NONE);
}
for (var child : target.children) child.buildTask.run(); public FunctionNode(Location loc, Location end, List<VariableNode> params, CompoundNode body) {
super(loc);
scope.finish(); this.end = end;
}); this.params = params;
} this.body = body;
public final CompileResult compileBody(CompileResult parent, String name, String selfName) { }
return compileBody(rootEnv(parent.env).child(), new FunctionScope(parent.scope), false, name, selfName);
}
public abstract void compile(CompileResult target, boolean pollute, String name, BreakpointType bp); public static void compileWithName(Node stm, CompileResult target, boolean pollute, String name) {
public void compile(CompileResult target, boolean pollute, String name) { if (stm instanceof FunctionNode) ((FunctionNode)stm).compile(target, pollute, name);
compile(target, pollute, name, BreakpointType.NONE); else stm.compile(target, pollute);
} }
@Override public void compile(CompileResult target, boolean pollute, BreakpointType bp) { public static void compileWithName(Node stm, CompileResult target, boolean pollute, String name, BreakpointType bp) {
compile(target, pollute, (String)null, bp); if (stm instanceof FunctionNode) ((FunctionNode)stm).compile(target, pollute, name, bp);
} else stm.compile(target, pollute, bp);
@Override public void compile(CompileResult target, boolean pollute) { }
compile(target, pollute, (String)null, BreakpointType.NONE);
}
public FunctionNode(Location loc, Location end, Parameters params, CompoundNode body) { public static ParseRes<FunctionNode> parseFunction(Source src, int i, boolean statement) {
super(loc); var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
this.end = end; if (!Parsing.isIdentifier(src, i + n, "function")) return ParseRes.failed();
this.params = params; n += 8;
this.body = body;
this.body.hasScope = false;
}
public static void compileWithName(Node stm, CompileResult target, boolean pollute, String name) { var name = Parsing.parseIdentifier(src, i + n);
if (stm instanceof FunctionNode) ((FunctionNode)stm).compile(target, pollute, name); if (!name.isSuccess() && statement) return ParseRes.error(src.loc(i + n), "A statement function requires a name");
else stm.compile(target, pollute); n += name.n;
} n += Parsing.skipEmpty(src, i + n);
public static void compileWithName(Node stm, CompileResult target, boolean pollute, String name, BreakpointType bp) {
if (stm instanceof FunctionNode) ((FunctionNode)stm).compile(target, pollute, name, bp);
else stm.compile(target, pollute, bp);
}
public static ParseRes<FunctionNode> parseFunction(Source src, int i, boolean statement) { var params = JavaScript.parseParameters(src, i + n);
var n = Parsing.skipEmpty(src, i); if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected a parameter list");
var loc = src.loc(i + n); n += params.n;
if (!Parsing.isIdentifier(src, i + n, "function")) return ParseRes.failed(); var body = CompoundNode.parse(src, i + n);
n += 8; if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compound statement for function");
n += body.n;
var name = Parsing.parseIdentifier(src, i + n); if (statement) return ParseRes.res(new FunctionStatementNode(
if (!name.isSuccess() && statement) return ParseRes.error(src.loc(i + n), "A statement function requires a name"); loc, src.loc(i + n - 1),
n += name.n; params.result, body.result, name.result
n += Parsing.skipEmpty(src, i + n); ), n);
else return ParseRes.res(new FunctionValueNode(
var params = Parameters.parseParameters(src, i + n); loc, src.loc(i + n - 1),
if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected a parameter list"); params.result, body.result, name.result
n += params.n; ), n);
}
var body = CompoundNode.parse(src, i + n);
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compound statement for function");
n += body.n;
if (statement) return ParseRes.res(new FunctionStatementNode(
loc, src.loc(i + n - 1),
params.result, body.result, name.result
), n);
else return ParseRes.res(new FunctionValueNode(
loc, src.loc(i + n - 1),
params.result, body.result, name.result
), n);
}
} }

View File

@@ -1,5 +1,7 @@
package me.topchetoeu.jscript.compilation; package me.topchetoeu.jscript.compilation;
import java.util.List;
import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.common.parsing.Location;
@@ -7,23 +9,25 @@ import me.topchetoeu.jscript.compilation.scope.Variable;
import me.topchetoeu.jscript.compilation.values.VariableNode; import me.topchetoeu.jscript.compilation.values.VariableNode;
public class FunctionStatementNode extends FunctionNode { public class FunctionStatementNode extends FunctionNode {
public final String name; public final String name;
@Override public String name() { return name; } @Override public String name() { return name; }
@Override public void resolve(CompileResult target) { @Override public void resolve(CompileResult target) {
target.scope.define(new Variable(name, false), end); target.scope.define(new Variable(name, false));
} }
@Override public void compileFunctions(CompileResult target) {
target.addChild(this, compileBody(target, name()));
}
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) { @Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
var id = target.addChild(compileBody(target, name, null)); target.add(Instruction.loadFunc(target.childrenIndices.get(this), name(name), captures(target))).setLocation(loc());
target.add(_i -> Instruction.loadFunc(id, true, true, false, false, name, captures(id, target))); target.add(VariableNode.toSet(target, end, this.name, false, true)).setLocation(loc());
target.add(VariableNode.toInit(target, end, this.name)); if (pollute) target.add(Instruction.pushUndefined());
if (pollute) target.add(Instruction.pushUndefined()); }
}
public FunctionStatementNode(Location loc, Location end, Parameters params, CompoundNode body, String name) { public FunctionStatementNode(Location loc, Location end, List<VariableNode> params, CompoundNode body, String name) {
super(loc, end, params, body); super(loc, end, params, body);
this.name = name; this.name = name;
} }
} }

View File

@@ -1,21 +1,28 @@
package me.topchetoeu.jscript.compilation; package me.topchetoeu.jscript.compilation;
import java.util.List;
import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.compilation.values.VariableNode;
public class FunctionValueNode extends FunctionNode { public class FunctionValueNode extends FunctionNode {
public final String name; public final String name;
@Override public String name() { return name; } @Override public String name() { return name; }
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) { @Override public void compileFunctions(CompileResult target) {
var id = target.addChild(compileBody(target, name, null)); target.addChild(this, compileBody(target, name()));
target.add(_i -> Instruction.loadFunc(id, true, true, false, false, name, captures(id, target))); }
}
public FunctionValueNode(Location loc, Location end, Parameters params, CompoundNode body, String name) { @Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
super(loc, end, params, body); target.add(Instruction.loadFunc(target.childrenIndices.get(this), name(name), captures(target))).setLocation(loc());
this.name = name; if (!pollute) target.add(Instruction.discard());
} }
public FunctionValueNode(Location loc, Location end, List<VariableNode> params, CompoundNode body, String name) {
super(loc, end, params, body);
this.name = name;
}
} }

View File

@@ -3,6 +3,7 @@ package me.topchetoeu.jscript.compilation;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import me.topchetoeu.jscript.common.SyntaxException; import me.topchetoeu.jscript.common.SyntaxException;
@@ -19,7 +20,6 @@ import me.topchetoeu.jscript.compilation.control.DeleteNode;
import me.topchetoeu.jscript.compilation.control.DoWhileNode; import me.topchetoeu.jscript.compilation.control.DoWhileNode;
import me.topchetoeu.jscript.compilation.control.ForInNode; import me.topchetoeu.jscript.compilation.control.ForInNode;
import me.topchetoeu.jscript.compilation.control.ForNode; import me.topchetoeu.jscript.compilation.control.ForNode;
import me.topchetoeu.jscript.compilation.control.ForOfNode;
import me.topchetoeu.jscript.compilation.control.IfNode; import me.topchetoeu.jscript.compilation.control.IfNode;
import me.topchetoeu.jscript.compilation.control.ReturnNode; import me.topchetoeu.jscript.compilation.control.ReturnNode;
import me.topchetoeu.jscript.compilation.control.SwitchNode; import me.topchetoeu.jscript.compilation.control.SwitchNode;
@@ -29,10 +29,9 @@ import me.topchetoeu.jscript.compilation.control.WhileNode;
import me.topchetoeu.jscript.compilation.scope.FunctionScope; import me.topchetoeu.jscript.compilation.scope.FunctionScope;
import me.topchetoeu.jscript.compilation.values.ArgumentsNode; import me.topchetoeu.jscript.compilation.values.ArgumentsNode;
import me.topchetoeu.jscript.compilation.values.ArrayNode; import me.topchetoeu.jscript.compilation.values.ArrayNode;
import me.topchetoeu.jscript.compilation.values.ClassValueNode; import me.topchetoeu.jscript.compilation.values.GlobalThisNode;
import me.topchetoeu.jscript.compilation.values.ObjectNode; import me.topchetoeu.jscript.compilation.values.ObjectNode;
import me.topchetoeu.jscript.compilation.values.RegexNode; import me.topchetoeu.jscript.compilation.values.RegexNode;
import me.topchetoeu.jscript.compilation.values.SuperNode;
import me.topchetoeu.jscript.compilation.values.ThisNode; import me.topchetoeu.jscript.compilation.values.ThisNode;
import me.topchetoeu.jscript.compilation.values.VariableNode; import me.topchetoeu.jscript.compilation.values.VariableNode;
import me.topchetoeu.jscript.compilation.values.constants.BoolNode; import me.topchetoeu.jscript.compilation.values.constants.BoolNode;
@@ -48,255 +47,279 @@ import me.topchetoeu.jscript.compilation.values.operations.PostfixNode;
import me.topchetoeu.jscript.compilation.values.operations.TypeofNode; import me.topchetoeu.jscript.compilation.values.operations.TypeofNode;
public final class JavaScript { public final class JavaScript {
public static enum DeclarationType { public static enum DeclarationType {
VAR(false, false), @Deprecated
CONST(true, true), VAR;
LET(true, false); }
public final boolean strict, readonly; public static final Key<Environment> COMPILE_ROOT = new Key<>();
private DeclarationType(boolean strict, boolean readonly) { static final Set<String> reserved = new HashSet<>(Arrays.asList(
this.strict = strict; "true", "false", "void", "null", "this", "if", "else", "try", "catch",
this.readonly = readonly; "finally", "for", "do", "while", "switch", "case", "default", "new",
} "function", "var", "return", "throw", "typeof", "delete", "break",
} "continue", "debugger", "implements", "interface", "package", "private",
"protected", "public", "static", "arguments", "class", "extends"
));
public static final Key<Environment> COMPILE_ROOT = Key.of(); public static ParseRes<? extends Node> parseParens(Source src, int i) {
int n = 0;
static final Set<String> reserved = new HashSet<>(Arrays.asList( var openParen = Parsing.parseOperator(src, i + n, "(");
"true", "false", "void", "null", "this", "if", "else", "try", "catch", if (!openParen.isSuccess()) return openParen.chainError();
"finally", "for", "do", "while", "switch", "case", "default", "new", n += openParen.n;
"function", "var", "return", "throw", "typeof", "delete", "break",
"continue", "debugger", "implements", "interface", "package", "private",
"protected", "public", "static", "arguments", "class", "extends"
));
public static ParseRes<? extends Node> parseParens(Source src, int i) { var res = JavaScript.parseExpression(src, i + n, 0);
int n = 0; if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an expression in parens");
n += res.n;
var openParen = Parsing.parseOperator(src, i + n, "("); var closeParen = Parsing.parseOperator(src, i + n, ")");
if (!openParen.isSuccess()) return openParen.chainError(); if (!closeParen.isSuccess()) return closeParen.chainError(src.loc(i + n), "Expected a closing paren");
n += openParen.n; n += closeParen.n;
var res = JavaScript.parseExpression(src, i + n, 0); return ParseRes.res(res.result, n);
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an expression in parens"); }
n += res.n;
var closeParen = Parsing.parseOperator(src, i + n, ")"); public static ParseRes<? extends Node> parseSimple(Source src, int i, boolean statement) {
if (!closeParen.isSuccess()) return closeParen.chainError(src.loc(i + n), "Expected a closing paren"); return ParseRes.first(src, i,
n += closeParen.n; (s, j) -> statement ? ParseRes.failed() : ObjectNode.parse(s, j),
(s, j) -> statement ? ParseRes.failed() : FunctionNode.parseFunction(s, j, false),
JavaScript::parseLiteral,
StringNode::parse,
RegexNode::parse,
NumberNode::parse,
ChangeNode::parsePrefixDecrease,
ChangeNode::parsePrefixIncrease,
OperationNode::parsePrefix,
ArrayNode::parse,
JavaScript::parseParens,
CallNode::parseNew,
TypeofNode::parse,
DiscardNode::parse,
DeleteNode::parse,
VariableNode::parse
);
}
return ParseRes.res(res.result, n); public static ParseRes<? extends Node> parseLiteral(Source src, int i) {
} var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
public static ParseRes<? extends Node> parseSimple(Source src, int i, boolean statement) { var id = Parsing.parseIdentifier(src, i);
return ParseRes.first(src, i, if (!id.isSuccess()) return id.chainError();
(s, j) -> statement ? ParseRes.failed() : ObjectNode.parse(s, j), n += id.n;
(s, j) -> statement ? ParseRes.failed() : FunctionNode.parseFunction(s, j, false),
(s, j) -> statement ? ParseRes.failed() : ClassValueNode.parse(s, j),
JavaScript::parseLiteral,
StringNode::parse,
RegexNode::parse,
NumberNode::parse,
ChangeNode::parsePrefixDecrease,
ChangeNode::parsePrefixIncrease,
OperationNode::parsePrefix,
ArrayNode::parse,
(s, j) -> statement ? ParseRes.failed() : FunctionArrowNode.parse(s, j),
JavaScript::parseParens,
CallNode::parseNew,
TypeofNode::parse,
DiscardNode::parse,
DeleteNode::parse,
VariableNode::parse
);
}
public static ParseRes<? extends Node> parseLiteral(Source src, int i) { if (id.result.equals("true")) return ParseRes.res(new BoolNode(loc, true), n);
var n = Parsing.skipEmpty(src, i); if (id.result.equals("false")) return ParseRes.res(new BoolNode(loc, false), n);
var loc = src.loc(i + n); if (id.result.equals("null")) return ParseRes.res(new NullNode(loc), n);
if (id.result.equals("this")) return ParseRes.res(new ThisNode(loc), n);
if (id.result.equals("arguments")) return ParseRes.res(new ArgumentsNode(loc), n);
if (id.result.equals("globalThis")) return ParseRes.res(new GlobalThisNode(loc), n);
var id = Parsing.parseIdentifier(src, i); return ParseRes.failed();
if (!id.isSuccess()) return id.chainError(); }
n += id.n;
if (id.result.equals("true")) return ParseRes.res(new BoolNode(loc, true), n); public static ParseRes<Node> parseExpression(Source src, int i, int precedence, boolean statement) {
if (id.result.equals("false")) return ParseRes.res(new BoolNode(loc, false), n); var n = Parsing.skipEmpty(src, i);
if (id.result.equals("null")) return ParseRes.res(new NullNode(loc), n); Node prev = null;
if (id.result.equals("this")) return ParseRes.res(new ThisNode(loc), n);
if (id.result.equals("super")) return ParseRes.res(new SuperNode(loc), n);
if (id.result.equals("arguments")) return ParseRes.res(new ArgumentsNode(loc), n);
return ParseRes.failed(); while (true) {
} if (prev == null) {
var res = parseSimple(src, i + n, statement);
if (res.isSuccess()) {
n += res.n;
prev = res.result;
}
else if (res.isError()) return res.chainError();
else break;
}
else {
var _prev = prev;
ParseRes<Node> res = ParseRes.first(src, i + n,
(s, j) -> OperationNode.parseInstanceof(s, j, _prev, precedence),
(s, j) -> OperationNode.parseIn(s, j, _prev, precedence),
(s, j) -> PostfixNode.parsePostfixIncrease(s, j, _prev, precedence),
(s, j) -> PostfixNode.parsePostfixDecrease(s, j, _prev, precedence),
(s, j) -> OperationNode.parseOperator(s, j, _prev, precedence),
(s, j) -> IfNode.parseTernary(s, j, _prev, precedence),
(s, j) -> IndexNode.parseMember(s, j, _prev, precedence),
(s, j) -> IndexNode.parseIndex(s, j, _prev, precedence),
(s, j) -> CallNode.parseCall(s, j, _prev, precedence),
(s, j) -> CompoundNode.parseComma(s, j, _prev, precedence)
);
public static ParseRes<Node> parseExpression(Source src, int i, int precedence, boolean statement) { if (res.isSuccess()) {
var n = Parsing.skipEmpty(src, i); n += res.n;
Node prev = null; prev = res.result;
continue;
}
else if (res.isError()) return res.chainError();
while (true) { break;
if (prev == null) { }
var res = parseSimple(src, i + n, statement); }
if (res.isSuccess()) {
n += res.n;
prev = res.result;
}
else if (res.isError()) return res.chainError();
else break;
}
else {
var _prev = prev;
ParseRes<Node> res = ParseRes.first(src, i + n,
(s, j) -> OperationNode.parseInstanceof(s, j, _prev, precedence),
(s, j) -> OperationNode.parseIn(s, j, _prev, precedence),
(s, j) -> PostfixNode.parsePostfixIncrease(s, j, _prev, precedence),
(s, j) -> PostfixNode.parsePostfixDecrease(s, j, _prev, precedence),
(s, j) -> OperationNode.parseOperator(s, j, _prev, precedence),
(s, j) -> IfNode.parseTernary(s, j, _prev, precedence),
(s, j) -> IndexNode.parseMember(s, j, _prev, precedence),
(s, j) -> IndexNode.parseIndex(s, j, _prev, precedence),
(s, j) -> CallNode.parseCall(s, j, _prev, precedence),
(s, j) -> CompoundNode.parseComma(s, j, _prev, precedence)
);
if (res.isSuccess()) { if (prev == null) return ParseRes.failed();
n += res.n; else return ParseRes.res(prev, n);
prev = res.result; }
continue;
}
else if (res.isError()) return res.chainError();
break; public static ParseRes<Node> parseExpression(Source src, int i, int precedence) {
} return parseExpression(src, i, precedence, false);
} }
if (prev == null) return ParseRes.failed(); public static ParseRes<Node> parseExpressionStatement(Source src, int i) {
else return ParseRes.res(prev, n); var res = parseExpression(src, i, 0, true);
} if (!res.isSuccess()) return res.chainError();
public static ParseRes<Node> parseExpression(Source src, int i, int precedence) { var end = JavaScript.parseStatementEnd(src, i + res.n);
return parseExpression(src, i, precedence, false); if (!end.isSuccess()) return ParseRes.error(src.loc(i + res.n), "Expected an end of statement");
}
public static ParseRes<Node> parseExpressionStatement(Source src, int i) { return res.addN(end.n);
var res = parseExpression(src, i, 0, true); }
if (!res.isSuccess()) return res.chainError();
var end = JavaScript.parseStatementEnd(src, i + res.n); public static ParseRes<Node> parseStatement(Source src, int i) {
if (!end.isSuccess()) return ParseRes.error(src.loc(i + res.n), "Expected an end of statement"); var n = Parsing.skipEmpty(src, i);
return res.addN(end.n); if (src.is(i + n, ";")) return ParseRes.res(new DiscardNode(src.loc(i+ n), null), n + 1);
} if (Parsing.isIdentifier(src, i + n, "with")) return ParseRes.error(src.loc(i + n), "'with' statements are not allowed.");
public static ParseRes<? extends Node> parseStatement(Source src, int i) { ParseRes<Node> res = ParseRes.first(src, i + n,
var n = Parsing.skipEmpty(src, i); VariableDeclareNode::parse,
ReturnNode::parse,
ThrowNode::parse,
ContinueNode::parse,
BreakNode::parse,
DebugNode::parse,
IfNode::parse,
WhileNode::parse,
SwitchNode::parse,
ForNode::parse,
ForInNode::parse,
DoWhileNode::parse,
TryNode::parse,
CompoundNode::parse,
(s, j) -> FunctionNode.parseFunction(s, j, true),
JavaScript::parseExpressionStatement
);
return res.addN(n);
}
if (src.is(i + n, ";")) return ParseRes.res(new DiscardNode(src.loc(i+ n), null), n + 1); public static ParseRes<Boolean> parseStatementEnd(Source src, int i) {
if (Parsing.isIdentifier(src, i + n, "with")) return ParseRes.error(src.loc(i + n), "'with' statements are not allowed."); var n = Parsing.skipEmpty(src, i);
if (i + n >= src.size()) return ParseRes.res(true, n);
ParseRes<? extends Node> res = ParseRes.first(src, i + n, for (var j = i; j < i + n; j++) {
ClassStatementNode::parse, if (src.is(j, '\n')) return ParseRes.res(true, n);
VariableDeclareNode::parse, }
ReturnNode::parse,
ThrowNode::parse,
ContinueNode::parse,
BreakNode::parse,
DebugNode::parse,
IfNode::parse,
WhileNode::parse,
SwitchNode::parse,
ForNode::parse,
ForInNode::parse,
ForOfNode::parse,
DoWhileNode::parse,
TryNode::parse,
CompoundNode::parse,
(s, j) -> FunctionNode.parseFunction(s, j, true),
JavaScript::parseExpressionStatement
);
return res.addN(n);
}
public static ParseRes<Boolean> parseStatementEnd(Source src, int i) { if (src.is(i + n, ';')) return ParseRes.res(true, n + 1);
var n = Parsing.skipEmpty(src, i); if (src.is(i + n, '}')) return ParseRes.res(true, n);
if (i + n >= src.size()) return ParseRes.res(true, n);
for (var j = i; j < i + n; j++) { return ParseRes.failed();
if (src.is(j, '\n')) return ParseRes.res(true, n); }
}
if (src.is(i + n, ';')) return ParseRes.res(true, n + 1); public static ParseRes<Boolean> parseDeclarationType(Source src, int i) {
if (src.is(i + n, '}')) return ParseRes.res(true, n); var res = Parsing.parseIdentifier(src, i);
if (!res.isSuccess()) return res.chainError();
return ParseRes.failed(); if (res.result.equals("var")) return ParseRes.res(true, res.n);
}
public static ParseRes<DeclarationType> parseDeclarationType(Source src, int i) { return ParseRes.failed();
var res = Parsing.parseIdentifier(src, i); }
if (!res.isSuccess()) return res.chainError();
if (res.result.equals("var")) return ParseRes.res(DeclarationType.VAR, res.n); public static Node[] parse(Environment env, Filename filename, String raw) {
if (res.result.equals("let")) return ParseRes.res(DeclarationType.LET, res.n); var src = new Source(env, filename, raw);
if (res.result.equals("const")) return ParseRes.res(DeclarationType.CONST, res.n); var list = new ArrayList<Node>();
int i = 0;
return ParseRes.failed(); while (true) {
} i += Parsing.skipEmpty(src, i);
if (i >= src.size()) break;
public static Node[] parse(Environment env, Filename filename, String raw) { var res = parseStatement(src, i);
var src = new Source(env, filename, raw);
var list = new ArrayList<Node>();
int i = 0;
while (true) { if (res.isError()) throw new SyntaxException(res.errorLocation, res.error);
if (i >= src.size()) break; else if (res.isFailed()) throw new SyntaxException(src.loc(i), "Unexpected syntax");
var res = parseStatement(src, i); i += res.n;
i += Parsing.skipEmpty(src, i);
if (res.isError()) throw new SyntaxException(res.errorLocation, res.error); list.add(res.result);
else if (res.isFailed()) throw new SyntaxException(src.loc(i), "Unexpected syntax"); }
i += res.n; return list.toArray(new Node[0]);
i += Parsing.skipEmpty(src, i); }
list.add(res.result); public static boolean checkVarName(String name) {
} return !JavaScript.reserved.contains(name);
}
return list.toArray(new Node[0]); public static CompileResult compile(Environment env, boolean passthrough, Node ...statements) {
} env = env.child();
env.add(COMPILE_ROOT, env);
public static boolean checkVarName(String name) { var func = new FunctionValueNode(null, null, Arrays.asList(), new CompoundNode(null, statements), null);
return !JavaScript.reserved.contains(name); var res = func.compileBody(env, new FunctionScope(passthrough), true, null);
} return res;
}
public static CompileResult compile(Environment env, Node ...statements) { public static CompileResult compile(Environment env, Filename filename, String raw, boolean passthrough) {
env = env.child(); return JavaScript.compile(env, passthrough, JavaScript.parse(env, filename, raw));
env.add(COMPILE_ROOT, env); }
public static CompileResult compile(Filename filename, String raw, boolean passthrough) {
var env = new Environment();
return JavaScript.compile(env, passthrough, JavaScript.parse(env, filename, raw));
}
var func = new FunctionValueNode(null, null, new Parameters(Arrays.asList()), new CompoundNode(null, true, statements), null); public static ParseRes<String> parseLabel(Source src, int i) {
var res = func.compileBody(env, new FunctionScope(true), true, null, null); int n = Parsing.skipEmpty(src, i);
res.buildTask.run();
return res; var nameRes = Parsing.parseIdentifier(src, i + n);
} if (!nameRes.isSuccess()) return nameRes.chainError();
n += nameRes.n;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, ":")) return ParseRes.failed();
n++;
return ParseRes.res(nameRes.result, n);
}
public static CompileResult compile(Environment env, Filename filename, String raw) { public static ParseRes<List<VariableNode>> parseParameters(Source src, int i) {
return JavaScript.compile(env, JavaScript.parse(env, filename, raw)); var n = Parsing.skipEmpty(src, i);
}
public static CompileResult compile(Filename filename, String raw) { var openParen = Parsing.parseOperator(src, i + n, "(");
var env = new Environment(); if (!openParen.isSuccess()) return openParen.chainError(src.loc(i + n), "Expected a parameter list");
return JavaScript.compile(env, JavaScript.parse(env, filename, raw)); n += openParen.n;
}
var params = new ArrayList<VariableNode>();
public static ParseRes<String> parseLabel(Source src, int i) {
int n = Parsing.skipEmpty(src, i); var closeParen = Parsing.parseOperator(src, i + n, ")");
n += closeParen.n;
var nameRes = Parsing.parseIdentifier(src, i + n);
if (!nameRes.isSuccess()) return nameRes.chainError(); if (!closeParen.isSuccess()) {
n += nameRes.n; while (true) {
n += Parsing.skipEmpty(src, i + n); n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, ":")) return ParseRes.failed(); var param = VariableNode.parse(src, i + n);
n++; if (!param.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a parameter or a closing brace");
n += param.n;
return ParseRes.res(nameRes.result, n); n += Parsing.skipEmpty(src, i + n);
}
params.add(param.result);
if (src.is(i + n, ",")) {
n++;
n += Parsing.skipEmpty(src, i + n);
}
if (src.is(i + n, ")")) {
n++;
break;
}
}
}
return ParseRes.res(params, n);
}
} }

View File

@@ -1,8 +1,8 @@
package me.topchetoeu.jscript.compilation; package me.topchetoeu.jscript.compilation;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.function.IntFunction;
import java.util.function.IntSupplier; import java.util.function.IntSupplier;
import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Instruction;
@@ -12,74 +12,109 @@ import me.topchetoeu.jscript.common.environment.Key;
import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.common.parsing.Location;
public class LabelContext { public class LabelContext {
public static final Key<LabelContext> BREAK_CTX = Key.of(); public static final Key<LabelContext> BREAK_CTX = new Key<>();
public static final Key<LabelContext> CONTINUE_CTX = Key.of(); public static final Key<LabelContext> CONTINUE_CTX = new Key<>();
private final LinkedList<IntSupplier> list = new LinkedList<>(); private final LinkedList<IntSupplier> list = new LinkedList<>();
private final HashMap<String, IntSupplier> map = new HashMap<>(); private final HashMap<String, IntSupplier> map = new HashMap<>();
public IntSupplier get() { private final LinkedList<ArrayList<Runnable>> deferredList = new LinkedList<>();
return list.peekLast(); private final HashMap<String, ArrayList<Runnable>> deferredMap = new HashMap<>();
}
public IntSupplier get(String name) {
return map.get(name);
}
public IntFunction<Instruction> getJump() { public IntSupplier get() {
var res = get(); return list.peekLast();
if (res == null) return null; }
else return i -> Instruction.jmp(res.getAsInt() - i); public IntSupplier get(String name) {
} return map.get(name);
public IntFunction<Instruction> getJump(String name) { }
var res = get(name);
if (res == null) return null;
else return i -> Instruction.jmp(res.getAsInt() - i);
}
public void push(IntSupplier jumpTarget) { public void flushAdders(String name) {
list.add(jumpTarget); for (var adder : deferredList.peek()) {
} adder.run();
public void push(Location loc, String name, IntSupplier jumpTarget) { }
if (name == null) return;
if (map.containsKey(name)) throw new SyntaxException(loc, String.format("Label '%s' has already been declared", name));
map.put(name, jumpTarget);
}
public void pushLoop(Location loc, String name, IntSupplier jumpTarget) { deferredList.pop();
push(jumpTarget);
push(loc, name, jumpTarget);
}
public void pop() { if (name != null) {
list.removeLast(); var adders = deferredMap.remove(name);
} if (adders != null) {
public void pop(String name) { for (var adder : adders) adder.run();
if (name == null) return;
map.remove(name); }
} }
}
public void popLoop(String name) { public boolean jump(CompileResult target) {
pop(); var res = get();
pop(name); if (res != null) {
} var tmp = target.temp();
this.deferredList.peek().add(() -> target.set(tmp, Instruction.jmp(res.getAsInt() - tmp)));
return true;
}
else return false;
}
public boolean jump(CompileResult target, String name) {
var res = name == null ? get() : get(name);
if (res != null) {
var tmp = target.temp();
Runnable task = () -> target.set(tmp, Instruction.jmp(res.getAsInt() - tmp));
public static LabelContext getBreak(Environment env) { if (name == null) this.deferredList.peekLast().add(task);
return env.initFrom(BREAK_CTX, () -> new LabelContext()); else if (deferredMap.containsKey(name)) this.deferredMap.get(name).add(task);
} else return false;
public static LabelContext getCont(Environment env) {
return env.initFrom(CONTINUE_CTX, () -> new LabelContext());
}
public static void pushLoop(Environment env, Location loc, String name, IntSupplier breakTarget, int contTarget) { return true;
LabelContext.getBreak(env).pushLoop(loc, name, breakTarget); }
LabelContext.getCont(env).pushLoop(loc, name, () -> contTarget); else return false;
} }
public static void pushLoop(Environment env, Location loc, String name, IntSupplier breakTarget, IntSupplier contTarget) {
LabelContext.getBreak(env).pushLoop(loc, name, breakTarget); public void push(IntSupplier jumpTarget) {
LabelContext.getCont(env).pushLoop(loc, name, contTarget); list.add(jumpTarget);
} }
public static void popLoop(Environment env, String name) { public void push(Location loc, String name, IntSupplier jumpTarget) {
LabelContext.getBreak(env).popLoop(name); if (name == null) return;
LabelContext.getCont(env).popLoop(name); if (map.containsKey(name)) throw new SyntaxException(loc, String.format("Label '%s' has already been declared", name));
} map.put(name, jumpTarget);
}
public void pushLoop(Location loc, String name, IntSupplier jumpTarget) {
push(jumpTarget);
push(loc, name, jumpTarget);
deferredList.push(new ArrayList<>());
if (name != null) deferredMap.put(name, new ArrayList<>());
}
public void pop() {
list.removeLast();
}
public void pop(String name) {
if (name == null) return;
map.remove(name);
}
public void popLoop(String name) {
pop();
pop(name);
flushAdders(name);
}
public static LabelContext getBreak(Environment env) {
return env.initFrom(BREAK_CTX, () -> new LabelContext());
}
public static LabelContext getCont(Environment env) {
return env.initFrom(CONTINUE_CTX, () -> new LabelContext());
}
public static void pushLoop(Environment env, Location loc, String name, IntSupplier breakTarget, int contTarget) {
LabelContext.getBreak(env).pushLoop(loc, name, breakTarget);
LabelContext.getCont(env).pushLoop(loc, name, () -> contTarget);
}
public static void pushLoop(Environment env, Location loc, String name, IntSupplier breakTarget, IntSupplier contTarget) {
LabelContext.getBreak(env).pushLoop(loc, name, breakTarget);
LabelContext.getCont(env).pushLoop(loc, name, contTarget);
}
public static void popLoop(Environment env, String name) {
LabelContext.getBreak(env).popLoop(name);
LabelContext.getCont(env).popLoop(name);
}
} }

View File

@@ -4,23 +4,25 @@ import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.common.parsing.Location;
public abstract class Node { public abstract class Node {
private Location loc; private Location loc;
public void resolve(CompileResult target) {} public void resolve(CompileResult target) {}
public void compile(CompileResult target, boolean pollute, BreakpointType type) { public void compile(CompileResult target, boolean pollute, BreakpointType type) {
int start = target.size(); int start = target.size();
compile(target, pollute); compile(target, pollute);
if (target.size() != start) target.setLocationAndDebug(start, loc(), type); if (target.size() != start) target.setLocationAndDebug(start, loc(), type);
} }
public void compile(CompileResult target, boolean pollute) { public void compile(CompileResult target, boolean pollute) {
compile(target, pollute, BreakpointType.NONE); compile(target, pollute, BreakpointType.NONE);
} }
public Location loc() { return loc; } public abstract void compileFunctions(CompileResult target);
public void setLoc(Location loc) { this.loc = loc; }
protected Node(Location loc) { public Location loc() { return loc; }
this.loc = loc; public void setLoc(Location loc) { this.loc = loc; }
}
protected Node(Location loc) {
this.loc = loc;
}
} }

View File

@@ -5,89 +5,89 @@ import java.util.Iterator;
import java.util.function.Function; import java.util.function.Function;
public final class NodeChildren implements Iterable<Node> { public final class NodeChildren implements Iterable<Node> {
public static final class Slot { public static final class Slot {
private Node node; private Node node;
private final Function<Node, Node> replacer; private final Function<Node, Node> replacer;
public final void replace(Node node) { public final void replace(Node node) {
this.node = this.replacer.apply(node); this.node = this.replacer.apply(node);
} }
public Slot(Node nodes, Function<Node, Node> replacer) { public Slot(Node nodes, Function<Node, Node> replacer) {
this.node = nodes; this.node = nodes;
this.replacer = replacer; this.replacer = replacer;
} }
} }
private final Slot[] slots; private final Slot[] slots;
private NodeChildren(Slot[] slots) { private NodeChildren(Slot[] slots) {
this.slots = slots; this.slots = slots;
} }
@Override public Iterator<Node> iterator() { @Override public Iterator<Node> iterator() {
return new Iterator<Node>() { return new Iterator<Node>() {
private int i = 0; private int i = 0;
private Slot[] arr = slots; private Slot[] arr = slots;
@Override public boolean hasNext() { @Override public boolean hasNext() {
if (arr == null) return false; if (arr == null) return false;
else if (i >= arr.length) { else if (i >= arr.length) {
arr = null; arr = null;
return false; return false;
} }
else return true; else return true;
} }
@Override public Node next() { @Override public Node next() {
if (!hasNext()) return null; if (!hasNext()) return null;
return arr[i++].node; return arr[i++].node;
} }
}; };
} }
public Iterable<Slot> slots() { public Iterable<Slot> slots() {
return () -> new Iterator<Slot>() { return () -> new Iterator<Slot>() {
private int i = 0; private int i = 0;
private Slot[] arr = slots; private Slot[] arr = slots;
@Override public boolean hasNext() { @Override public boolean hasNext() {
if (arr == null) return false; if (arr == null) return false;
else if (i >= arr.length) { else if (i >= arr.length) {
arr = null; arr = null;
return false; return false;
} }
else return true; else return true;
} }
@Override public Slot next() { @Override public Slot next() {
if (!hasNext()) return null; if (!hasNext()) return null;
return arr[i++]; return arr[i++];
} }
}; };
} }
public static final class Builder { public static final class Builder {
private final ArrayList<Slot> slots = new ArrayList<>(); private final ArrayList<Slot> slots = new ArrayList<>();
public final Builder add(Slot ...children) { public final Builder add(Slot ...children) {
for (var child : children) { for (var child : children) {
this.slots.add(child); this.slots.add(child);
} }
return this; return this;
} }
public final Builder add(Iterable<Slot> children) { public final Builder add(Iterable<Slot> children) {
for (var child : children) { for (var child : children) {
this.slots.add(child); this.slots.add(child);
} }
return this; return this;
} }
public final Builder add(Node child, Function<Node, Node> replacer) { public final Builder add(Node child, Function<Node, Node> replacer) {
slots.add(new Slot(child, replacer)); slots.add(new Slot(child, replacer));
return this; return this;
} }
public final NodeChildren build() { public final NodeChildren build() {
return new NodeChildren(slots.toArray(new Slot[0])); return new NodeChildren(slots.toArray(new Slot[0]));
} }
} }
} }

View File

@@ -3,13 +3,13 @@ package me.topchetoeu.jscript.compilation;
import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.common.parsing.Location;
public final class Parameter { public final class Parameter {
public final Location loc; public final Location loc;
public final String name; public final String name;
public final Node node; public final Node node;
public Parameter(Location loc, String name, Node node) { public Parameter(Location loc, String name, Node node) {
this.name = name; this.name = name;
this.node = node; this.node = node;
this.loc = loc; this.loc = loc;
} }
} }

View File

@@ -1,84 +0,0 @@
package me.topchetoeu.jscript.compilation;
import java.util.ArrayList;
import java.util.List;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.patterns.Pattern;
import me.topchetoeu.jscript.compilation.values.operations.AssignNode;
public final class Parameters {
public final int length;
public final List<Pattern> params;
public final Pattern rest;
public Parameters(List<Pattern> params, Pattern rest) {
var len = params.size();
for (var i = params.size() - 1; i >= 0; i--) {
if (!(params.get(i) instanceof AssignNode)) break;
len--;
}
this.params = params;
this.length = len;
this.rest = rest;
}
public Parameters(List<Pattern> params) {
this(params, null);
}
public static ParseRes<Parameters> parseParameters(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var openParen = Parsing.parseOperator(src, i + n, "(");
if (!openParen.isSuccess()) return openParen.chainError(src.loc(i + n), "Expected a parameter list");
n += openParen.n;
var params = new ArrayList<Pattern>();
var closeParen = Parsing.parseOperator(src, i + n, ")");
n += closeParen.n;
if (!closeParen.isSuccess()) {
while (true) {
n += Parsing.skipEmpty(src, i + n);
if (src.is(i + n, "...")) {
n += 3;
var rest = Pattern.parse(src, i + n, true);
if (!rest.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a rest parameter");
n += rest.n;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected an end of parameters list after rest parameter");
n++;
return ParseRes.res(new Parameters(params, rest.result), n);
}
var param = Pattern.parse(src, i + n, true);
if (!param.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a parameter or a closing brace");
n += param.n;
n += Parsing.skipEmpty(src, i + n);
params.add(param.result);
if (src.is(i + n, ",")) {
n++;
n += Parsing.skipEmpty(src, i + n);
}
if (src.is(i + n, ")")) {
n++;
break;
}
}
}
return ParseRes.res(new Parameters(params), n);
}
}

View File

@@ -3,113 +3,113 @@ package me.topchetoeu.jscript.compilation;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.github.bsideup.jabel.Desugar;
import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; import me.topchetoeu.jscript.compilation.values.VariableNode;
import me.topchetoeu.jscript.compilation.patterns.Pattern;
public class VariableDeclareNode extends Node { public class VariableDeclareNode extends Node {
public static class Pair { @Desugar
public final Pattern destructor; public static record Pair(VariableNode var, Node value) { }
public final Node value;
public final Location location;
public Pair(Pattern destr, Node value, Location location) { public final List<Pair> values;
this.destructor = destr;
this.value = value;
this.location = location;
}
}
public final List<Pair> values; @Override public void resolve(CompileResult target) {
public final DeclarationType declType; for (var entry : values) {
target.scope.define(entry.var.name);
}
}
@Override public void compileFunctions(CompileResult target) {
for (var pair : values) {
if (pair.value != null) pair.value.compileFunctions(target);
}
}
@Override public void compile(CompileResult target, boolean pollute) {
for (var entry : values) {
var index = target.scope.get(entry.var.name, false);
@Override public void resolve(CompileResult target) { if (entry.value != null) {
if (!declType.strict) { entry.value.compile(target, true);
for (var entry : values) { }
entry.destructor.destructDeclResolve(target);
}
}
}
@Override public void compile(CompileResult target, boolean pollute) {
for (var entry : values) {
if (entry.value == null) {
if (declType == DeclarationType.VAR) entry.destructor.declare(target, null, false);
else entry.destructor.declare(target, declType, false);
}
else {
entry.value.compile(target, true);
if (declType == DeclarationType.VAR) entry.destructor.destruct(target, null, true); if (index == null) {
else entry.destructor.destruct(target, declType, true); if (entry.value == null) {
} target.add(Instruction.globDef(entry.var.name));
} }
else {
target.add(Instruction.globSet(entry.var.name, false, true));
}
}
else if (entry.value != null) {
target.add(index.index().toSet(false));
}
}
if (pollute) target.add(Instruction.pushUndefined()); if (pollute) target.add(Instruction.pushUndefined());
} }
public VariableDeclareNode(Location loc, DeclarationType declType, List<Pair> values) { public VariableDeclareNode(Location loc, List<Pair> values) {
super(loc); super(loc);
this.values = values; this.values = values;
this.declType = declType; }
}
public static ParseRes<VariableDeclareNode> parse(Source src, int i) { public static ParseRes<VariableDeclareNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i); var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n); var loc = src.loc(i + n);
var declType = JavaScript.parseDeclarationType(src, i + n); var declType = JavaScript.parseDeclarationType(src, i + n);
if (!declType.isSuccess()) return declType.chainError(); if (!declType.isSuccess()) return declType.chainError();
n += declType.n; n += declType.n;
var res = new ArrayList<Pair>(); var res = new ArrayList<Pair>();
var end = JavaScript.parseStatementEnd(src, i + n); var end = JavaScript.parseStatementEnd(src, i + n);
if (end.isSuccess()) { if (end.isSuccess()) {
n += end.n; n += end.n;
return ParseRes.res(new VariableDeclareNode(loc, declType.result, res), n); return ParseRes.res(new VariableDeclareNode(loc, res), n);
} }
while (true) { while (true) {
var nameLoc = src.loc(i + n); var nameLoc = src.loc(i + n);
var name = Pattern.parse(src, i + n, false); var name = Parsing.parseIdentifier(src, i + n);
if (!name.isSuccess()) return name.chainError(nameLoc, "Expected a variable name or a destructor"); if (!name.isSuccess()) return name.chainError(nameLoc, "Expected a variable name");
n += name.n; n += name.n;
Node val = null; Node val = null;
var endN = n; var endN = n;
n += Parsing.skipEmpty(src, i + n); n += Parsing.skipEmpty(src, i + n);
if (src.is(i + n, "=")) { if (src.is(i + n, "=")) {
n++; n++;
var valRes = JavaScript.parseExpression(src, i + n, 2); var valRes = JavaScript.parseExpression(src, i + n, 2);
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after '='"); if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after '='");
n += valRes.n; n += valRes.n;
endN = n; endN = n;
n += Parsing.skipEmpty(src, i + n); n += Parsing.skipEmpty(src, i + n);
val = valRes.result; val = valRes.result;
} }
res.add(new Pair(name.result, val, nameLoc)); res.add(new Pair(new VariableNode(nameLoc, name.result), val));
if (src.is(i + n, ",")) { if (src.is(i + n, ",")) {
n++; n++;
continue; continue;
} }
end = JavaScript.parseStatementEnd(src, i + endN); end = JavaScript.parseStatementEnd(src, i + endN);
if (end.isSuccess()) { if (end.isSuccess()) {
n += end.n + endN - n; n += end.n + endN - n;
return ParseRes.res(new VariableDeclareNode(loc, declType.result, res), n); return ParseRes.res(new VariableDeclareNode(loc, res), n);
} }
else return end.chainError(src.loc(i + n), "Expected a comma or end of statement"); else return end.chainError(src.loc(i + n), "Expected a comma or end of statement");
} }
} }
} }

View File

@@ -12,46 +12,47 @@ import me.topchetoeu.jscript.compilation.LabelContext;
import me.topchetoeu.jscript.compilation.Node; import me.topchetoeu.jscript.compilation.Node;
public class BreakNode extends Node { public class BreakNode extends Node {
public final String label; public final String label;
@Override public void compile(CompileResult target, boolean pollute) { @Override public void compileFunctions(CompileResult target) {
var res = LabelContext.getBreak(target.env).getJump(); }
if (res == null) {
if (label != null) throw new SyntaxException(loc(), String.format("Undefined label '%s'", label));
else throw new SyntaxException(loc(), "Illegal break statement");
}
target.add(res);
if (pollute) target.add(Instruction.pushUndefined()); @Override public void compile(CompileResult target, boolean pollute) {
} if (!LabelContext.getBreak(target.env).jump(target, label)) {
if (label != null) throw new SyntaxException(loc(), String.format("Undefined label '%s'", label));
else throw new SyntaxException(loc(), "Illegal break statement");
}
public BreakNode(Location loc, String label) { if (pollute) target.add(Instruction.pushUndefined());
super(loc); }
this.label = label;
}
public static ParseRes<BreakNode> parse(Source src, int i) { public BreakNode(Location loc, String label) {
var n = Parsing.skipEmpty(src, i); super(loc);
var loc = src.loc(i + n); this.label = label;
}
if (!Parsing.isIdentifier(src, i + n, "break")) return ParseRes.failed(); public static ParseRes<BreakNode> parse(Source src, int i) {
n += 5; var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var end = JavaScript.parseStatementEnd(src, i + n); if (!Parsing.isIdentifier(src, i + n, "break")) return ParseRes.failed();
if (end.isSuccess()) { n += 5;
n += end.n;
return ParseRes.res(new BreakNode(loc, null), n);
}
var label = Parsing.parseIdentifier(src, i + n); var end = JavaScript.parseStatementEnd(src, i + n);
if (label.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a label name or an end of statement"); if (end.isSuccess()) {
n += label.n; n += end.n;
return ParseRes.res(new BreakNode(loc, null), n);
}
end = JavaScript.parseStatementEnd(src, i + n); var label = Parsing.parseIdentifier(src, i + n);
if (end.isSuccess()) { if (label.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a label name or an end of statement");
n += end.n; n += label.n;
return ParseRes.res(new BreakNode(loc, label.result), n);
} end = JavaScript.parseStatementEnd(src, i + n);
else return end.chainError(src.loc(i + n), "Expected end of statement"); if (end.isSuccess()) {
} n += end.n;
return ParseRes.res(new BreakNode(loc, label.result), n);
}
else return end.chainError(src.loc(i + n), "Expected end of statement");
}
} }

View File

@@ -12,46 +12,47 @@ import me.topchetoeu.jscript.compilation.LabelContext;
import me.topchetoeu.jscript.compilation.Node; import me.topchetoeu.jscript.compilation.Node;
public class ContinueNode extends Node { public class ContinueNode extends Node {
public final String label; public final String label;
@Override public void compile(CompileResult target, boolean pollute) { @Override public void compileFunctions(CompileResult target) {
var res = LabelContext.getCont(target.env).getJump(); }
if (res == null) {
if (label != null) throw new SyntaxException(loc(), String.format("Undefined label '%s'", label));
else throw new SyntaxException(loc(), "Illegal continue statement");
}
target.add(res);
if (pollute) target.add(Instruction.pushUndefined()); @Override public void compile(CompileResult target, boolean pollute) {
} if (!LabelContext.getCont(target.env).jump(target)) {
if (label != null) throw new SyntaxException(loc(), String.format("Undefined label '%s'", label));
else throw new SyntaxException(loc(), "Illegal continue statement");
}
public ContinueNode(Location loc, String label) { if (pollute) target.add(Instruction.pushUndefined());
super(loc); }
this.label = label;
}
public static ParseRes<ContinueNode> parse(Source src, int i) { public ContinueNode(Location loc, String label) {
var n = Parsing.skipEmpty(src, i); super(loc);
var loc = src.loc(i + n); this.label = label;
}
if (!Parsing.isIdentifier(src, i + n, "continue")) return ParseRes.failed(); public static ParseRes<ContinueNode> parse(Source src, int i) {
n += 8; var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var end = JavaScript.parseStatementEnd(src, i + n); if (!Parsing.isIdentifier(src, i + n, "continue")) return ParseRes.failed();
if (end.isSuccess()) { n += 8;
n += end.n;
return ParseRes.res(new ContinueNode(loc, null), n);
}
var label = Parsing.parseIdentifier(src, i + n); var end = JavaScript.parseStatementEnd(src, i + n);
if (label.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a label name or an end of statement"); if (end.isSuccess()) {
n += label.n; n += end.n;
return ParseRes.res(new ContinueNode(loc, null), n);
}
end = JavaScript.parseStatementEnd(src, i + n); var label = Parsing.parseIdentifier(src, i + n);
if (end.isSuccess()) { if (label.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a label name or an end of statement");
n += end.n; n += label.n;
return ParseRes.res(new ContinueNode(loc, label.result), n);
} end = JavaScript.parseStatementEnd(src, i + n);
else return end.chainError(src.loc(i + n), "Expected end of statement"); if (end.isSuccess()) {
} n += end.n;
return ParseRes.res(new ContinueNode(loc, label.result), n);
}
else return end.chainError(src.loc(i + n), "Expected end of statement");
}
} }

View File

@@ -10,28 +10,31 @@ import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.Node; import me.topchetoeu.jscript.compilation.Node;
public class DebugNode extends Node { public class DebugNode extends Node {
@Override public void compile(CompileResult target, boolean pollute) { @Override public void compileFunctions(CompileResult target) {
target.add(Instruction.debug()); }
if (pollute) target.add(Instruction.pushUndefined());
}
public DebugNode(Location loc) { @Override public void compile(CompileResult target, boolean pollute) {
super(loc); target.add(Instruction.debug());
} if (pollute) target.add(Instruction.pushUndefined());
}
public static ParseRes<DebugNode> parse(Source src, int i) { public DebugNode(Location loc) {
var n = Parsing.skipEmpty(src, i); super(loc);
var loc = src.loc(i + n); }
if (!Parsing.isIdentifier(src, i + n, "debugger")) return ParseRes.failed(); public static ParseRes<DebugNode> parse(Source src, int i) {
n += 8; var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var end = JavaScript.parseStatementEnd(src, i + n); if (!Parsing.isIdentifier(src, i + n, "debugger")) return ParseRes.failed();
if (end.isSuccess()) { n += 8;
n += end.n;
return ParseRes.res(new DebugNode(loc), n); var end = JavaScript.parseStatementEnd(src, i + n);
} if (end.isSuccess()) {
else return end.chainError(src.loc(i + n), "Expected end of statement"); n += end.n;
} return ParseRes.res(new DebugNode(loc), n);
}
else return end.chainError(src.loc(i + n), "Expected end of statement");
}
} }

View File

@@ -13,41 +13,45 @@ import me.topchetoeu.jscript.compilation.values.constants.BoolNode;
import me.topchetoeu.jscript.compilation.values.operations.IndexNode; import me.topchetoeu.jscript.compilation.values.operations.IndexNode;
public class DeleteNode extends Node { public class DeleteNode extends Node {
public final Node key; public final Node key;
public final Node value; public final Node value;
@Override public void compile(CompileResult target, boolean pollute) { @Override public void compileFunctions(CompileResult target) {
value.compile(target, true); key.compileFunctions(target);
key.compile(target, true); value.compileFunctions(target);
}
@Override public void compile(CompileResult target, boolean pollute) {
value.compile(target, true);
key.compile(target, true);
target.add(Instruction.delete()); target.add(Instruction.delete());
if (pollute) target.add(Instruction.pushValue(true)); if (pollute) target.add(Instruction.pushValue(true));
} }
public static ParseRes<? extends Node> parse(Source src, int i) { public static ParseRes<? extends Node> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i); var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n); var loc = src.loc(i + n);
if (!Parsing.isIdentifier(src, i + n, "delete")) return ParseRes.failed(); if (!Parsing.isIdentifier(src, i + n, "delete")) return ParseRes.failed();
n += 6; n += 6;
var valRes = JavaScript.parseExpression(src, i + n, 15); var valRes = JavaScript.parseExpression(src, i + n, 15);
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'delete'"); if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'delete'");
n += valRes.n; n += valRes.n;
if (valRes.result instanceof IndexNode) { if (valRes.result instanceof IndexNode) {
var index = (IndexNode)valRes.result; var index = (IndexNode)valRes.result;
return ParseRes.res(new DeleteNode(loc, index.index, index.object), n); return ParseRes.res(new DeleteNode(loc, index.index, index.object), n);
} }
else if (valRes.result instanceof VariableNode) { else if (valRes.result instanceof VariableNode) {
return ParseRes.error(src.loc(i + n), "A variable may not be deleted"); return ParseRes.error(src.loc(i + n), "A variable may not be deleted");
} }
else return ParseRes.res(new BoolNode(loc, true), n); else return ParseRes.res(new BoolNode(loc, true), n);
} }
public DeleteNode(Location loc, Node key, Node value) { public DeleteNode(Location loc, Node key, Node value) {
super(loc); super(loc);
this.key = key; this.key = key;
this.value = value; this.value = value;
} }
} }

View File

@@ -13,72 +13,76 @@ import me.topchetoeu.jscript.compilation.LabelContext;
import me.topchetoeu.jscript.compilation.Node; import me.topchetoeu.jscript.compilation.Node;
public class DoWhileNode extends Node { public class DoWhileNode extends Node {
public final Node condition, body; public final Node condition, body;
public final String label; public final String label;
@Override public void resolve(CompileResult target) { @Override public void compileFunctions(CompileResult target) {
body.resolve(target); condition.compileFunctions(target);
} body.compileFunctions(target);
}
@Override public void resolve(CompileResult target) {
body.resolve(target);
}
@Override public void compile(CompileResult target, boolean pollute) { @Override public void compile(CompileResult target, boolean pollute) {
int start = target.size(); int start = target.size();
var end = new DeferredIntSupplier(); var end = new DeferredIntSupplier();
var mid = new DeferredIntSupplier();
LabelContext.pushLoop(target.env, loc(), label, end, start); LabelContext.pushLoop(target.env, loc(), label, end, start);
body.compile(target, false, BreakpointType.STEP_OVER); body.compile(target, false, BreakpointType.STEP_OVER);
LabelContext.popLoop(target.env, label);
mid.set(target.size()); condition.compile(target, true, BreakpointType.STEP_OVER);
condition.compile(target, true, BreakpointType.STEP_OVER); int endI = target.size();
int endI = target.size(); end.set(endI + 1);
end.set(endI + 1);
target.add(Instruction.jmpIf(start - endI)); LabelContext.popLoop(target.env, label);
}
public DoWhileNode(Location loc, String label, Node condition, Node body) { target.add(Instruction.jmpIf(start - endI));
super(loc); }
this.label = label;
this.condition = condition;
this.body = body;
}
public static ParseRes<DoWhileNode> parse(Source src, int i) { public DoWhileNode(Location loc, String label, Node condition, Node body) {
var n = Parsing.skipEmpty(src, i); super(loc);
var loc = src.loc(i + n); this.label = label;
this.condition = condition;
this.body = body;
}
var labelRes = JavaScript.parseLabel(src, i + n); public static ParseRes<DoWhileNode> parse(Source src, int i) {
n += labelRes.n; var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!Parsing.isIdentifier(src, i + n, "do")) return ParseRes.failed(); var labelRes = JavaScript.parseLabel(src, i + n);
n += 2; n += labelRes.n;
var bodyRes = JavaScript.parseStatement(src, i + n); if (!Parsing.isIdentifier(src, i + n, "do")) return ParseRes.failed();
if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a do-while body."); n += 2;
n += bodyRes.n;
if (!Parsing.isIdentifier(src, i + n, "while")) return ParseRes.failed(); var bodyRes = JavaScript.parseStatement(src, i + n);
n += 5; if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a do-while body.");
n += Parsing.skipEmpty(src, i + n); n += bodyRes.n;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'while'."); if (!Parsing.isIdentifier(src, i + n, "while")) return ParseRes.failed();
n++; n += 5;
n += Parsing.skipEmpty(src, i + n);
var condRes = JavaScript.parseExpression(src, i + n, 0); if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'while'.");
if (!condRes.isSuccess()) return condRes.chainError(src.loc(i + n), "Expected a do-while condition."); n++;
n += condRes.n;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after do-while condition."); var condRes = JavaScript.parseExpression(src, i + n, 0);
n++; if (!condRes.isSuccess()) return condRes.chainError(src.loc(i + n), "Expected a do-while condition.");
n += condRes.n;
n += Parsing.skipEmpty(src, i + n);
var end = JavaScript.parseStatementEnd(src, i + n); if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after do-while condition.");
if (end.isSuccess()) { n++;
n += end.n;
return ParseRes.res(new DoWhileNode(loc, labelRes.result, condRes.result, bodyRes.result), n); var end = JavaScript.parseStatementEnd(src, i + n);
} if (end.isSuccess()) {
else return end.chainError(src.loc(i + n), "Expected end of statement"); n += end.n;
} return ParseRes.res(new DoWhileNode(loc, labelRes.result, condRes.result, bodyRes.result), n);
}
else return end.chainError(src.loc(i + n), "Expected end of statement");
}
} }

View File

@@ -7,95 +7,108 @@ import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.CompoundNode;
import me.topchetoeu.jscript.compilation.DeferredIntSupplier;
import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.LabelContext; import me.topchetoeu.jscript.compilation.LabelContext;
import me.topchetoeu.jscript.compilation.DeferredIntSupplier;
import me.topchetoeu.jscript.compilation.Node; import me.topchetoeu.jscript.compilation.Node;
import me.topchetoeu.jscript.compilation.patterns.Binding; import me.topchetoeu.jscript.compilation.values.VariableNode;
public class ForInNode extends Node { public class ForInNode extends Node {
public final Binding binding; public final boolean isDecl;
public final Node object, body; public final VariableNode binding;
public final String label; public final Node object, body;
public final String label;
@Override public void resolve(CompileResult target) { @Override public void resolve(CompileResult target) {
body.resolve(target); body.resolve(target);
binding.resolve(target); binding.resolve(target);
} }
@Override public void compile(CompileResult target, boolean pollute) { @Override public void compileFunctions(CompileResult target) {
binding.declareLateInit(target); object.compileFunctions(target);
body.compileFunctions(target);
}
@Override public void compile(CompileResult target, boolean pollute) {
object.compile(target, true, BreakpointType.STEP_OVER);
target.add(Instruction.keys(false, true));
object.compile(target, true, BreakpointType.STEP_OVER); int start = target.size();
target.add(Instruction.keys(false, true)); target.add(Instruction.dup());
int mid = target.temp();
int start = target.size(); target.add(Instruction.loadMember("value")).setLocation(binding.loc());
target.add(Instruction.dup()); target.add(VariableNode.toSet(target, loc(), binding.name, false, true)).setLocation(binding.loc());
int mid = target.temp();
target.add(Instruction.loadMember("value")).setLocation(binding.loc()); target.setLocationAndDebug(object.loc(), BreakpointType.STEP_OVER);
binding.assign(target, false);
target.setLocationAndDebug(object.loc(), BreakpointType.STEP_OVER);
var end = new DeferredIntSupplier(); var end = new DeferredIntSupplier();
LabelContext.pushLoop(target.env, loc(), label, end, start); LabelContext.pushLoop(target.env, loc(), label, end, start);
CompoundNode.compileMultiEntry(body, target, false, BreakpointType.STEP_OVER); body.compile(target, false, BreakpointType.STEP_OVER);
LabelContext.popLoop(target.env, label);
int endI = target.size(); int endI = target.size();
target.add(Instruction.jmp(start - endI)); target.add(Instruction.jmp(start - endI));
target.add(Instruction.discard()); target.add(Instruction.discard());
target.set(mid, Instruction.jmpIfNot(endI - mid + 1)); target.set(mid, Instruction.jmpIfNot(endI - mid + 1));
if (pollute) target.add(Instruction.pushUndefined());
}
public ForInNode(Location loc, String label, Binding binding, Node object, Node body) { end.set(endI + 1);
super(loc); LabelContext.popLoop(target.env, label);
this.label = label;
this.binding = binding;
this.object = object;
this.body = body;
}
public static ParseRes<ForInNode> parse(Source src, int i) { if (pollute) target.add(Instruction.pushUndefined());
var n = Parsing.skipEmpty(src, i); }
var loc = src.loc(i + n);
var label = JavaScript.parseLabel(src, i + n); public ForInNode(Location loc, String label, VariableNode binding, boolean isDecl, Node object, Node body) {
n += label.n; super(loc);
n += Parsing.skipEmpty(src, i + n); this.label = label;
this.binding = binding;
this.isDecl = isDecl;
this.object = object;
this.body = body;
}
if (!Parsing.isIdentifier(src, i + n, "for")) return ParseRes.failed(); public static ParseRes<ForInNode> parse(Source src, int i) {
n += 3; var n = Parsing.skipEmpty(src, i);
n += Parsing.skipEmpty(src, i + n); var loc = src.loc(i + n);
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected an opening paren"); var label = JavaScript.parseLabel(src, i + n);
n++; n += label.n;
n += Parsing.skipEmpty(src, i + n); n += Parsing.skipEmpty(src, i + n);
var binding = Binding.parse(src, i + n); if (!Parsing.isIdentifier(src, i + n, "for")) return ParseRes.failed();
if (!binding.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a binding in for-in loop"); n += 3;
n += binding.n; n += Parsing.skipEmpty(src, i + n);
n += Parsing.skipEmpty(src, i + n);
if (!Parsing.isIdentifier(src, i + n, "in")) return ParseRes.error(src.loc(i + n), "Expected 'in' keyword after variable declaration"); if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected an opening paren");
n += 2; n++;
n += Parsing.skipEmpty(src, i + n);
var obj = JavaScript.parseExpression(src, i + n, 0); var varKw = JavaScript.parseDeclarationType(src, i + n);
if (!obj.isSuccess()) return obj.chainError(src.loc(i + n), "Expected a value"); n += varKw.n;
n += obj.n; n += Parsing.skipEmpty(src, i + n);
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren"); var bindingLoc = src.loc(i + n);
n++;
var bodyRes = JavaScript.parseStatement(src, i + n); var name = Parsing.parseIdentifier(src, i + n);
if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a for-in body"); if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a variable name");
n += bodyRes.n; n += name.n;
n += Parsing.skipEmpty(src, i + n);
return ParseRes.res(new ForInNode(loc, label.result, binding.result, obj.result, bodyRes.result), n); if (!Parsing.isIdentifier(src, i + n, "in")) return ParseRes.error(src.loc(i + n), "Expected 'in' keyword after variable declaration");
} n += 2;
var obj = JavaScript.parseExpression(src, i + n, 0);
if (!obj.isSuccess()) return obj.chainError(src.loc(i + n), "Expected a value");
n += obj.n;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren");
n++;
var bodyRes = JavaScript.parseStatement(src, i + n);
if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a for-in body");
n += bodyRes.n;
return ParseRes.res(new ForInNode(loc, label.result, new VariableNode(bindingLoc, name.result), varKw.isSuccess(), obj.result, bodyRes.result), n);
}
} }

View File

@@ -7,125 +7,126 @@ import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.CompoundNode;
import me.topchetoeu.jscript.compilation.DeferredIntSupplier; import me.topchetoeu.jscript.compilation.DeferredIntSupplier;
import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.LabelContext; import me.topchetoeu.jscript.compilation.LabelContext;
import me.topchetoeu.jscript.compilation.Node; import me.topchetoeu.jscript.compilation.Node;
import me.topchetoeu.jscript.compilation.VariableDeclareNode; import me.topchetoeu.jscript.compilation.VariableDeclareNode;
import me.topchetoeu.jscript.compilation.values.operations.DiscardNode;
public class ForNode extends Node { public class ForNode extends Node {
public final Node declaration, assignment, condition, body; public final Node declaration, assignment, condition, body;
public final String label; public final String label;
@Override public void resolve(CompileResult target) { @Override public void resolve(CompileResult target) {
declaration.resolve(target); if (declaration != null) declaration.resolve(target);
body.resolve(target); body.resolve(target);
} }
@Override public void compile(CompileResult target, boolean pollute) { @Override public void compileFunctions(CompileResult target) {
var subtarget = target.subtarget(); if (declaration != null) declaration.compileFunctions(target);
subtarget.scope.singleEntry = false; if (assignment != null) assignment.compileFunctions(target);
subtarget.beginScope(); if (condition != null) condition.compileFunctions(target);
body.compileFunctions(target);
}
@Override public void compile(CompileResult target, boolean pollute) {
if (declaration != null) declaration.compile(target, false, BreakpointType.STEP_OVER);
declaration.compile(subtarget, false, BreakpointType.STEP_OVER); var continueTarget = new DeferredIntSupplier();
int start = target.size();
int mid = -1;
if (condition != null) {
condition.compile(target, true, BreakpointType.STEP_OVER);
mid = target.temp();
}
int start = subtarget.size(); var end = new DeferredIntSupplier();
CompoundNode.compileMultiEntry(condition, subtarget, true, BreakpointType.STEP_OVER);
int mid = subtarget.temp();
var end = new DeferredIntSupplier(); LabelContext.pushLoop(target.env, loc(), label, end, continueTarget);
body.compile(target, false, BreakpointType.STEP_OVER);
LabelContext.pushLoop(subtarget.env, loc(), label, end, start); continueTarget.set(target.size());
CompoundNode.compileMultiEntry(body, subtarget, false, BreakpointType.STEP_OVER); if (assignment != null) assignment.compile(target, false, BreakpointType.STEP_OVER);
LabelContext.popLoop(subtarget.env, label); int endI = target.size();
subtarget.reallocScope(); end.set(endI + 1);
LabelContext.popLoop(target.env, label);
CompoundNode.compileMultiEntry(assignment, subtarget, false, BreakpointType.STEP_OVER); target.add(Instruction.jmp(start - endI));
int endI = subtarget.size(); if (condition != null) target.set(mid, Instruction.jmpIfNot(endI - mid + 1));
if (pollute) target.add(Instruction.pushUndefined());
}
end.set(endI); public ForNode(Location loc, String label, Node declaration, Node condition, Node assignment, Node body) {
super(loc);
this.label = label;
this.declaration = declaration;
this.condition = condition;
this.assignment = assignment;
this.body = body;
}
subtarget.add(Instruction.jmp(start - endI)); private static ParseRes<Node> parseSemicolon(Source src, int i) {
subtarget.set(mid, Instruction.jmpIfNot(endI - mid + 1)); var n = Parsing.skipEmpty(src, i);
if (pollute) subtarget.add(Instruction.pushUndefined());
subtarget.endScope(); if (!src.is(i + n, ";")) return ParseRes.failed();
} else return ParseRes.res(null, n + 1);
}
private static ParseRes<Node> parseCondition(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
public ForNode(Location loc, String label, Node declaration, Node condition, Node assignment, Node body) { var res = JavaScript.parseExpression(src, i + n, 0);
super(loc); if (!res.isSuccess()) return res.chainError();
this.label = label; n += res.n;
this.declaration = declaration; n += Parsing.skipEmpty(src, i + n);
this.condition = condition;
this.assignment = assignment;
this.body = body;
}
private static ParseRes<Node> parseSemicolon(Source src, int i) { if (!src.is(i + n, ";")) return ParseRes.error(src.loc(i + n), "Expected a semicolon");
var n = Parsing.skipEmpty(src, i); else return ParseRes.res(res.result, n + 1);
}
private static ParseRes<? extends Node> parseUpdater(Source src, int i) {
return JavaScript.parseExpression(src, i, 0);
}
if (!src.is(i + n, ";")) return ParseRes.failed(); public static ParseRes<ForNode> parse(Source src, int i) {
else return ParseRes.res(new DiscardNode(src.loc(i), null), n + 1); var n = Parsing.skipEmpty(src, i);
} var loc = src.loc(i + n);
private static ParseRes<Node> parseCondition(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var res = JavaScript.parseExpression(src, i + n, 0); var labelRes = JavaScript.parseLabel(src, i + n);
if (!res.isSuccess()) return res.chainError(); n += labelRes.n;
n += res.n; n += Parsing.skipEmpty(src, i + n);
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, ";")) return ParseRes.error(src.loc(i + n), "Expected a semicolon"); if (!Parsing.isIdentifier(src, i + n, "for")) return ParseRes.failed();
else return ParseRes.res(res.result, n + 1); n += 3;
} n += Parsing.skipEmpty(src, i + n);
private static ParseRes<? extends Node> parseUpdater(Source src, int i) {
return JavaScript.parseExpression(src, i, 0);
}
public static ParseRes<ForNode> parse(Source src, int i) { if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'for'");
var n = Parsing.skipEmpty(src, i); n++;
var loc = src.loc(i + n);
var labelRes = JavaScript.parseLabel(src, i + n); ParseRes<Node> decl = ParseRes.first(src, i + n,
n += labelRes.n; ForNode::parseSemicolon,
n += Parsing.skipEmpty(src, i + n); VariableDeclareNode::parse,
ForNode::parseCondition
);
if (!decl.isSuccess()) return decl.chainError(src.loc(i + n), "Expected a declaration or an expression");
n += decl.n;
if (!Parsing.isIdentifier(src, i + n, "for")) return ParseRes.failed(); ParseRes<Node> cond = ParseRes.first(src, i + n,
n += 3; ForNode::parseSemicolon,
n += Parsing.skipEmpty(src, i + n); ForNode::parseCondition
);
if (!cond.isSuccess()) return cond.chainError(src.loc(i + n), "Expected a condition");
n += cond.n;
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'for'"); var update = parseUpdater(src, i + n);
n++; if (update.isError()) return update.chainError();
n += update.n;
n += Parsing.skipEmpty(src, i + n);
ParseRes<Node> decl = ParseRes.first(src, i + n, if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a close paren after for updater");
ForNode::parseSemicolon, n++;
VariableDeclareNode::parse,
ForNode::parseCondition
);
if (!decl.isSuccess()) return decl.chainError(src.loc(i + n), "Expected a declaration or an expression");
n += decl.n;
ParseRes<Node> cond = ParseRes.first(src, i + n, var body = JavaScript.parseStatement(src, i + n);
ForNode::parseSemicolon, if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a for body.");
ForNode::parseCondition n += body.n;
);
if (!cond.isSuccess()) return cond.chainError(src.loc(i + n), "Expected a condition");
n += cond.n;
var update = parseUpdater(src, i + n); return ParseRes.res(new ForNode(loc, labelRes.result, decl.result, cond.result, update.result, body.result), n);
if (update.isError()) return update.chainError(); }
n += update.n;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a close paren after for updater");
n++;
var body = JavaScript.parseStatement(src, i + n);
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a for body.");
n += body.n;
return ParseRes.res(new ForNode(loc, labelRes.result, decl.result, cond.result, update.result, body.result), n);
}
} }

View File

@@ -1,109 +0,0 @@
package me.topchetoeu.jscript.compilation.control;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.CompoundNode;
import me.topchetoeu.jscript.compilation.DeferredIntSupplier;
import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.LabelContext;
import me.topchetoeu.jscript.compilation.Node;
import me.topchetoeu.jscript.compilation.patterns.Binding;
public class ForOfNode extends Node {
public final Binding binding;
public final Node iterable, body;
public final String label;
@Override public void resolve(CompileResult target) {
body.resolve(target);
binding.resolve(target);
}
@Override public void compile(CompileResult target, boolean pollute) {
binding.declareLateInit(target);
iterable.compile(target, true, BreakpointType.STEP_OVER);
target.add(Instruction.dup());
target.add(Instruction.loadIntrinsics("it_key"));
target.add(Instruction.loadMember()).setLocation(iterable.loc());
target.add(Instruction.call(0, true)).setLocation(iterable.loc());
int start = target.size();
target.add(Instruction.dup(2, 0));
target.add(Instruction.loadMember("next")).setLocation(iterable.loc());
target.add(Instruction.call(0, true)).setLocation(iterable.loc());
target.add(Instruction.dup());
target.add(Instruction.loadMember("done")).setLocation(iterable.loc());
int mid = target.temp();
target.add(Instruction.loadMember("value")).setLocation(binding.loc);
binding.assign(target, false);
var end = new DeferredIntSupplier();
LabelContext.pushLoop(target.env, loc(), label, end, start);
CompoundNode.compileMultiEntry(body, target, false, BreakpointType.STEP_OVER);
LabelContext.popLoop(target.env, label);
int endI = target.size();
end.set(endI);
target.add(Instruction.jmp(start - endI));
target.add(Instruction.discard());
target.add(Instruction.discard());
target.set(mid, Instruction.jmpIf(endI - mid + 1));
if (pollute) target.add(Instruction.pushUndefined());
}
public ForOfNode(Location loc, String label, Binding binding, Node object, Node body) {
super(loc);
this.label = label;
this.binding = binding;
this.iterable = object;
this.body = body;
}
public static ParseRes<ForOfNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var label = JavaScript.parseLabel(src, i + n);
n += label.n;
n += Parsing.skipEmpty(src, i + n);
if (!Parsing.isIdentifier(src, i + n, "for")) return ParseRes.failed();
n += 3;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected an opening paren");
n++;
n += Parsing.skipEmpty(src, i + n);
var binding = Binding.parse(src, i + n);
if (!binding.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a binding in for-of loop");
n += binding.n;
n += Parsing.skipEmpty(src, i + n);
if (!Parsing.isIdentifier(src, i + n, "of")) return ParseRes.error(src.loc(i + n), "Expected 'of' keyword after variable declaration");
n += 2;
var obj = JavaScript.parseExpression(src, i + n, 0);
if (!obj.isSuccess()) return obj.chainError(src.loc(i + n), "Expected a value");
n += obj.n;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren");
n++;
var bodyRes = JavaScript.parseStatement(src, i + n);
if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a for-of body");
n += bodyRes.n;
return ParseRes.res(new ForOfNode(loc, label.result, binding.result, obj.result, bodyRes.result), n);
}
}

View File

@@ -13,119 +13,123 @@ import me.topchetoeu.jscript.compilation.LabelContext;
import me.topchetoeu.jscript.compilation.Node; import me.topchetoeu.jscript.compilation.Node;
public class IfNode extends Node { public class IfNode extends Node {
public final Node condition, body, elseBody; public final Node condition, body, elseBody;
public final String label; public final String label;
@Override public void resolve(CompileResult target) { @Override public void resolve(CompileResult target) {
body.resolve(target); body.resolve(target);
if (elseBody != null) elseBody.resolve(target); if (elseBody != null) elseBody.resolve(target);
} }
@Override public void compileFunctions(CompileResult target) {
condition.compileFunctions(target);
body.compileFunctions(target);
if (elseBody != null) elseBody.compileFunctions(target);
}
@Override public void compile(CompileResult target, boolean pollute, BreakpointType breakpoint) {
condition.compile(target, true, breakpoint);
@Override public void compile(CompileResult target, boolean pollute, BreakpointType breakpoint) { if (elseBody == null) {
condition.compile(target, true, breakpoint); int start = target.temp();
var end = new DeferredIntSupplier();
if (elseBody == null) { LabelContext.getBreak(target.env).push(loc(), label, end);
int start = target.temp(); body.compile(target, pollute, BreakpointType.STEP_OVER);
var end = new DeferredIntSupplier(); LabelContext.getBreak(target.env).pop(label);
LabelContext.getBreak(target.env).push(loc(), label, end); int endI = target.size();
body.compile(target, false, BreakpointType.STEP_OVER); end.set(endI);
LabelContext.getBreak(target.env).pop(label);
int endI = target.size(); target.set(start, Instruction.jmpIfNot(endI - start));
end.set(endI); }
else {
int start = target.temp();
var end = new DeferredIntSupplier();
target.set(start, Instruction.jmpIfNot(endI - start)); LabelContext.getBreak(target.env).push(loc(), label, end);
} body.compile(target, pollute, BreakpointType.STEP_OVER);
else {
int start = target.temp();
var end = new DeferredIntSupplier();
LabelContext.getBreak(target.env).push(loc(), label, end); int mid = target.temp();
body.compile(target, false, BreakpointType.STEP_OVER);
int mid = target.temp(); elseBody.compile(target, pollute, BreakpointType.STEP_OVER);
LabelContext.getBreak(target.env).pop(label);
elseBody.compile(target, false, BreakpointType.STEP_OVER); int endI = target.size();
LabelContext.getBreak(target.env).pop(label); end.set(endI);
int endI = target.size(); target.set(start, Instruction.jmpIfNot(mid - start + 1));
end.set(endI); target.set(mid, Instruction.jmp(endI - mid));
}
}
@Override public void compile(CompileResult target, boolean pollute) {
compile(target, pollute, BreakpointType.STEP_IN);
}
target.set(start, Instruction.jmpIfNot(mid - start + 1)); public IfNode(Location loc, Node condition, Node body, Node elseBody, String label) {
target.set(mid, Instruction.jmp(endI - mid)); super(loc);
} this.condition = condition;
} this.body = body;
@Override public void compile(CompileResult target, boolean pollute) { this.elseBody = elseBody;
compile(target, pollute, BreakpointType.STEP_IN); this.label = label;
} }
public IfNode(Location loc, Node condition, Node body, Node elseBody, String label) { public static ParseRes<IfNode> parseTernary(Source src, int i, Node prev, int precedence) {
super(loc); if (precedence > 2) return ParseRes.failed();
this.condition = condition;
this.body = body;
this.elseBody = elseBody;
this.label = label;
}
public static ParseRes<IfNode> parseTernary(Source src, int i, Node prev, int precedence) { var n = Parsing.skipEmpty(src, i);
if (precedence > 2) return ParseRes.failed();
var n = Parsing.skipEmpty(src, i); if (!src.is(i + n, "?")) return ParseRes.failed();
var loc = src.loc(i + n);
n++;
if (!src.is(i + n, "?")) return ParseRes.failed(); var a = JavaScript.parseExpression(src, i + n, 2);
var loc = src.loc(i + n); if (!a.isSuccess()) return a.chainError(src.loc(i + n), "Expected a value after the ternary operator.");
n++; n += a.n;
n += Parsing.skipEmpty(src, i + n);
var a = JavaScript.parseExpression(src, i + n, 2); if (!src.is(i + n, ":")) return ParseRes.failed();
if (!a.isSuccess()) return a.chainError(src.loc(i + n), "Expected a value after the ternary operator."); n++;
n += a.n;
n += Parsing.skipEmpty(src, i);
if (!src.is(i + n, ":")) return ParseRes.failed(); var b = JavaScript.parseExpression(src, i + n, 2);
n++; if (!b.isSuccess()) return b.chainError(src.loc(i + n), "Expected a second value after the ternary operator.");
n += b.n;
var b = JavaScript.parseExpression(src, i + n, 2); return ParseRes.res(new IfNode(loc, prev, a.result, b.result, null), n);
if (!b.isSuccess()) return b.chainError(src.loc(i + n), "Expected a second value after the ternary operator."); }
n += b.n; public static ParseRes<IfNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
return ParseRes.res(new IfNode(loc, prev, a.result, b.result, null), n); var label = JavaScript.parseLabel(src, i + n);
} n += label.n;
public static ParseRes<IfNode> parse(Source src, int i) { n += Parsing.skipEmpty(src, i + n);
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var label = JavaScript.parseLabel(src, i + n); if (!Parsing.isIdentifier(src, i + n, "if")) return ParseRes.failed();
n += label.n; n += 2;
n += Parsing.skipEmpty(src, i + n); n += Parsing.skipEmpty(src, i + n);
if (!Parsing.isIdentifier(src, i + n, "if")) return ParseRes.failed(); if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'if'.");
n += 2; n++;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'if'."); var condRes = JavaScript.parseExpression(src, i + n, 0);
n++; if (!condRes.isSuccess()) return condRes.chainError(src.loc(i + n), "Expected an if condition.");
n += condRes.n;
n += Parsing.skipEmpty(src, i + n);
var condRes = JavaScript.parseExpression(src, i + n, 0); if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after if condition.");
if (!condRes.isSuccess()) return condRes.chainError(src.loc(i + n), "Expected an if condition."); n++;
n += condRes.n;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after if condition."); var res = JavaScript.parseStatement(src, i + n);
n++; if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an if body.");
n += res.n;
var res = JavaScript.parseStatement(src, i + n); var elseKw = Parsing.parseIdentifier(src, i + n, "else");
if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an if body."); if (!elseKw.isSuccess()) return ParseRes.res(new IfNode(loc, condRes.result, res.result, null, label.result), n);
n += res.n; n += elseKw.n;
var elseKw = Parsing.parseIdentifier(src, i + n, "else"); var elseRes = JavaScript.parseStatement(src, i + n);
if (!elseKw.isSuccess()) return ParseRes.res(new IfNode(loc, condRes.result, res.result, null, label.result), n); if (!elseRes.isSuccess()) return elseRes.chainError(src.loc(i + n), "Expected an else body.");
n += elseKw.n; n += elseRes.n;
var elseRes = JavaScript.parseStatement(src, i + n); return ParseRes.res(new IfNode(loc, condRes.result, res.result, elseRes.result, label.result), n);
if (!elseRes.isSuccess()) return elseRes.chainError(src.loc(i + n), "Expected an else body."); }
n += elseRes.n;
return ParseRes.res(new IfNode(loc, condRes.result, res.result, elseRes.result, label.result), n);
}
} }

View File

@@ -10,41 +10,44 @@ import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.Node; import me.topchetoeu.jscript.compilation.Node;
public class ReturnNode extends Node { public class ReturnNode extends Node {
public final Node value; public final Node value;
@Override public void compile(CompileResult target, boolean pollute) { @Override public void compileFunctions(CompileResult target) {
if (value == null) target.add(Instruction.pushUndefined()); if (value != null) value.compileFunctions(target);
else value.compile(target, true); }
target.add(Instruction.ret()).setLocation(loc()); @Override public void compile(CompileResult target, boolean pollute) {
} if (value == null) target.add(Instruction.pushUndefined());
else value.compile(target, true);
target.add(Instruction.ret()).setLocation(loc());
}
public ReturnNode(Location loc, Node value) { public ReturnNode(Location loc, Node value) {
super(loc); super(loc);
this.value = value; this.value = value;
} }
public static ParseRes<ReturnNode> parse(Source src, int i) { public static ParseRes<ReturnNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i); var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n); var loc = src.loc(i + n);
if (!Parsing.isIdentifier(src, i + n, "return")) return ParseRes.failed(); if (!Parsing.isIdentifier(src, i + n, "return")) return ParseRes.failed();
n += 6; n += 6;
var end = JavaScript.parseStatementEnd(src, i + n); var end = JavaScript.parseStatementEnd(src, i + n);
if (end.isSuccess()) { if (end.isSuccess()) {
n += end.n; n += end.n;
return ParseRes.res(new ReturnNode(loc, null), n); return ParseRes.res(new ReturnNode(loc, null), n);
} }
var val = JavaScript.parseExpression(src, i + n, 0); var val = JavaScript.parseExpression(src, i + n, 0);
if (val.isError()) return val.chainError(); if (val.isError()) return val.chainError();
n += val.n; n += val.n;
end = JavaScript.parseStatementEnd(src, i + n); end = JavaScript.parseStatementEnd(src, i + n);
if (end.isSuccess()) { if (end.isSuccess()) {
n += end.n; n += end.n;
return ParseRes.res(new ReturnNode(loc, val.result), n); return ParseRes.res(new ReturnNode(loc, val.result), n);
} }
else return end.chainError(src.loc(i + n), "Expected end of statement or a return value"); else return end.chainError(src.loc(i + n), "Expected end of statement or a return value");
} }
} }

View File

@@ -17,177 +17,182 @@ import me.topchetoeu.jscript.compilation.LabelContext;
import me.topchetoeu.jscript.compilation.Node; import me.topchetoeu.jscript.compilation.Node;
public class SwitchNode extends Node { public class SwitchNode extends Node {
public static class SwitchCase { public static class SwitchCase {
public final Node value; public final Node value;
public final int statementI; public final int statementI;
public SwitchCase(Node value, int statementI) { public SwitchCase(Node value, int statementI) {
this.value = value; this.value = value;
this.statementI = statementI; this.statementI = statementI;
} }
} }
public final Node value; public final Node value;
public final SwitchCase[] cases; public final SwitchCase[] cases;
public final Node[] body; public final Node[] body;
public final int defaultI; public final int defaultI;
public final String label; public final String label;
@Override public void resolve(CompileResult target) { @Override public void resolve(CompileResult target) {
for (var stm : body) stm.resolve(target); for (var stm : body) stm.resolve(target);
} }
@Override public void compileFunctions(CompileResult target) {
value.compileFunctions(target);
for (var _case : cases) {
_case.value.compileFunctions(target);
}
for (var stm : body) {
stm.compileFunctions(target);
}
}
@Override public void compile(CompileResult target, boolean pollute) {
var caseToStatement = new HashMap<Integer, Integer>();
var statementToIndex = new HashMap<Integer, Integer>();
@Override public void compile(CompileResult target, boolean pollute) { value.compile(target, true, BreakpointType.STEP_OVER);
var caseToStatement = new HashMap<Integer, Integer>();
var statementToIndex = new HashMap<Integer, Integer>();
value.compile(target, true, BreakpointType.STEP_OVER); // TODO: create a jump map
for (var ccase : cases) {
target.add(Instruction.dup());
ccase.value.compile(target, true);
target.add(Instruction.operation(Operation.EQUALS));
caseToStatement.put(target.temp(), ccase.statementI);
}
var subtarget = target.subtarget(); int start = target.temp();
subtarget.beginScope(); var end = new DeferredIntSupplier();
// TODO: create a jump map LabelContext.getBreak(target.env).pushLoop(loc(), label, end);
for (var ccase : cases) { for (var stm : body) {
subtarget.add(Instruction.dup()); statementToIndex.put(statementToIndex.size(), target.size());
ccase.value.compile(subtarget, true); stm.compile(target, false, BreakpointType.STEP_OVER);
subtarget.add(Instruction.operation(Operation.EQUALS)); }
caseToStatement.put(subtarget.temp(), ccase.statementI);
}
int start = subtarget.temp(); int endI = target.size();
var end = new DeferredIntSupplier(); end.set(endI);
LabelContext.getBreak(target.env).popLoop(label);
LabelContext.getBreak(target.env).push(loc(), label, end); target.add(Instruction.discard());
for (var stm : body) { if (pollute) target.add(Instruction.pushUndefined());
statementToIndex.put(statementToIndex.size(), subtarget.size());
stm.compile(subtarget, false, BreakpointType.STEP_OVER);
}
LabelContext.getBreak(target.env).pop(label);
subtarget.endScope(); if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(endI - start));
else target.set(start, Instruction.jmp(statementToIndex.get(defaultI) - start));
int endI = subtarget.size(); for (var el : caseToStatement.entrySet()) {
end.set(endI); var i = statementToIndex.get(el.getValue());
subtarget.add(Instruction.discard()); if (i == null) i = endI;
if (pollute) subtarget.add(Instruction.pushUndefined()); target.set(el.getKey(), Instruction.jmpIf(i - el.getKey()));
}
if (defaultI < 0 || defaultI >= body.length) subtarget.set(start, Instruction.jmp(endI - start)); }
else subtarget.set(start, Instruction.jmp(statementToIndex.get(defaultI) - start));
for (var el : caseToStatement.entrySet()) { public SwitchNode(Location loc, String label, Node value, int defaultI, SwitchCase[] cases, Node[] body) {
var i = statementToIndex.get(el.getValue()); super(loc);
if (i == null) i = endI; this.label = label;
subtarget.set(el.getKey(), Instruction.jmpIf(i - el.getKey())); this.value = value;
} this.defaultI = defaultI;
this.cases = cases;
this.body = body;
}
} private static ParseRes<Node> parseSwitchCase(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
public SwitchNode(Location loc, String label, Node value, int defaultI, SwitchCase[] cases, Node[] body) { if (!Parsing.isIdentifier(src, i + n, "case")) return ParseRes.failed();
super(loc); n += 4;
this.label = label;
this.value = value;
this.defaultI = defaultI;
this.cases = cases;
this.body = body;
}
private static ParseRes<Node> parseSwitchCase(Source src, int i) { var val = JavaScript.parseExpression(src, i + n, 0);
var n = Parsing.skipEmpty(src, i); if (!val.isSuccess()) return val.chainError(src.loc(i + n), "Expected a value after 'case'");
n += val.n;
n += Parsing.skipEmpty(src, i + n);
if (!Parsing.isIdentifier(src, i + n, "case")) return ParseRes.failed(); if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected colons after 'case' value");
n += 4; n++;
var val = JavaScript.parseExpression(src, i + n, 0); return ParseRes.res(val.result, n);
if (!val.isSuccess()) return val.chainError(src.loc(i + n), "Expected a value after 'case'"); }
n += val.n; private static ParseRes<Void> parseDefaultCase(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected colons after 'case' value"); if (!Parsing.isIdentifier(src, i + n, "default")) return ParseRes.failed();
n++; n += 7;
n += Parsing.skipEmpty(src, i + n);
return ParseRes.res(val.result, n); if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected colons after 'default'");
} n++;
private static ParseRes<Void> parseDefaultCase(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
if (!Parsing.isIdentifier(src, i + n, "default")) return ParseRes.failed(); return ParseRes.res(null, n);
n += 7; }
n += Parsing.skipEmpty(src, i + n); public static ParseRes<SwitchNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected colons after 'default'"); var label = JavaScript.parseLabel(src, i + n);
n++; n += label.n;
n += Parsing.skipEmpty(src, i + n);
return ParseRes.res(null, n); if (!Parsing.isIdentifier(src, i + n, "switch")) return ParseRes.failed();
} n += 6;
public static ParseRes<SwitchNode> parse(Source src, int i) { n += Parsing.skipEmpty(src, i + n);
var n = Parsing.skipEmpty(src, i); if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'switch'");
var loc = src.loc(i + n); n++;
var label = JavaScript.parseLabel(src, i + n); var val = JavaScript.parseExpression(src, i + n, 0);
n += label.n; if (!val.isSuccess()) return val.chainError(src.loc(i + n), "Expected a switch value");
n += Parsing.skipEmpty(src, i + n); n += val.n;
n += Parsing.skipEmpty(src, i + n);
if (!Parsing.isIdentifier(src, i + n, "switch")) return ParseRes.failed(); if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after switch value");
n += 6; n++;
n += Parsing.skipEmpty(src, i + n); n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'switch'");
n++;
var val = JavaScript.parseExpression(src, i + n, 0); if (!src.is(i + n, "{")) return ParseRes.error(src.loc(i + n), "Expected an opening brace after switch value");
if (!val.isSuccess()) return val.chainError(src.loc(i + n), "Expected a switch value"); n++;
n += val.n; n += Parsing.skipEmpty(src, i + n);
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after switch value"); var statements = new ArrayList<Node>();
n++; var cases = new ArrayList<SwitchCase>();
n += Parsing.skipEmpty(src, i + n); var defaultI = -1;
if (!src.is(i + n, "{")) return ParseRes.error(src.loc(i + n), "Expected an opening brace after switch value"); while (true) {
n++; n += Parsing.skipEmpty(src, i + n);
n += Parsing.skipEmpty(src, i + n);
var statements = new ArrayList<Node>(); if (src.is(i + n, "}")) {
var cases = new ArrayList<SwitchCase>(); n++;
var defaultI = -1; break;
}
if (src.is(i + n, ";")) {
n++;
continue;
}
while (true) { ParseRes<Node> caseRes = ParseRes.first(src, i + n,
n += Parsing.skipEmpty(src, i + n); SwitchNode::parseDefaultCase,
SwitchNode::parseSwitchCase
);
if (src.is(i + n, "}")) { if (caseRes.isSuccess()) {
n++; n += caseRes.n;
break;
}
if (src.is(i + n, ";")) {
n++;
continue;
}
ParseRes<Node> caseRes = ParseRes.first(src, i + n, if (caseRes.result == null) defaultI = statements.size();
SwitchNode::parseDefaultCase, else cases.add(new SwitchCase(caseRes.result, statements.size()));
SwitchNode::parseSwitchCase continue;
); }
if (caseRes.isError()) return caseRes.chainError();
if (caseRes.isSuccess()) { var stm = JavaScript.parseStatement(src, i + n);
n += caseRes.n; if (stm.isSuccess()) {
n += stm.n;
statements.add(stm.result);
continue;
}
else stm.chainError(src.loc(i + n), "Expected a statement, 'case' or 'default'");
}
if (caseRes.result == null) defaultI = statements.size(); return ParseRes.res(new SwitchNode(
else cases.add(new SwitchCase(caseRes.result, statements.size())); loc, label.result, val.result, defaultI,
continue; cases.toArray(new SwitchCase[0]),
} statements.toArray(new Node[0])
if (caseRes.isError()) return caseRes.chainError(); ), n);
}
var stm = JavaScript.parseStatement(src, i + n);
if (stm.isSuccess()) {
n += stm.n;
statements.add(stm.result);
continue;
}
else stm.chainError(src.loc(i + n), "Expected a statement, 'case' or 'default'");
}
return ParseRes.res(new SwitchNode(
loc, label.result, val.result, defaultI,
cases.toArray(new SwitchCase[0]),
statements.toArray(new Node[0])
), n);
}
} }

View File

@@ -10,40 +10,37 @@ import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.Node; import me.topchetoeu.jscript.compilation.Node;
public class ThrowNode extends Node { public class ThrowNode extends Node {
public final Node value; public final Node value;
@Override public void compile(CompileResult target, boolean pollute) { @Override public void compileFunctions(CompileResult target) {
value.compile(target, true); value.compileFunctions(target);
target.add(Instruction.throwInstr()).setLocation(loc()); }
} @Override public void compile(CompileResult target, boolean pollute) {
value.compile(target, true);
target.add(Instruction.throwInstr()).setLocation(loc());
}
public ThrowNode(Location loc, Node value) { public ThrowNode(Location loc, Node value) {
super(loc); super(loc);
this.value = value; this.value = value;
} }
public static ParseRes<ThrowNode> parse(Source src, int i) { public static ParseRes<ThrowNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i); var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n); var loc = src.loc(i + n);
if (!Parsing.isIdentifier(src, i + n, "throw")) return ParseRes.failed(); if (!Parsing.isIdentifier(src, i + n, "throw")) return ParseRes.failed();
n += 5; n += 5;
var end = JavaScript.parseStatementEnd(src, i + n); var val = JavaScript.parseExpression(src, i + n, 0);
if (end.isSuccess()) { if (val.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a throw value");
n += end.n; n += val.n;
return ParseRes.res(new ThrowNode(loc, null), n);
}
var val = JavaScript.parseExpression(src, i + n, 0); var end = JavaScript.parseStatementEnd(src, i + n);
if (val.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a value"); if (end.isSuccess()) {
n += val.n; n += end.n;
return ParseRes.res(new ThrowNode(loc, val.result), n);
end = JavaScript.parseStatementEnd(src, i + n); }
if (end.isSuccess()) { else return end.chainError(src.loc(i + n), "Expected end of statement");
n += end.n; }
return ParseRes.res(new ThrowNode(loc, val.result), n);
}
else return end.chainError(src.loc(i + n), "Expected end of statement");
}
} }

View File

@@ -12,131 +12,137 @@ import me.topchetoeu.jscript.compilation.DeferredIntSupplier;
import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.LabelContext; import me.topchetoeu.jscript.compilation.LabelContext;
import me.topchetoeu.jscript.compilation.Node; import me.topchetoeu.jscript.compilation.Node;
import me.topchetoeu.jscript.compilation.scope.Variable;
public class TryNode extends Node { public class TryNode extends Node {
public final CompoundNode tryBody; public final CompoundNode tryBody;
public final CompoundNode catchBody; public final CompoundNode catchBody;
public final CompoundNode finallyBody; public final CompoundNode finallyBody;
public final String captureName; public final String captureName;
public final String label; public final String label;
@Override public void resolve(CompileResult target) { @Override public void resolve(CompileResult target) {
tryBody.resolve(target); tryBody.resolve(target);
if (catchBody != null) catchBody.resolve(target); if (catchBody != null) catchBody.resolve(target);
if (finallyBody != null) finallyBody.resolve(target); if (finallyBody != null) finallyBody.resolve(target);
} }
@Override public void compileFunctions(CompileResult target) {
tryBody.compileFunctions(target);
if (catchBody != null) {
if (captureName != null) {
var index = target.scope.defineCatch(captureName);
target.catchBindings.put(this, index);
}
catchBody.compileFunctions(target);
if (captureName != null) target.scope.undefineCatch();
}
if (finallyBody != null) finallyBody.compileFunctions(target);
}
@Override public void compile(CompileResult target, boolean pollute, BreakpointType bpt) {
int replace = target.temp();
var endSuppl = new DeferredIntSupplier();
@Override public void compile(CompileResult target, boolean pollute, BreakpointType bpt) { int start = replace + 1, catchStart = -1, finallyStart = -1;
int replace = target.temp();
var endSuppl = new DeferredIntSupplier();
int start = replace + 1, catchStart = -1, finallyStart = -1; LabelContext.getBreak(target.env).push(loc(), label, endSuppl);
LabelContext.getBreak(target.env).push(loc(), label, endSuppl); tryBody.compile(target, false);
target.add(Instruction.tryEnd());
tryBody.compile(target, false); if (catchBody != null) {
target.add(Instruction.tryEnd()); catchStart = target.size() - start;
if (catchBody != null) { if (captureName != null) {
catchStart = target.size() - start; var catchVar = target.catchBindings.get(this);
target.scope.defineCatch(captureName, catchVar);
target.add(Instruction.loadError()).setLocation(catchBody.loc());
target.add(catchVar.index().toSet(false)).setLocation(catchBody.loc());
catchBody.compile(target, false);
target.scope.undefineCatch();
}
else catchBody.compile(target, false);
if (captureName != null) { target.add(Instruction.tryEnd());
var subtarget = target.subtarget(); }
subtarget.beginScope();
subtarget.scope.singleEntry = true;
var catchVar = subtarget.scope.defineStrict(new Variable(captureName, false), catchBody.loc()); if (finallyBody != null) {
subtarget.add(Instruction.loadError()); finallyStart = target.size() - start;
subtarget.add(catchVar.index().toInit()); finallyBody.compile(target, false);
catchBody.compile(subtarget, false); target.add(Instruction.tryEnd());
subtarget.endScope(); }
subtarget.scope.end(); LabelContext.getBreak(target.env).pop(label);
}
else catchBody.compile(target, false);
target.add(Instruction.tryEnd()); endSuppl.set(target.size());
}
if (finallyBody != null) { target.set(replace, Instruction.tryStart(catchStart, finallyStart, target.size() - start));
finallyStart = target.size() - start; target.setLocationAndDebug(replace, loc(), BreakpointType.STEP_OVER);
finallyBody.compile(target, false);
}
LabelContext.getBreak(target.env).pop(label); if (pollute) target.add(Instruction.pushUndefined());
}
endSuppl.set(target.size()); public TryNode(Location loc, String label, CompoundNode tryBody, CompoundNode catchBody, CompoundNode finallyBody, String captureName) {
super(loc);
this.tryBody = tryBody;
this.catchBody = catchBody;
this.finallyBody = finallyBody;
this.captureName = captureName;
this.label = label;
}
target.set(replace, Instruction.tryStart(catchStart, finallyStart, target.size() - start)); public static ParseRes<TryNode> parse(Source src, int i) {
target.setLocationAndDebug(replace, loc(), BreakpointType.STEP_OVER); var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (pollute) target.add(Instruction.pushUndefined()); var labelRes = JavaScript.parseLabel(src, i + n);
} n += labelRes.n;
n += Parsing.skipEmpty(src, i + n);
public TryNode(Location loc, String label, CompoundNode tryBody, CompoundNode catchBody, CompoundNode finallyBody, String captureName) { if (!Parsing.isIdentifier(src, i + n, "try")) return ParseRes.failed();
super(loc); n += 3;
this.tryBody = tryBody;
this.catchBody = catchBody;
this.finallyBody = finallyBody;
this.captureName = captureName;
this.label = label;
}
public static ParseRes<TryNode> parse(Source src, int i) { var tryBody = CompoundNode.parse(src, i + n);
var n = Parsing.skipEmpty(src, i); if (!tryBody.isSuccess()) return tryBody.chainError(src.loc(i + n), "Expected a try body");
var loc = src.loc(i + n); n += tryBody.n;
n += Parsing.skipEmpty(src, i + n);
var labelRes = JavaScript.parseLabel(src, i + n); String capture = null;
n += labelRes.n; CompoundNode catchBody = null, finallyBody = null;
n += Parsing.skipEmpty(src, i + n);
if (!Parsing.isIdentifier(src, i + n, "try")) return ParseRes.failed(); if (Parsing.isIdentifier(src, i + n, "catch")) {
n += 3; n += 5;
n += Parsing.skipEmpty(src, i + n);
if (src.is(i + n, "(")) {
n++;
var nameRes = Parsing.parseIdentifier(src, i + n);
if (!nameRes.isSuccess()) return nameRes.chainError(src.loc(i + n), "xpected a catch variable name");
capture = nameRes.result;
n += nameRes.n;
n += Parsing.skipEmpty(src, i + n);
var tryBody = CompoundNode.parse(src, i + n); if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after catch variable name");
if (!tryBody.isSuccess()) return tryBody.chainError(src.loc(i + n), "Expected a try body"); n++;
n += tryBody.n; }
n += Parsing.skipEmpty(src, i + n);
String capture = null; var bodyRes = CompoundNode.parse(src, i + n);
CompoundNode catchBody = null, finallyBody = null; if (!bodyRes.isSuccess()) return tryBody.chainError(src.loc(i + n), "Expected a catch body");
n += bodyRes.n;
n += Parsing.skipEmpty(src, i + n);
if (Parsing.isIdentifier(src, i + n, "catch")) { catchBody = bodyRes.result;
n += 5; }
n += Parsing.skipEmpty(src, i + n);
if (src.is(i + n, "(")) {
n++;
var nameRes = Parsing.parseIdentifier(src, i + n);
if (!nameRes.isSuccess()) return nameRes.chainError(src.loc(i + n), "xpected a catch variable name");
capture = nameRes.result;
n += nameRes.n;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after catch variable name"); if (Parsing.isIdentifier(src, i + n, "finally")) {
n++; n += 7;
}
var bodyRes = CompoundNode.parse(src, i + n); var bodyRes = CompoundNode.parse(src, i + n);
if (!bodyRes.isSuccess()) return tryBody.chainError(src.loc(i + n), "Expected a catch body"); if (!bodyRes.isSuccess()) return tryBody.chainError(src.loc(i + n), "Expected a finally body");
n += bodyRes.n; n += bodyRes.n;
n += Parsing.skipEmpty(src, i + n); n += Parsing.skipEmpty(src, i + n);
finallyBody = bodyRes.result;
}
catchBody = bodyRes.result; if (finallyBody == null && catchBody == null) ParseRes.error(src.loc(i + n), "Expected catch or finally");
}
if (Parsing.isIdentifier(src, i + n, "finally")) { return ParseRes.res(new TryNode(loc, labelRes.result, tryBody.result, catchBody, finallyBody, capture), n);
n += 7; }
var bodyRes = CompoundNode.parse(src, i + n);
if (!bodyRes.isSuccess()) return tryBody.chainError(src.loc(i + n), "Expected a finally body");
n += bodyRes.n;
n += Parsing.skipEmpty(src, i + n);
finallyBody = bodyRes.result;
}
if (finallyBody == null && catchBody == null) ParseRes.error(src.loc(i + n), "Expected catch or finally");
return ParseRes.res(new TryNode(loc, labelRes.result, tryBody.result, catchBody, finallyBody, capture), n);
}
} }

View File

@@ -7,73 +7,76 @@ import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.CompoundNode;
import me.topchetoeu.jscript.compilation.DeferredIntSupplier; import me.topchetoeu.jscript.compilation.DeferredIntSupplier;
import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.LabelContext; import me.topchetoeu.jscript.compilation.LabelContext;
import me.topchetoeu.jscript.compilation.Node; import me.topchetoeu.jscript.compilation.Node;
public class WhileNode extends Node { public class WhileNode extends Node {
public final Node condition, body; public final Node condition, body;
public final String label; public final String label;
@Override public void resolve(CompileResult target) { @Override public void resolve(CompileResult target) {
body.resolve(target); body.resolve(target);
} }
@Override public void compile(CompileResult target, boolean pollute) { @Override public void compileFunctions(CompileResult target) {
int start = target.size(); condition.compileFunctions(target);
condition.compile(target, true); body.compileFunctions(target);
int mid = target.temp(); }
@Override public void compile(CompileResult target, boolean pollute) {
int start = target.size();
condition.compile(target, true);
int mid = target.temp();
var end = new DeferredIntSupplier(); var end = new DeferredIntSupplier();
LabelContext.pushLoop(target.env, loc(), label, end, start); LabelContext.pushLoop(target.env, loc(), label, end, start);
CompoundNode.compileMultiEntry(body, target, false, BreakpointType.STEP_OVER); body.compile(target, false, BreakpointType.STEP_OVER);
LabelContext.popLoop(target.env, label);
var endI = target.size(); var endI = target.size();
end.set(endI + 1); end.set(endI + 1);
LabelContext.popLoop(target.env, label);
target.add(Instruction.jmp(start - end.getAsInt())); target.add(Instruction.jmp(start - endI));
target.set(mid, Instruction.jmpIfNot(end.getAsInt() - mid + 1)); target.set(mid, Instruction.jmpIfNot(endI - mid + 1));
if (pollute) target.add(Instruction.pushUndefined()); if (pollute) target.add(Instruction.pushUndefined());
} }
public WhileNode(Location loc, String label, Node condition, Node body) { public WhileNode(Location loc, String label, Node condition, Node body) {
super(loc); super(loc);
this.label = label; this.label = label;
this.condition = condition; this.condition = condition;
this.body = body; this.body = body;
} }
public static ParseRes<WhileNode> parse(Source src, int i) { public static ParseRes<WhileNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i); var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n); var loc = src.loc(i + n);
var label = JavaScript.parseLabel(src, i + n); var label = JavaScript.parseLabel(src, i + n);
n += label.n; n += label.n;
n += Parsing.skipEmpty(src, i + n); n += Parsing.skipEmpty(src, i + n);
if (!Parsing.isIdentifier(src, i + n, "while")) return ParseRes.failed(); if (!Parsing.isIdentifier(src, i + n, "while")) return ParseRes.failed();
n += 5; n += 5;
n += Parsing.skipEmpty(src, i + n); n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'while'."); if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'while'.");
n++; n++;
var cond = JavaScript.parseExpression(src, i + n, 0); var cond = JavaScript.parseExpression(src, i + n, 0);
if (!cond.isSuccess()) return cond.chainError(src.loc(i + n), "Expected a while condition."); if (!cond.isSuccess()) return cond.chainError(src.loc(i + n), "Expected a while condition.");
n += cond.n; n += cond.n;
n += Parsing.skipEmpty(src, i + n); n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after while condition."); if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after while condition.");
n++; n++;
var body = JavaScript.parseStatement(src, i + n); var body = JavaScript.parseStatement(src, i + n);
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a while body."); if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a while body.");
n += body.n; n += body.n;
return ParseRes.res(new WhileNode(loc, label.result, cond.result, body.result), n); return ParseRes.res(new WhileNode(loc, label.result, cond.result, body.result), n);
} }
} }

View File

@@ -1,57 +0,0 @@
package me.topchetoeu.jscript.compilation.members;
import me.topchetoeu.jscript.common.SyntaxException;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.Node;
import me.topchetoeu.jscript.compilation.patterns.AssignTarget;
import me.topchetoeu.jscript.compilation.values.VariableNode;
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
import me.topchetoeu.jscript.compilation.values.operations.AssignNode;
public class AssignShorthandNode implements Member {
public final Location loc;
public final Node key;
public final AssignTarget target;
public final Node value;
@Override public Location loc() { return loc; }
@Override public void compile(CompileResult target, boolean pollute, boolean enumerable) {
throw new SyntaxException(loc(), "Unexpected assign shorthand in non-destructor context");
}
public AssignShorthandNode(Location loc, Node key, AssignTarget target, Node value) {
this.loc = loc;
this.key = key;
this.target = target;
this.value = value;
}
public AssignTarget target() {
return new AssignNode(loc(), target, value);
}
public static ParseRes<AssignShorthandNode> parse(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var var = VariableNode.parse(src, i + n);
if (!var.isSuccess()) return var.chainError();
n += var.n;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, "=")) return ParseRes.failed();
n++;
var value = JavaScript.parseExpression(src, i + n, 2);
if (!value.isSuccess()) return value.chainError(src.loc(i + n), "Expected a shorthand initializer");
n += value.n;
return ParseRes.res(new AssignShorthandNode(loc, new StringNode(loc, var.result.name), var.result, value.result), n);
}
}

View File

@@ -9,84 +9,51 @@ import me.topchetoeu.jscript.compilation.CompileResult;
import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.Node; import me.topchetoeu.jscript.compilation.Node;
import me.topchetoeu.jscript.compilation.values.ObjectNode; import me.topchetoeu.jscript.compilation.values.ObjectNode;
import me.topchetoeu.jscript.compilation.values.VariableNode;
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
public class FieldMemberNode implements Member { public class FieldMemberNode implements Member {
public final Location loc; public final Location loc;
public final Node key; public final Node key;
public final Node value; public final Node value;
@Override public Location loc() { return loc; } @Override public Location loc() { return loc; }
@Override public void compile(CompileResult target, boolean pollute, boolean enumerable) { @Override public void compileFunctions(CompileResult target) {
if (pollute) target.add(Instruction.dup()); key.compileFunctions(target);
key.compile(target, true); value.compileFunctions(target);
}
if (value == null) target.add(Instruction.pushUndefined()); @Override public void compile(CompileResult target, boolean pollute) {
else value.compile(target, true); if (pollute) target.add(Instruction.dup());
key.compile(target, true);
target.add(Instruction.defField(enumerable)); if (value == null) target.add(Instruction.pushUndefined());
} else value.compile(target, true);
public FieldMemberNode(Location loc, Node key, Node value) { target.add(Instruction.defField());
this.loc = loc; }
this.key = key;
this.value = value;
}
public static ParseRes<FieldMemberNode> parseObject(Source src, int i) { public FieldMemberNode(Location loc, Node key, Node value) {
var n = Parsing.skipEmpty(src, i); this.loc = loc;
var loc = src.loc(i + n); this.key = key;
this.value = value;
}
var name = ObjectNode.parsePropName(src, i + n); public static ParseRes<FieldMemberNode> parse(Source src, int i) {
if (!name.isSuccess()) return name.chainError(); var n = Parsing.skipEmpty(src, i);
n += name.n; var loc = src.loc(i + n);
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, ":")) return ParseRes.failed(); var name = ObjectNode.parsePropName(src, i + n);
n++; if (!name.isSuccess()) return name.chainError();
n += name.n;
n += Parsing.skipEmpty(src, i + n);
var value = JavaScript.parseExpression(src, i + n, 2); if (!src.is(i + n, ":")) return ParseRes.failed();
if (!value.isSuccess()) return value.chainError(src.loc(i + n), "Expected a value"); n++;
n += value.n;
return ParseRes.res(new FieldMemberNode(loc, name.result, value.result), n); var value = JavaScript.parseExpression(src, i + n, 2);
} if (!value.isSuccess()) return value.chainError(src.loc(i + n), "Expected a value");
n += value.n;
public static ParseRes<FieldMemberNode> parseShorthand(Source src, int i) { return ParseRes.res(new FieldMemberNode(loc, name.result, value.result), n);
var n = Parsing.skipEmpty(src, i); }
var loc = src.loc(i + n);
var var = VariableNode.parse(src, i + n);
if (!var.isSuccess()) return var.chainError();
n += var.n;
return ParseRes.res(new FieldMemberNode(loc, new StringNode(loc, var.result.name), var.result), n);
}
public static ParseRes<FieldMemberNode> parseClass(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
var name = ObjectNode.parsePropName(src, i + n);
if (!name.isSuccess()) return name.chainError();
n += name.n;
n += Parsing.skipEmpty(src, i + n);
if (!src.is(i + n, "=")) {
var end = JavaScript.parseStatementEnd(src, i + n);
if (!end.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected an end of statement or a field initializer");
n += end.n;
return ParseRes.res(new FieldMemberNode(loc, name.result, null), n);
}
n++;
var value = JavaScript.parseExpression(src, i + n, 2);
if (!value.isSuccess()) return value.chainError(src.loc(i + n), "Expected a value");
n += value.n;
return ParseRes.res(new FieldMemberNode(loc, name.result, value.result), n);
}
} }

View File

@@ -4,7 +4,8 @@ import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.CompileResult;
public interface Member { public interface Member {
Location loc(); Location loc();
void compile(CompileResult target, boolean pollute, boolean enumerable); void compileFunctions(CompileResult target);
void compile(CompileResult target, boolean pollute);
} }

Some files were not shown because too many files have changed in this diff Show More