nuprl / elementaryjs Goto Github PK
View Code? Open in Web Editor NEWJavaScript without the sharp edges
License: BSD 3-Clause "New" or "Revised" License
JavaScript without the sharp edges
License: BSD 3-Clause "New" or "Revised" License
Previously in #62.
class Dog { constructor(leash) { } }; let d = new Dog();Produces:
function (anonymous) expected 1 argument but received 0 arguments at Line 2: in Dog ... : in handleNew ...
The handleNew bit is an internal (Stopify) leaking through.
Currently, the environment used to determined (un)initialized variables from w/i a function is simply discarded once we exit the function. Alternatively, on exit we could save environment to another data structure. Subsequently, if the function is invoked, reference its environment and merge it with the current environment.
In the case of a function declaration, we can use the id (an Identifier) as the key since it's required to be non-null. In the case of function expression, it's trickier as to how provide an appropriate key to map to the function's environment (e.g. distinguish an IIFE). A class method would apply here as well.
Reported by a student:
The following code compiles in Ocelot but does not run as expected.
function hi(...args){
console.log(args.length);
}
hi(0);
// Result: 1
hi(1,2,3);
// Result:
// function hi expected 1 argument but received 3 arguments at Line 1: in hi
// Expected: 3
// Or hi does not compile
Currently, the entire 220 library of functions is exposed as a global lib220
. However, this is not the case for other libraries where we define separate globals for each of the methods. This scheme presents a tighter coupling between EJS and its libraries, which makes it harder to update them. Also, it unnecessarily pollutes the EJS global namespace.
Ideally, there would be no preset list of globals pertaining the libraries, which would force users to require
every EJS library they wanted to utilize. At a minimum for now, we should expose each library as we do for 220, as a standalone single global object reference. This will also necessitate updates to the 220 assignments.
See /src/index.ts for context.
[0].map
object does not have member 'map'
let arr = [1, undefined, 2]
console.log(arr[1])
Any index with value 'undefined' is affected, other indices are fine.
Create ocelot namespace as an alias to lib220
let x = 0;
console.log(x.f); // prints 0
Possibly related to #178 . Likewise, happens for false, but not true.
let y = false;
console.log(y.f); // prints false
Instead of completely disabling switch, what if we require breaks at the end of each case instead? This is what Java does.
MWE: while(0) {}
.
Wrap loop tests with checkIfBoolean.
Previously https://github.com/umass-compsci220/Ocelot/issues/20
Spectacularly bad error if you invoke a class without new:
TypeError is not defined at : in _classCallCheck
The solution is to fork the babel class transform.
Previously https://github.com/umass-compsci220/Ocelot/issues/27
This is an ElementaryJS issue, but we should fix it before release:
Dynamically check that this is not undefined in an object method.
Currently we check that a Boolean expression is present. However, it's still possible to write something like this:
let a = 'nonsense', b = true;
if (a = b) { // Branch taken.
}
Can we allow uninitialized let as long as it is used before definition?
Should we disallow explicit assignment of undefined
? Generally bad practice to do so as it is an internal value. In contrast, null
is for external use by the programmer.
let a = undefined, //discouraged
b = null; //encouraged
This would require buy-in from EJS stakeholders.
Works:
function m(v) {
return {
toStr: () => {
const r = { v };
return r.v.toString();
}
};
}
m(1).toStr();
Also works:
function m(v) {
const r = { v };
return {
toStr: () => {
const b = r.v;
return b.toString();
}
};
}
m(1).toStr();
Doesn't work:
function m(v) {
const r = { v };
return {
toStr: () => {
return r.v.toString();
}
};
}
m(1).toStr();
Related to this commit. For now this check has temporarily been disabled online; use /eval/eval.js
to reproduce.
At the moment, ElementaryJS (and Ocelot) has two built-in libraries: (1) the image processing library and (2) the "sane" JavaScript runtime library. We are planning a third for robot control. Every time we make a change to these libraries, we have to rebuild and redeploy Ocelot. This is stupid. I talked to Joydeep about this and came up with the following plan:
https//paws.googlecloudstorage.com/libs.json
, we should have the file:{ Math : "https://...",
lib220: "https://..."
...
}
require
function that allows the user to require('lib220')
. Builtins, such as Math
should be automatically required. The JSON file above thus serves as a whitelist and resolution mechanism for libraries.The obvious way to implement this is to have ElementaryJS's require
function dynamically fetch each library. But, this is not going to work well when we have crappy Internet. A better approach is to have ElementaryJS fetch all libraries when the page is loaded.
We have to also think about how this will work in Gradescope. The obvious approach is to download libraries from the web, even in Gradescope. But, I am concerned that we will suffer spurious network errors. I think a better approach is to modify the compile function in ElementaryJS to take the path to the JSON mapping (edit CompilerOpts):
https://github.com/plasma-umass/ElementaryJS/blob/master/ts/types.ts#L33
If the path is an HTTPS path, it should load it from the web. However, if the path is a file, it should load it from the file. This would allow us to have the libraries available locally in Gradescope. We would even be able to use different versions of a library in a grading environment.
Note:: The EJS compile function will have to become asynchron
Comments welcome.
A let
in a loop header should create a new binding for each iteration.
This code should print out 0 1 2
let fa = [];
for (let i = 0; i < 3; ++i) {
fa.push(() => i);
}
fa.forEach(f => console.log(f()));
However, it prints 3 three times.
Any "attribute" of empty string is evaluated as empty string.
''.length; // ''; should be 0
''.foo; // ''; should be error
''.bar; // ''; should be error
Steps to reproduce:
Error behavior: At step 4, the new file entry shows the last error message.
Expected behavior: At step 4, the new file entry should not have an error message.
Justification:
bool1 & bool2
instead of bool1 && bool2
.Needs to be fixed fast.
[1, 2, 3,4].find(4)
pred is not a function at Line 278: in (anonymous function)
This should be fixed with a dyn type check
Writing x += 0; produces the uninformative error message
Line 0: unknown: path.node.loc is undefined
Writing x = 0; instead produces the friendly
Line 1: You must declare variable 'x' before assigning a value to it.
Infinity
is not supportedHe wanted to use reduce
to find max value of an array but accumulator then needs to be -Infinity
.
This should run with no errors:
function Duck() {
this.x = 1;
}
class DuckClass {
constructor() {
this.x = 1;
}
}
let duck1 = new Duck();
let duck2 = new DuckClass();
However, it produces:
object does not have member 'x' at Line 4: in Duck
... : in handleNew
...
I don't think we should fix this. Main issue is in Stopify:
Through indication from code, JSONStopified
is expected to be a clone of the JSON
object. Per MDN Web Docs for Object.assign()
:
The
Object.assign()
method copies all enumerable own properties from one or more source objects to a target object. It returns the target object.
However, no properties in JSON
are enumerable, thus nothing is copied and JSONStopified
is an empty object after the assignment.
This interaction:
> [1,2,3].indexOf
object does not have member 'indexOf'
> [1,2,3].indexOf(2)
1
This occurs with other array methods too.
These should not be possible in EJS:
let a = {10:"Wow"}; console.log(a[10]);
let a = []; a.length = 4; console.log(a);
Previously https://github.com/umass-compsci220/Ocelot/issues/21
Free variables do not get locations reported. The problem is that we use a proxy to catch unbound identifiers, and don't have original source locations at that time.
When attempting to use spread operator on an array, ElementaryJS encountered an unknown error. This issue also affects the live version at https://code.ocelot-ide.org/
[...[1, 2]].length
2
It throws error and give the message as shown below:
TypeError: unknown: Property init of VariableDeclarator expected node to be of a type ["Expression"] but instead got "SpreadElement"
FAIL tests/spread.test.ts (6.095 s)
● ElementaryJS - Spread › Spread issue
expect(received).resolves.toEqual()
Received promise rejected instead of resolved
Rejected to value: [TypeError: unknown: Property init of VariableDeclarator expected node to be of a type ["Expression"] but instead got "SpreadElement"]
3 | describe("ElementaryJS - Spread", () => {
4 | test("Spread issue", async () => {
> 5 | await expect(run(`[...[1, 2]].length`)).resolves.toEqual(2);
| ^
6 | });
7 | });
8 |
at expect (node_modules/expect/build/index.js:134:15)
at tests/spread.test.ts:5:15
at tests/spread.test.ts:8:71
at Object.<anonymous>.__awaiter (tests/spread.test.ts:4:12)
at Object.<anonymous> (tests/spread.test.ts:4:37)
PASS tests/ejs-environments.test.ts (6.794 s)
PASS tests/ejs.test.ts (13.463 s)
PASS tests/ejs-test-mode.test.ts (25.029 s)
Test Suites: 1 failed, 3 passed, 4 total
Tests: 1 failed, 147 passed, 148 total
Snapshots: 0 total
Time: 25.668 s
Ran all test suites.
The desugaring process performed here in the AssignmentExpression visitor does not generate equivalent code as defined by the JavaScript spec. This nuance is also described on MDN here.
Consider the following code:
let a = [1, 3, 5, 7];
let i = 0;
a[++i] += 3;
console.log(i);
The output is 2 (as viewed in Ocelot) when the expected value is 1 (tested on Node v11.4.0).
let x = 0;
{
{ let x = 10; }
x += 10;
}
console.log(x);
This will print 20 although the correct value should be 10.
Names are not representative of what they do.
This should not throw an error:
function MyClass() {
this.x = 0;
}
let m = new MyClass();
But it results in:
object does not have member 'x' at Line 2: in MyClass
... : in handleNew
...
EJS require
works as intended. However, it can interfere with the native require
when interfacing with third-party tools (e.g. Jalangi). It would be ideal if our require
had an alias to circumvent this problem (e.g. requireLib
). User's could continue using EJS require
, although a non-colliding alias would be provided when necessary.
Alternatively, as perhaps a better solution, we could just rename it and publish a breaking change (i.e. major version).
Since ECMAScript 2022 arrays have new methods like findLastIndex
I assume their handling in ElementaryJS is simply forwarded.
Every once in a while, findLastIndex returns false
when looking for an existing array element (it correctly returns -1 when the element does not exist).
MWE (n=100 is sufficiently large to exhibit every few runs):
test("findLastIndex", () => {
let a = [];
for (let i = 0; i < 100; ++i) { a.push(Math.random()); }
for (let i = 0; i < a.length; ++i) {
assert(a.findLastIndex((e, _i, _a) => e === a[i]) !== false);
}
});
We report two errors for a single pair of missing braces on conditionals:
if (true)
console.log('hi');
Line 1: if statement body must be enclosed in braces.
Line 1: if statement body must be enclosed in braces.
While, for (pun intended) loops we report a single error per pair of missing braces:
while (true)
console.log('hi');
Line 1: Loop body must be enclosed in braces.
The conditional reporting should be fixed to a single error message per violation.
Just a heads up, I notice that in your pacage.json
file you are using babel-core
as a dependency (and by proxy babylon
for parsing).
Those dependencies have seemed to move to @babel/core
and @babel/parser
now.
Punctuation and capitalization should be consistent in all error messages, both static and dynamic.
Previously in #62.
[1, 2, 3, 4].find(4) // pred is not a function at Line 278: in (anonymous function)This should be fixed with a dyn type check.
The console allows let a = undefined;
but the code editor does not.
let arr = [1, 2, 3] arr[0],arr[2] = arr[2], arr[0] console.log(arr)
This should throw an error, but it does not and simply prints out 1, 2, 3. The left hand side is not flagged as a comma operator.
It hasn't been updated since we pulled rrt
out of lib220
. As a result all its methods are undefined
. Seems the latest instructions say to use manually load the library.
Strings are defined with ``, '', "". Make them consistent.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.