Code Monkey home page Code Monkey logo

elementaryjs's Introduction

ElementaryJS

ElementaryJS is JavaScript, but with sharp edges removed. We use it in the Ocelot JavaScript IDE. For an overview of what ElementaryJS does, see this page

Building

On initialization (or when you update package.json):

npm install

To build:

npm run-script build

To lint:

npm run-script lint

To test:

npm run-script test

Development

  • src/types.ts: Types used throughout the codebase.

  • src/visitor.ts: The heart of the ElementaryJS compiler. This code performs static checks and inserts dynamic checks that ElementaryJS enforces.

  • src/runtime.ts: The ElementaryJS runtime system. This module has the implementations of the dynamic checks that the compiler inserts.

  • src/index.ts: Entrypoint of the ElementaryJS package. This is the interface to ElementaryJS.

  • tests/: Unit tests.

  • eval/: Scripts and files used for evaluating ElementaryJS effectiveness.

Unsupported Features

ElementaryJS does not support some JavaScript features, not because we intend to omit them, but because we haven't done the work to support them:

  1. Array spread syntax
  2. Destructuring assignment

elementaryjs's People

Contributors

sp1tz avatar arjunguha avatar joydeep-b avatar lchsam avatar dependabot[bot] avatar sdlane avatar chriscbr avatar

Stargazers

Anna Allen avatar Evan Cole avatar Rachit Nigam avatar Second Datke avatar

Watchers

James Cloos avatar Abhinav Jangda avatar Chester Moses avatar  avatar  avatar

elementaryjs's Issues

Missed Edge Case

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.

DynChecks spill Stopify code

MWE:

function foo() {
}

if (foo) {
}

Output:
image

The problem is that the dyn checks print out string conversions for error reporting, and sometimes those strings could be Stopify-compiled code.

Solution: Add case handling for functions before printing errors.

wrong scoping of `let` variables declared in loop header

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.

Array spread syntax is not supported

Problem

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/

Steps to Reproduce:

  1. Input the below javascript code:
[...[1, 2]].length

Expected value

2

Actual behavior

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"

Jest output

 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.

Uninitialized let w/ functions

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.

Arity with new

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.

Use of undefined primative

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.

bug handling new ECMAScript findLastIndex()

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);
  }
});

Not handling "this" correctly

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
... 

Disable (or support) rest arguments

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

Dynamic linking

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:

  • Each library should be available on the web
  • There should be a JSON file on the web that maps library names to URLs. E.g., at https//paws.googlecloudstorage.com/libs.json, we should have the file:
{ Math : "https://...",
  lib220: "https://..."
 ...
}
  • ElementaryJS should export a 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.

  • Write a separate function that takes a dictionary of URLs / file paths and turns it into a dictionary with code.

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.

@sp1tz @lchsam @joydeep-b

babel-core (and babylon) depricated

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.

Desugaring of assignment operators is invalid

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).

Member check incorrect when using new keyword

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
... 

Misuse of `Object.assign`

https://github.com/ocelot-ide/ElementaryJS/blob/c122f373fe65ec38fc35a91ec7168f167bd74368/src/index.ts#L50

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.

Message strings

Punctuation and capitalization should be consistent in all error messages, both static and dynamic.

Conditional missing braces reporting

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.

Consistent integration of libraries

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.

New file errors do not get cleared

Steps to reproduce:

  1. Click on "New"
  2. Enter an invalid name, e.g. "BAD"
  3. Abort the new file creation, by clicking on the delete icon.
  4. Click on "New" to start over.

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.

No support for Infinity

  • @Chriscbr found that Infinity is not supported

He wanted to use reduce to find max value of an array but accumulator then needs to be -Infinity.

Alias require

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).

Unhelpful error messages

[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

Empty string behavior

Any "attribute" of empty string is evaluated as empty string.

''.length; // ''; should be 0
''.foo; // ''; should be error
''.bar; // ''; should be error

Disallow assignment op in places

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.
}

Expecting func arg

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.

Disallow bitwise operators

Justification:

  • No integers in JS.
  • They are very slow here, being far from hardware.
  • Typos; for example bool1 & bool2 instead of bool1 && bool2.
  • An indicator of overly clever code, which decreases maintainability.

Update linter too.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.