From de9df9b78331b86b6e2d1754d229fb00f55acd90 Mon Sep 17 00:00:00 2001 From: ning Date: Mon, 14 May 2018 10:43:53 +0800 Subject: [PATCH 001/129] Add button for playground at Navbar No functionality. --- src/components/NavigationBar.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/components/NavigationBar.tsx b/src/components/NavigationBar.tsx index bace77863e..140c940609 100644 --- a/src/components/NavigationBar.tsx +++ b/src/components/NavigationBar.tsx @@ -21,6 +21,17 @@ const NavigationBar: React.SFC = ({ title }) => ( Dashboard + + + + + Playground + + ) From 42d4cb64f195c97a38c61d30e77bddaaa089fd47 Mon Sep 17 00:00:00 2001 From: ning Date: Mon, 14 May 2018 10:55:25 +0800 Subject: [PATCH 002/129] Make first NavbarGroup's align left explicit --- src/components/NavigationBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/NavigationBar.tsx b/src/components/NavigationBar.tsx index 140c940609..a117cd0733 100644 --- a/src/components/NavigationBar.tsx +++ b/src/components/NavigationBar.tsx @@ -9,7 +9,7 @@ export interface INavigationBarProps { const NavigationBar: React.SFC = ({ title }) => ( - + {title} Date: Mon, 14 May 2018 11:03:05 +0800 Subject: [PATCH 003/129] Change playground icon --- src/components/NavigationBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/NavigationBar.tsx b/src/components/NavigationBar.tsx index a117cd0733..7ee08da693 100644 --- a/src/components/NavigationBar.tsx +++ b/src/components/NavigationBar.tsx @@ -28,7 +28,7 @@ const NavigationBar: React.SFC = ({ title }) => ( activeClassName="pt-active" className="NavigationBar__link pt-button pt-minimal" > - + Playground From c00bfebf772b0a5c32730d50cefc61f5910aab37 Mon Sep 17 00:00:00 2001 From: ning Date: Mon, 14 May 2018 11:11:03 +0800 Subject: [PATCH 004/129] Add routes to Playground --- src/components/Application.tsx | 2 ++ src/components/Playground.tsx | 9 +++++++++ src/components/__tests__/Playground.tsx | 10 ++++++++++ .../__tests__/__snapshots__/Playground.tsx.snap | 9 +++++++++ src/containers/PlaygroundContainer.ts | 5 +++++ 5 files changed, 35 insertions(+) create mode 100644 src/components/Playground.tsx create mode 100644 src/components/__tests__/Playground.tsx create mode 100644 src/components/__tests__/__snapshots__/Playground.tsx.snap create mode 100644 src/containers/PlaygroundContainer.ts diff --git a/src/components/Application.tsx b/src/components/Application.tsx index 695061d0df..c12423465a 100644 --- a/src/components/Application.tsx +++ b/src/components/Application.tsx @@ -3,6 +3,7 @@ import * as React from 'react' import { Redirect, Route, RouteComponentProps, Switch } from 'react-router' import DashboardContainer from '../containers/DashboardContainer' +import PlaygroundContainer from '../containers/PlaygroundContainer' import { IApplicationState } from '../reducers/application' import NavigationBar from './NavigationBar' import NotFound from './NotFound' @@ -20,6 +21,7 @@ const Application: React.SFC = ({ application }) => {
+ diff --git a/src/components/Playground.tsx b/src/components/Playground.tsx new file mode 100644 index 0000000000..f466956c94 --- /dev/null +++ b/src/components/Playground.tsx @@ -0,0 +1,9 @@ +import * as React from 'react' + +const Playground: React.SFC<{}> = () => ( +
+

Playground

+
+) + +export default Playground diff --git a/src/components/__tests__/Playground.tsx b/src/components/__tests__/Playground.tsx new file mode 100644 index 0000000000..9189d3a54c --- /dev/null +++ b/src/components/__tests__/Playground.tsx @@ -0,0 +1,10 @@ +import * as React from 'react' + +import { shallow } from 'enzyme' + +import Playground from '../Playground' + +test('Playground renders correctly', () => { + const tree = shallow() + expect(tree.debug()).toMatchSnapshot() +}) diff --git a/src/components/__tests__/__snapshots__/Playground.tsx.snap b/src/components/__tests__/__snapshots__/Playground.tsx.snap new file mode 100644 index 0000000000..452798115b --- /dev/null +++ b/src/components/__tests__/__snapshots__/Playground.tsx.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Playground renders correctly 1`] = ` +"
+

+ Playground +

+
" +`; diff --git a/src/containers/PlaygroundContainer.ts b/src/containers/PlaygroundContainer.ts new file mode 100644 index 0000000000..d05eca17b2 --- /dev/null +++ b/src/containers/PlaygroundContainer.ts @@ -0,0 +1,5 @@ +import { connect } from 'react-redux' + +import Playground from '../components/Playground' + +export default connect()(Playground) From c70d46aeecb82861a862e09e7f1265f71f16b51a Mon Sep 17 00:00:00 2001 From: ning Date: Mon, 14 May 2018 12:16:24 +0800 Subject: [PATCH 005/129] Update test snapshots --- .../__tests__/__snapshots__/Application.tsx.snap | 1 + .../__tests__/__snapshots__/NavigationBar.tsx.snap | 8 +++++++- src/reducers/__tests__/__snapshots__/application.ts.snap | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/components/__tests__/__snapshots__/Application.tsx.snap b/src/components/__tests__/__snapshots__/Application.tsx.snap index 5718b8efc8..7d7d29d121 100644 --- a/src/components/__tests__/__snapshots__/Application.tsx.snap +++ b/src/components/__tests__/__snapshots__/Application.tsx.snap @@ -6,6 +6,7 @@ exports[`Application renders correctly 1`] = `
+ diff --git a/src/components/__tests__/__snapshots__/NavigationBar.tsx.snap b/src/components/__tests__/__snapshots__/NavigationBar.tsx.snap index 82438a6290..1a9caea40a 100644 --- a/src/components/__tests__/__snapshots__/NavigationBar.tsx.snap +++ b/src/components/__tests__/__snapshots__/NavigationBar.tsx.snap @@ -2,7 +2,7 @@ exports[`NavigationBar renders correctly 1`] = ` " - + Cadet @@ -12,5 +12,11 @@ exports[`NavigationBar renders correctly 1`] = ` Dashboard + + + + Playground + + " `; diff --git a/src/reducers/__tests__/__snapshots__/application.ts.snap b/src/reducers/__tests__/__snapshots__/application.ts.snap index c57267d708..21139a3532 100644 --- a/src/reducers/__tests__/__snapshots__/application.ts.snap +++ b/src/reducers/__tests__/__snapshots__/application.ts.snap @@ -3,6 +3,6 @@ exports[`initial state should match a snapshot 1`] = ` Object { "environment": "test", - "title": "Conveyor", + "title": "Cadet", } `; From 0ab401a6130527e8b16f628f8ab8a7a3cc8adf19 Mon Sep 17 00:00:00 2001 From: ning Date: Mon, 14 May 2018 12:27:47 +0800 Subject: [PATCH 006/129] Fix bad test for Playground component Oops --- src/components/__tests__/Playground.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/__tests__/Playground.tsx b/src/components/__tests__/Playground.tsx index 9189d3a54c..508f4758c7 100644 --- a/src/components/__tests__/Playground.tsx +++ b/src/components/__tests__/Playground.tsx @@ -5,6 +5,6 @@ import { shallow } from 'enzyme' import Playground from '../Playground' test('Playground renders correctly', () => { - const tree = shallow() + const tree = shallow() expect(tree.debug()).toMatchSnapshot() }) From c9de874e998b12f5a0a00aab38bfa826a8b3d170 Mon Sep 17 00:00:00 2001 From: ning Date: Mon, 14 May 2018 16:55:53 +0800 Subject: [PATCH 007/129] Add react-ace for IDE --- package.json | 1 + src/components/Playground.tsx | 3 ++ .../__snapshots__/Playground.tsx.snap | 1 + yarn.lock | 28 ++++++++++++++++++- 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index f419a3b32a..a333a53a16 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@blueprintjs/core": "^2.1.1", "normalize.css": "^8.0.0", "react": "^16.3.1", + "react-ace": "^6.1.1", "react-dom": "^16.3.1", "react-redux": "^5.0.7", "react-router": "^4.2.0", diff --git a/src/components/Playground.tsx b/src/components/Playground.tsx index f466956c94..b176c9c3bc 100644 --- a/src/components/Playground.tsx +++ b/src/components/Playground.tsx @@ -1,8 +1,11 @@ import * as React from 'react' +import AceEditor from 'react-ace' + const Playground: React.SFC<{}> = () => (

Playground

+
) diff --git a/src/components/__tests__/__snapshots__/Playground.tsx.snap b/src/components/__tests__/__snapshots__/Playground.tsx.snap index 452798115b..0326aad15d 100644 --- a/src/components/__tests__/__snapshots__/Playground.tsx.snap +++ b/src/components/__tests__/__snapshots__/Playground.tsx.snap @@ -5,5 +5,6 @@ exports[`Playground renders correctly 1`] = `

Playground

+
" `; diff --git a/yarn.lock b/yarn.lock index 86de278828..153ec2a405 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1216,6 +1216,10 @@ brace-expansion@^1.0.0, brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/brace/-/brace-0.11.1.tgz#4896fcc9d544eef45f4bb7660db320d3b379fe58" + braces@^1.8.2: version "1.8.5" resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" @@ -2190,6 +2194,10 @@ detect-port-alt@1.1.6: address "^1.0.1" debug "^2.6.0" +diff-match-patch@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.1.tgz#d5f880213d82fbc124d2b95111fb3c033dbad7fa" + diff@^3.2.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" @@ -4549,6 +4557,14 @@ lodash.flattendeep@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + +lodash.isequal@^4.1.1: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + lodash.isfunction@^3.0.8: version "3.0.9" resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" @@ -5819,7 +5835,7 @@ promise@^7.1.1: dependencies: asap "~2.0.3" -prop-types@^15.5.4, prop-types@^15.6.0, prop-types@^15.6.1: +prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1: version "15.6.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.1.tgz#36644453564255ddda391191fb3a125cbdf654ca" dependencies: @@ -5973,6 +5989,16 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.1.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +react-ace@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/react-ace/-/react-ace-6.1.1.tgz#c8339b4f0a27401bff53f477f125d3d7eac2a090" + dependencies: + brace "^0.11.0" + diff-match-patch "^1.0.0" + lodash.get "^4.4.2" + lodash.isequal "^4.1.1" + prop-types "^15.5.8" + react-dev-utils@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-5.0.1.tgz#1f396e161fe44b595db1b186a40067289bf06613" From b4f37b7e20e4bb1e6865a1aea5de4d93647addbb Mon Sep 17 00:00:00 2001 From: ning Date: Tue, 15 May 2018 11:08:56 +0800 Subject: [PATCH 008/129] Try passing props into playground --- src/components/Playground.tsx | 5 +++-- src/components/__tests__/Playground.tsx | 13 ++++++++++++- src/containers/PlaygroundContainer.ts | 11 +++++++++-- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/components/Playground.tsx b/src/components/Playground.tsx index b176c9c3bc..521fa1dcdb 100644 --- a/src/components/Playground.tsx +++ b/src/components/Playground.tsx @@ -1,11 +1,12 @@ import * as React from 'react' import AceEditor from 'react-ace' +import { IApplicationProps } from './Application' -const Playground: React.SFC<{}> = () => ( +const Playground: React.SFC = ({ application }) => (

Playground

- +
) diff --git a/src/components/__tests__/Playground.tsx b/src/components/__tests__/Playground.tsx index 508f4758c7..97534a383d 100644 --- a/src/components/__tests__/Playground.tsx +++ b/src/components/__tests__/Playground.tsx @@ -2,9 +2,20 @@ import * as React from 'react' import { shallow } from 'enzyme' +import { mockRouterProps } from '../../mocks/components' +import { ApplicationEnvironment } from '../../reducers/application' +import { IApplicationProps } from '../Application' import Playground from '../Playground' test('Playground renders correctly', () => { - const tree = shallow() + const props: IApplicationProps = { + ...mockRouterProps('/dashboard', {}), + application: { + title: 'Cadet', + environment: ApplicationEnvironment.Development + } + } + const app = + const tree = shallow(app) expect(tree.debug()).toMatchSnapshot() }) diff --git a/src/containers/PlaygroundContainer.ts b/src/containers/PlaygroundContainer.ts index d05eca17b2..bba7babdd3 100644 --- a/src/containers/PlaygroundContainer.ts +++ b/src/containers/PlaygroundContainer.ts @@ -1,5 +1,12 @@ -import { connect } from 'react-redux' +import { connect, MapStateToProps } from 'react-redux' + +import { IApplicationProps } from '../components/Application' +import { IState } from '../reducers' import Playground from '../components/Playground' -export default connect()(Playground) +const mapStateToProps: MapStateToProps = state => ({ + application: state.application +}) + +export default connect(mapStateToProps)(Playground) From c1d11a619bddb8f9c8a29b7e3b80f126537cff85 Mon Sep 17 00:00:00 2001 From: remo5000 Date: Tue, 15 May 2018 12:42:28 +0800 Subject: [PATCH 009/129] Load a preset "Hello World" onto the ace editor. Changes: - Changed `Playground` component to a statefull one (extends `React.Component) - add property `playgroundCode` to IApplicationState (bad practice, change to a slice of the state instead) - add interface `IPlaygroundProps` to denote the properties passed to Playground --- src/components/Playground.tsx | 29 +++++++++++++------ src/components/__tests__/Application.tsx | 3 +- src/components/__tests__/Playground.tsx | 12 ++------ .../__snapshots__/Playground.tsx.snap | 3 +- src/containers/PlaygroundContainer.ts | 22 +++++++------- src/mocks/store.ts | 3 +- .../__snapshots__/application.ts.snap | 1 + src/reducers/application.ts | 4 ++- tslint.json | 3 +- 9 files changed, 47 insertions(+), 33 deletions(-) diff --git a/src/components/Playground.tsx b/src/components/Playground.tsx index 521fa1dcdb..8044d7a611 100644 --- a/src/components/Playground.tsx +++ b/src/components/Playground.tsx @@ -1,13 +1,24 @@ import * as React from 'react' - import AceEditor from 'react-ace' -import { IApplicationProps } from './Application' -const Playground: React.SFC = ({ application }) => ( -
-

Playground

- -
-) -export default Playground +export interface IPlaygroundProps { + initialCode: string; +}; + +function onChange(newValue : string) : void { + // console.log('change', newValue); +} + +export default class Playground extends React.Component { + public render() { + return ( +
+

Playground

+ +
+ ); + } +} diff --git a/src/components/__tests__/Application.tsx b/src/components/__tests__/Application.tsx index 53ed0dcefe..d755946e18 100644 --- a/src/components/__tests__/Application.tsx +++ b/src/components/__tests__/Application.tsx @@ -11,7 +11,8 @@ test('Application renders correctly', () => { ...mockRouterProps('/dashboard', {}), application: { title: 'Cadet', - environment: ApplicationEnvironment.Development + environment: ApplicationEnvironment.Development, + playgroundCode: '' } } const app = diff --git a/src/components/__tests__/Playground.tsx b/src/components/__tests__/Playground.tsx index 97534a383d..05d07e8be4 100644 --- a/src/components/__tests__/Playground.tsx +++ b/src/components/__tests__/Playground.tsx @@ -2,18 +2,12 @@ import * as React from 'react' import { shallow } from 'enzyme' -import { mockRouterProps } from '../../mocks/components' -import { ApplicationEnvironment } from '../../reducers/application' -import { IApplicationProps } from '../Application' import Playground from '../Playground' +import { IPlaygroundProps as PlaygroundProps } from '../Playground'; test('Playground renders correctly', () => { - const props: IApplicationProps = { - ...mockRouterProps('/dashboard', {}), - application: { - title: 'Cadet', - environment: ApplicationEnvironment.Development - } + const props: PlaygroundProps = { + initialCode: 'Hello World' } const app = const tree = shallow(app) diff --git a/src/components/__tests__/__snapshots__/Playground.tsx.snap b/src/components/__tests__/__snapshots__/Playground.tsx.snap index 0326aad15d..ab4bbaf7c1 100644 --- a/src/components/__tests__/__snapshots__/Playground.tsx.snap +++ b/src/components/__tests__/__snapshots__/Playground.tsx.snap @@ -5,6 +5,7 @@ exports[`Playground renders correctly 1`] = `

Playground

- + +
" `; diff --git a/src/containers/PlaygroundContainer.ts b/src/containers/PlaygroundContainer.ts index bba7babdd3..16e4773af5 100644 --- a/src/containers/PlaygroundContainer.ts +++ b/src/containers/PlaygroundContainer.ts @@ -1,12 +1,14 @@ import { connect, MapStateToProps } from 'react-redux' -import { IApplicationProps } from '../components/Application' -import { IState } from '../reducers' - -import Playground from '../components/Playground' - -const mapStateToProps: MapStateToProps = state => ({ - application: state.application -}) - -export default connect(mapStateToProps)(Playground) +import { IPlaygroundProps as PlaygroundProps } from '../components/Playground'; +import PlaygroundComponent from '../components/Playground'; +import { IState } from '../reducers'; + +const mapStateToProps: MapStateToProps = state => { + console.log('MapStateToProps with inittialCode ' + state.application.playgroundCode) + return { + initialCode: state.application.playgroundCode +} +} + +export default connect(mapStateToProps)(PlaygroundComponent) diff --git a/src/mocks/store.ts b/src/mocks/store.ts index d2a5a763af..3a51ca84e6 100644 --- a/src/mocks/store.ts +++ b/src/mocks/store.ts @@ -9,7 +9,8 @@ export function mockInitialStore

(): Store { const state: IState = { application: { title: 'Cadet', - environment: ApplicationEnvironment.Development + environment: ApplicationEnvironment.Development, + playgroundCode: 'Hello World' } } return createStore(state) diff --git a/src/reducers/__tests__/__snapshots__/application.ts.snap b/src/reducers/__tests__/__snapshots__/application.ts.snap index 21139a3532..1fdd14dd60 100644 --- a/src/reducers/__tests__/__snapshots__/application.ts.snap +++ b/src/reducers/__tests__/__snapshots__/application.ts.snap @@ -3,6 +3,7 @@ exports[`initial state should match a snapshot 1`] = ` Object { "environment": "test", + "playgroundCode": "Hello the world", "title": "Cadet", } `; diff --git a/src/reducers/application.ts b/src/reducers/application.ts index 6885552c2a..06a9d3ae4b 100644 --- a/src/reducers/application.ts +++ b/src/reducers/application.ts @@ -9,6 +9,7 @@ export enum ApplicationEnvironment { export interface IApplicationState { title: string environment: ApplicationEnvironment + playgroundCode: string } const currentEnvironment = (): ApplicationEnvironment => { @@ -24,7 +25,8 @@ const currentEnvironment = (): ApplicationEnvironment => { const defaultState: IApplicationState = { title: 'Cadet', - environment: currentEnvironment() + environment: currentEnvironment(), + playgroundCode: 'Hello the world' } export const reducer: Reducer = (state = defaultState, action: Action) => { diff --git a/tslint.json b/tslint.json index 38976e529b..5a9bbe2a2a 100644 --- a/tslint.json +++ b/tslint.json @@ -1,7 +1,8 @@ { "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"], "rules": { - "object-literal-sort-keys": false + "object-literal-sort-keys": false, + "no-console": false }, "linterOptions": { "exclude": [ From 8145a2a7f113b577671b31046c6e4efb5fc98496 Mon Sep 17 00:00:00 2001 From: remo5000 Date: Tue, 15 May 2018 12:51:41 +0800 Subject: [PATCH 010/129] Remove unused onChange() --- src/components/Playground.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/components/Playground.tsx b/src/components/Playground.tsx index 8044d7a611..a8cf7f05c0 100644 --- a/src/components/Playground.tsx +++ b/src/components/Playground.tsx @@ -1,23 +1,22 @@ import * as React from 'react' import AceEditor from 'react-ace' - +import 'brace/mode/java'; +import 'brace/theme/github'; export interface IPlaygroundProps { initialCode: string; }; -function onChange(newValue : string) : void { - // console.log('change', newValue); -} - export default class Playground extends React.Component { public render() { return (

Playground

+ />
); } From c8add207e275fc24e5293fb6031ca470c5958f74 Mon Sep 17 00:00:00 2001 From: remo5000 Date: Tue, 15 May 2018 15:03:35 +0800 Subject: [PATCH 011/129] Add stored state for playground editor text Once again, this is a WIP. Text typed into the playground editor will stay in the playground (or rather, appear again) after leaving and coming back. Changes: - added of action, actionCreator (updatePlaygroundCode) - added a reducer under application's `reducer` function Possible problems: - Location of reducer - Location of playgroundCode stored (as in previous commit) --- src/actions/index.ts | 15 +++++++++++++ src/components/Playground.tsx | 4 ++-- src/components/__tests__/Playground.tsx | 3 ++- src/containers/PlaygroundContainer.ts | 29 ++++++++++++++++++------- src/reducers/application.ts | 20 ++++++++++++++--- 5 files changed, 57 insertions(+), 14 deletions(-) diff --git a/src/actions/index.ts b/src/actions/index.ts index b1c6ea436a..b7392eee8d 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -1 +1,16 @@ export default {} + +export type UPDATE_PLAYGROUND_CODE = 'UPDATE_PLAYGROUND_CODE'; +export const UPDATE_PLAYGROUND_CODE: UPDATE_PLAYGROUND_CODE = 'UPDATE_PLAYGROUND_CODE'; + +export interface IUpdatePlaygroundCodeAction { + type: UPDATE_PLAYGROUND_CODE + playgroundCode: string + }; + +export function updatePlaygroundCode(newCode: string): IUpdatePlaygroundCodeAction { + return { + type: UPDATE_PLAYGROUND_CODE, + playgroundCode: newCode + }; + }; \ No newline at end of file diff --git a/src/components/Playground.tsx b/src/components/Playground.tsx index a8cf7f05c0..65a2b0677e 100644 --- a/src/components/Playground.tsx +++ b/src/components/Playground.tsx @@ -1,10 +1,9 @@ import * as React from 'react' import AceEditor from 'react-ace' -import 'brace/mode/java'; -import 'brace/theme/github'; export interface IPlaygroundProps { initialCode: string; + updateCode: (newCode: string) => void; }; export default class Playground extends React.Component { @@ -16,6 +15,7 @@ export default class Playground extends React.Component { mode="java" theme="github" value={this.props.initialCode} + onChange={this.props.updateCode} /> ); diff --git a/src/components/__tests__/Playground.tsx b/src/components/__tests__/Playground.tsx index 05d07e8be4..7d8354313b 100644 --- a/src/components/__tests__/Playground.tsx +++ b/src/components/__tests__/Playground.tsx @@ -7,7 +7,8 @@ import { IPlaygroundProps as PlaygroundProps } from '../Playground'; test('Playground renders correctly', () => { const props: PlaygroundProps = { - initialCode: 'Hello World' + initialCode: 'Hello the world', + updateCode: (newCode : string ) => { return;} } const app = const tree = shallow(app) diff --git a/src/containers/PlaygroundContainer.ts b/src/containers/PlaygroundContainer.ts index 16e4773af5..4ee8005381 100644 --- a/src/containers/PlaygroundContainer.ts +++ b/src/containers/PlaygroundContainer.ts @@ -1,14 +1,27 @@ -import { connect, MapStateToProps } from 'react-redux' - -import { IPlaygroundProps as PlaygroundProps } from '../components/Playground'; +import * as Actions from '../actions/index' import PlaygroundComponent from '../components/Playground'; + +import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux' +import { Dispatch } from 'redux'; +import { IPlaygroundProps as PlaygroundProps} from '../components/Playground'; import { IState } from '../reducers'; -const mapStateToProps: MapStateToProps = state => { - console.log('MapStateToProps with inittialCode ' + state.application.playgroundCode) + +type StateProps = Pick; +type DispatchProps = Pick; + +const mapStateToProps: MapStateToProps = state => { return { - initialCode: state.application.playgroundCode -} + initialCode: state.application.playgroundCode + } } -export default connect(mapStateToProps)(PlaygroundComponent) +const mapDispatchToProps : MapDispatchToProps = (dispatch: Dispatch) => { + return { + updateCode: (newCode: string) => { + dispatch(Actions.updatePlaygroundCode(newCode)); + }, + }; +}; + +export default connect(mapStateToProps, mapDispatchToProps)(PlaygroundComponent) diff --git a/src/reducers/application.ts b/src/reducers/application.ts index 06a9d3ae4b..f18be3c9cc 100644 --- a/src/reducers/application.ts +++ b/src/reducers/application.ts @@ -1,4 +1,5 @@ -import { Action, Reducer } from 'redux' +import { Reducer } from 'redux' +import { IUpdatePlaygroundCodeAction as UpdatePlaygroundCodeAction, UPDATE_PLAYGROUND_CODE } from '../actions/index' export enum ApplicationEnvironment { Development = 'development', @@ -29,6 +30,19 @@ const defaultState: IApplicationState = { playgroundCode: 'Hello the world' } +type Action = UpdatePlaygroundCodeAction; + + export const reducer: Reducer = (state = defaultState, action: Action) => { - return state -} + switch (action.type) { + + case UPDATE_PLAYGROUND_CODE: + return { + ...state, + playgroundCode : action.playgroundCode + }; + + default: + return state; + } +}; From 48bd3694b78a2328ff2959d00fa57a649e32b5c5 Mon Sep 17 00:00:00 2001 From: remo5000 Date: Tue, 15 May 2018 16:51:23 +0800 Subject: [PATCH 012/129] Change initialValue prop to editorValue --- src/components/Playground.tsx | 4 ++-- src/components/__tests__/Playground.tsx | 2 +- src/containers/PlaygroundContainer.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/Playground.tsx b/src/components/Playground.tsx index 65a2b0677e..dba20fd84d 100644 --- a/src/components/Playground.tsx +++ b/src/components/Playground.tsx @@ -2,7 +2,7 @@ import * as React from 'react' import AceEditor from 'react-ace' export interface IPlaygroundProps { - initialCode: string; + editorValue: string; updateCode: (newCode: string) => void; }; @@ -14,7 +14,7 @@ export default class Playground extends React.Component { diff --git a/src/components/__tests__/Playground.tsx b/src/components/__tests__/Playground.tsx index 7d8354313b..cc80d47a11 100644 --- a/src/components/__tests__/Playground.tsx +++ b/src/components/__tests__/Playground.tsx @@ -7,7 +7,7 @@ import { IPlaygroundProps as PlaygroundProps } from '../Playground'; test('Playground renders correctly', () => { const props: PlaygroundProps = { - initialCode: 'Hello the world', + editorValue: 'Hello the world', updateCode: (newCode : string ) => { return;} } const app = diff --git a/src/containers/PlaygroundContainer.ts b/src/containers/PlaygroundContainer.ts index 4ee8005381..5e0eaafe71 100644 --- a/src/containers/PlaygroundContainer.ts +++ b/src/containers/PlaygroundContainer.ts @@ -7,12 +7,12 @@ import { IPlaygroundProps as PlaygroundProps} from '../components/Playground'; import { IState } from '../reducers'; -type StateProps = Pick; +type StateProps = Pick; type DispatchProps = Pick; const mapStateToProps: MapStateToProps = state => { return { - initialCode: state.application.playgroundCode + editorValue: state.application.playgroundCode } } From 023f277a12df44b0bc222a6b66877d5dd02a5d88 Mon Sep 17 00:00:00 2001 From: ning Date: Tue, 15 May 2018 16:52:22 +0800 Subject: [PATCH 013/129] Made playground actions a separate file --- src/actions/index.ts | 17 ++--------------- src/actions/playground.ts | 12 ++++++++++++ 2 files changed, 14 insertions(+), 15 deletions(-) create mode 100644 src/actions/playground.ts diff --git a/src/actions/index.ts b/src/actions/index.ts index b7392eee8d..762cdb9c7e 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -1,16 +1,3 @@ -export default {} +import { IUpdateEditorValue, UPDATE_EDITOR_VALUE, updateEditorValue } from './playground' -export type UPDATE_PLAYGROUND_CODE = 'UPDATE_PLAYGROUND_CODE'; -export const UPDATE_PLAYGROUND_CODE: UPDATE_PLAYGROUND_CODE = 'UPDATE_PLAYGROUND_CODE'; - -export interface IUpdatePlaygroundCodeAction { - type: UPDATE_PLAYGROUND_CODE - playgroundCode: string - }; - -export function updatePlaygroundCode(newCode: string): IUpdatePlaygroundCodeAction { - return { - type: UPDATE_PLAYGROUND_CODE, - playgroundCode: newCode - }; - }; \ No newline at end of file +export default { IUpdateEditorValue, UPDATE_EDITOR_VALUE, updateEditorValue } diff --git a/src/actions/playground.ts b/src/actions/playground.ts new file mode 100644 index 0000000000..4a1b8f75ce --- /dev/null +++ b/src/actions/playground.ts @@ -0,0 +1,12 @@ +import { Action, ActionCreator } from 'redux' + +export type UPDATE_EDITOR_VALUE = 'UPDATE_EDITOR_VALUE' +const UPDATE_EDITOR_VALUE: UPDATE_EDITOR_VALUE = 'UPDATE_EDITOR_VALUE' +export interface IUpdateEditorValue extends Action { + type: UPDATE_EDITOR_VALUE + newEditorValue: string +} +export const updateEditorValue: ActionCreator = (newEditorValue: string) => ({ + UPDATE_EDITOR_VALUE, + newEditorValue +}) From dd585f93eea4ace21ae492e47ae56190ecf535c4 Mon Sep 17 00:00:00 2001 From: remo5000 Date: Tue, 15 May 2018 17:33:10 +0800 Subject: [PATCH 014/129] Modify actions and dispatch call --- src/actions/index.ts | 3 --- src/actions/playground.ts | 9 +++++---- src/containers/PlaygroundContainer.ts | 20 +++++++++----------- 3 files changed, 14 insertions(+), 18 deletions(-) delete mode 100644 src/actions/index.ts diff --git a/src/actions/index.ts b/src/actions/index.ts deleted file mode 100644 index 762cdb9c7e..0000000000 --- a/src/actions/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { IUpdateEditorValue, UPDATE_EDITOR_VALUE, updateEditorValue } from './playground' - -export default { IUpdateEditorValue, UPDATE_EDITOR_VALUE, updateEditorValue } diff --git a/src/actions/playground.ts b/src/actions/playground.ts index 4a1b8f75ce..4464eb6a2d 100644 --- a/src/actions/playground.ts +++ b/src/actions/playground.ts @@ -1,12 +1,13 @@ import { Action, ActionCreator } from 'redux' -export type UPDATE_EDITOR_VALUE = 'UPDATE_EDITOR_VALUE' -const UPDATE_EDITOR_VALUE: UPDATE_EDITOR_VALUE = 'UPDATE_EDITOR_VALUE' +export const UPDATE_EDITOR_VALUE: string = 'UPDATE_EDITOR_VALUE' + export interface IUpdateEditorValue extends Action { - type: UPDATE_EDITOR_VALUE + type: string newEditorValue: string } + export const updateEditorValue: ActionCreator = (newEditorValue: string) => ({ - UPDATE_EDITOR_VALUE, + type: UPDATE_EDITOR_VALUE, newEditorValue }) diff --git a/src/containers/PlaygroundContainer.ts b/src/containers/PlaygroundContainer.ts index 5e0eaafe71..5c1c146c93 100644 --- a/src/containers/PlaygroundContainer.ts +++ b/src/containers/PlaygroundContainer.ts @@ -1,14 +1,12 @@ -import * as Actions from '../actions/index' -import PlaygroundComponent from '../components/Playground'; - import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux' -import { Dispatch } from 'redux'; -import { IPlaygroundProps as PlaygroundProps} from '../components/Playground'; -import { IState } from '../reducers'; +import { Dispatch } from 'redux' +import { updateEditorValue } from '../actions/playground' +import { default as PlaygroundComponent, IPlaygroundProps as PlaygroundProps} from '../components/Playground' +import { IState } from '../reducers' -type StateProps = Pick; -type DispatchProps = Pick; +type StateProps = Pick +type DispatchProps = Pick const mapStateToProps: MapStateToProps = state => { return { @@ -19,9 +17,9 @@ const mapStateToProps: MapStateToProps = state => { const mapDispatchToProps : MapDispatchToProps = (dispatch: Dispatch) => { return { updateCode: (newCode: string) => { - dispatch(Actions.updatePlaygroundCode(newCode)); + dispatch(updateEditorValue(newCode)); }, - }; -}; + } +} export default connect(mapStateToProps, mapDispatchToProps)(PlaygroundComponent) From 053b2c98e4741ecb3fcc9bc21db7251454f875c1 Mon Sep 17 00:00:00 2001 From: remo5000 Date: Tue, 15 May 2018 17:34:26 +0800 Subject: [PATCH 015/129] Change redux import statement --- src/containers/PlaygroundContainer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/containers/PlaygroundContainer.ts b/src/containers/PlaygroundContainer.ts index 5c1c146c93..d3fc2a3f3f 100644 --- a/src/containers/PlaygroundContainer.ts +++ b/src/containers/PlaygroundContainer.ts @@ -10,7 +10,7 @@ type DispatchProps = Pick const mapStateToProps: MapStateToProps = state => { return { - editorValue: state.application.playgroundCode + editorValue: state.playground.editorValue } } From b17cb2511b4813fddbf8722a78c10b33bef4a9f1 Mon Sep 17 00:00:00 2001 From: remo5000 Date: Tue, 15 May 2018 17:43:44 +0800 Subject: [PATCH 016/129] Add IPlaygroundState to IState in index reducer --- src/reducers/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/reducers/index.ts b/src/reducers/index.ts index a5ce7a4d83..7b3e015371 100644 --- a/src/reducers/index.ts +++ b/src/reducers/index.ts @@ -1,9 +1,12 @@ import { IApplicationState, reducer as application } from './application' +import { IPlaygroundState, reducer as playground } from './playground' export interface IState { readonly application: IApplicationState + readonly playground: IPlaygroundState } export default { - application + application, + playground } From 8d480a3911c3403c75057981da461929d1fb5f2b Mon Sep 17 00:00:00 2001 From: remo5000 Date: Tue, 15 May 2018 17:46:53 +0800 Subject: [PATCH 017/129] Add playground reducer --- src/reducers/playground.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/reducers/playground.ts diff --git a/src/reducers/playground.ts b/src/reducers/playground.ts new file mode 100644 index 0000000000..a542151eb2 --- /dev/null +++ b/src/reducers/playground.ts @@ -0,0 +1,24 @@ +import { Action, Reducer } from 'redux' +import { IUpdateEditorValue, UPDATE_EDITOR_VALUE } from '../actions/playground' + +export interface IPlaygroundState { + editorValue: string +} + +const defaultState: IPlaygroundState = { + editorValue: '' +} + +export const reducer: Reducer = (state = defaultState, action: Action) => { + switch (action.type) { + + case UPDATE_EDITOR_VALUE: + return { + ...state, + editorValue : ( action).newEditorValue + }; + + default: + return state; + } +}; From 7c3ad6d2b0923c9c44cf3070d649e3db85ff96f3 Mon Sep 17 00:00:00 2001 From: ning Date: Tue, 15 May 2018 17:49:28 +0800 Subject: [PATCH 018/129] Remove playgroundCode from IApplicationState --- src/components/__tests__/Application.tsx | 3 +-- src/reducers/application.ts | 20 ++------------------ 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/src/components/__tests__/Application.tsx b/src/components/__tests__/Application.tsx index d755946e18..53ed0dcefe 100644 --- a/src/components/__tests__/Application.tsx +++ b/src/components/__tests__/Application.tsx @@ -11,8 +11,7 @@ test('Application renders correctly', () => { ...mockRouterProps('/dashboard', {}), application: { title: 'Cadet', - environment: ApplicationEnvironment.Development, - playgroundCode: '' + environment: ApplicationEnvironment.Development } } const app = diff --git a/src/reducers/application.ts b/src/reducers/application.ts index f18be3c9cc..083cb695b0 100644 --- a/src/reducers/application.ts +++ b/src/reducers/application.ts @@ -1,5 +1,4 @@ -import { Reducer } from 'redux' -import { IUpdatePlaygroundCodeAction as UpdatePlaygroundCodeAction, UPDATE_PLAYGROUND_CODE } from '../actions/index' +import { Action, Reducer } from 'redux' export enum ApplicationEnvironment { Development = 'development', @@ -10,7 +9,6 @@ export enum ApplicationEnvironment { export interface IApplicationState { title: string environment: ApplicationEnvironment - playgroundCode: string } const currentEnvironment = (): ApplicationEnvironment => { @@ -27,22 +25,8 @@ const currentEnvironment = (): ApplicationEnvironment => { const defaultState: IApplicationState = { title: 'Cadet', environment: currentEnvironment(), - playgroundCode: 'Hello the world' } -type Action = UpdatePlaygroundCodeAction; - - export const reducer: Reducer = (state = defaultState, action: Action) => { - switch (action.type) { - - case UPDATE_PLAYGROUND_CODE: - return { - ...state, - playgroundCode : action.playgroundCode - }; - - default: - return state; - } + return state; }; From fcf1c604a2f56ac0aa1b59d4a3dd238dfdff27f3 Mon Sep 17 00:00:00 2001 From: ning Date: Tue, 15 May 2018 19:01:50 +0800 Subject: [PATCH 019/129] Fix type errors Made a compromise by including an IPlaygroundState as part of IApplicationProps --- src/components/Application.tsx | 2 ++ src/components/Playground.tsx | 12 ++++++------ src/components/__tests__/Application.tsx | 3 +++ src/components/__tests__/Playground.tsx | 6 ++++-- .../__tests__/__snapshots__/Playground.tsx.snap | 3 +-- src/containers/ApplicationContainer.ts | 3 ++- src/containers/PlaygroundContainer.ts | 11 +++++++---- src/mocks/store.ts | 6 ++++-- .../__tests__/__snapshots__/application.ts.snap | 1 - src/reducers/application.ts | 6 +++--- src/reducers/playground.ts | 17 ++++++++--------- 11 files changed, 40 insertions(+), 30 deletions(-) diff --git a/src/components/Application.tsx b/src/components/Application.tsx index c12423465a..16afa571d5 100644 --- a/src/components/Application.tsx +++ b/src/components/Application.tsx @@ -5,11 +5,13 @@ import { Redirect, Route, RouteComponentProps, Switch } from 'react-router' import DashboardContainer from '../containers/DashboardContainer' import PlaygroundContainer from '../containers/PlaygroundContainer' import { IApplicationState } from '../reducers/application' +import { IPlaygroundState } from '../reducers/playground' import NavigationBar from './NavigationBar' import NotFound from './NotFound' export interface IApplicationProps extends RouteComponentProps<{}> { application: IApplicationState + playground: IPlaygroundState } const Application: React.SFC = ({ application }) => { diff --git a/src/components/Playground.tsx b/src/components/Playground.tsx index dba20fd84d..48097fa9f6 100644 --- a/src/components/Playground.tsx +++ b/src/components/Playground.tsx @@ -2,22 +2,22 @@ import * as React from 'react' import AceEditor from 'react-ace' export interface IPlaygroundProps { - editorValue: string; - updateCode: (newCode: string) => void; -}; + editorValue: string + updateCode: (newCode: string) => void +} export default class Playground extends React.Component { public render() { return (
-

Playground

- Playground +
- ); + ) } } diff --git a/src/components/__tests__/Application.tsx b/src/components/__tests__/Application.tsx index 53ed0dcefe..b0a4348428 100644 --- a/src/components/__tests__/Application.tsx +++ b/src/components/__tests__/Application.tsx @@ -12,6 +12,9 @@ test('Application renders correctly', () => { application: { title: 'Cadet', environment: ApplicationEnvironment.Development + }, + playground: { + editorValue: '' } } const app = diff --git a/src/components/__tests__/Playground.tsx b/src/components/__tests__/Playground.tsx index cc80d47a11..8e685bf0c4 100644 --- a/src/components/__tests__/Playground.tsx +++ b/src/components/__tests__/Playground.tsx @@ -3,12 +3,14 @@ import * as React from 'react' import { shallow } from 'enzyme' import Playground from '../Playground' -import { IPlaygroundProps as PlaygroundProps } from '../Playground'; +import { IPlaygroundProps as PlaygroundProps } from '../Playground' test('Playground renders correctly', () => { const props: PlaygroundProps = { editorValue: 'Hello the world', - updateCode: (newCode : string ) => { return;} + updateCode: (newCode: string) => { + return + } } const app = const tree = shallow(app) diff --git a/src/components/__tests__/__snapshots__/Playground.tsx.snap b/src/components/__tests__/__snapshots__/Playground.tsx.snap index ab4bbaf7c1..5688c45751 100644 --- a/src/components/__tests__/__snapshots__/Playground.tsx.snap +++ b/src/components/__tests__/__snapshots__/Playground.tsx.snap @@ -5,7 +5,6 @@ exports[`Playground renders correctly 1`] = `

Playground

- - + " `; diff --git a/src/containers/ApplicationContainer.ts b/src/containers/ApplicationContainer.ts index f22403d51f..7e9aab4839 100644 --- a/src/containers/ApplicationContainer.ts +++ b/src/containers/ApplicationContainer.ts @@ -5,7 +5,8 @@ import Application, { IApplicationProps } from '../components/Application' import { IState } from '../reducers' const mapStateToProps: MapStateToProps = state => ({ - application: state.application + application: state.application, + playground: state.playground }) export default withRouter(connect(mapStateToProps)(Application)) diff --git a/src/containers/PlaygroundContainer.ts b/src/containers/PlaygroundContainer.ts index d3fc2a3f3f..94a59b557d 100644 --- a/src/containers/PlaygroundContainer.ts +++ b/src/containers/PlaygroundContainer.ts @@ -2,7 +2,10 @@ import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux' import { Dispatch } from 'redux' import { updateEditorValue } from '../actions/playground' -import { default as PlaygroundComponent, IPlaygroundProps as PlaygroundProps} from '../components/Playground' +import { + default as PlaygroundComponent, + IPlaygroundProps as PlaygroundProps +} from '../components/Playground' import { IState } from '../reducers' type StateProps = Pick @@ -14,11 +17,11 @@ const mapStateToProps: MapStateToProps = state => { } } -const mapDispatchToProps : MapDispatchToProps = (dispatch: Dispatch) => { +const mapDispatchToProps: MapDispatchToProps = (dispatch: Dispatch) => { return { updateCode: (newCode: string) => { - dispatch(updateEditorValue(newCode)); - }, + dispatch(updateEditorValue(newCode)) + } } } diff --git a/src/mocks/store.ts b/src/mocks/store.ts index 3a51ca84e6..d2656b1a7a 100644 --- a/src/mocks/store.ts +++ b/src/mocks/store.ts @@ -9,8 +9,10 @@ export function mockInitialStore

(): Store { const state: IState = { application: { title: 'Cadet', - environment: ApplicationEnvironment.Development, - playgroundCode: 'Hello World' + environment: ApplicationEnvironment.Development + }, + playground: { + editorValue: '' } } return createStore(state) diff --git a/src/reducers/__tests__/__snapshots__/application.ts.snap b/src/reducers/__tests__/__snapshots__/application.ts.snap index 1fdd14dd60..21139a3532 100644 --- a/src/reducers/__tests__/__snapshots__/application.ts.snap +++ b/src/reducers/__tests__/__snapshots__/application.ts.snap @@ -3,7 +3,6 @@ exports[`initial state should match a snapshot 1`] = ` Object { "environment": "test", - "playgroundCode": "Hello the world", "title": "Cadet", } `; diff --git a/src/reducers/application.ts b/src/reducers/application.ts index 083cb695b0..6885552c2a 100644 --- a/src/reducers/application.ts +++ b/src/reducers/application.ts @@ -24,9 +24,9 @@ const currentEnvironment = (): ApplicationEnvironment => { const defaultState: IApplicationState = { title: 'Cadet', - environment: currentEnvironment(), + environment: currentEnvironment() } export const reducer: Reducer = (state = defaultState, action: Action) => { - return state; -}; + return state +} diff --git a/src/reducers/playground.ts b/src/reducers/playground.ts index a542151eb2..2f9658a7c0 100644 --- a/src/reducers/playground.ts +++ b/src/reducers/playground.ts @@ -11,14 +11,13 @@ const defaultState: IPlaygroundState = { export const reducer: Reducer = (state = defaultState, action: Action) => { switch (action.type) { + case UPDATE_EDITOR_VALUE: + return { + ...state, + editorValue: (action as IUpdateEditorValue).newEditorValue + } - case UPDATE_EDITOR_VALUE: - return { - ...state, - editorValue : ( action).newEditorValue - }; - - default: - return state; + default: + return state } -}; +} From 4004d644806109d04498fb053ff91d22f85c9450 Mon Sep 17 00:00:00 2001 From: ning Date: Tue, 15 May 2018 19:18:47 +0800 Subject: [PATCH 020/129] Re-disable console.logs (tslint) --- tslint.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tslint.json b/tslint.json index 5a9bbe2a2a..38976e529b 100644 --- a/tslint.json +++ b/tslint.json @@ -1,8 +1,7 @@ { "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"], "rules": { - "object-literal-sort-keys": false, - "no-console": false + "object-literal-sort-keys": false }, "linterOptions": { "exclude": [ From 18790837834f7cd2dc0885a6066638b5e96d52e4 Mon Sep 17 00:00:00 2001 From: ning Date: Tue, 15 May 2018 19:38:07 +0800 Subject: [PATCH 021/129] Fix missing imports for react-ace mode, theme --- src/components/Playground.tsx | 5 ++++- src/components/__tests__/__snapshots__/Playground.tsx.snap | 2 +- src/reducers/playground.ts | 1 - 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/Playground.tsx b/src/components/Playground.tsx index 48097fa9f6..09f0c85959 100644 --- a/src/components/Playground.tsx +++ b/src/components/Playground.tsx @@ -1,6 +1,9 @@ import * as React from 'react' import AceEditor from 'react-ace' +import 'brace/mode/javascript' +import 'brace/theme/github' + export interface IPlaygroundProps { editorValue: string updateCode: (newCode: string) => void @@ -12,7 +15,7 @@ export default class Playground extends React.Component {

Playground

Playground - +
" `; diff --git a/src/reducers/playground.ts b/src/reducers/playground.ts index 2f9658a7c0..d0a032e4c4 100644 --- a/src/reducers/playground.ts +++ b/src/reducers/playground.ts @@ -16,7 +16,6 @@ export const reducer: Reducer = (state = defaultState, action: ...state, editorValue: (action as IUpdateEditorValue).newEditorValue } - default: return state } From 1f5edb7275043bffc9ae4eb35b20d7be2b20476c Mon Sep 17 00:00:00 2001 From: ning Date: Tue, 15 May 2018 19:56:31 +0800 Subject: [PATCH 022/129] Add playground CSS --- src/components/Playground.tsx | 14 ++++++++------ src/styles/index.css | 1 + src/styles/playground.css | 11 +++++++++++ 3 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 src/styles/playground.css diff --git a/src/components/Playground.tsx b/src/components/Playground.tsx index 09f0c85959..3d94ab5438 100644 --- a/src/components/Playground.tsx +++ b/src/components/Playground.tsx @@ -14,12 +14,14 @@ export default class Playground extends React.Component { return (

Playground

- +
) } diff --git a/src/styles/index.css b/src/styles/index.css index 3fbe733b45..8cff40f342 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -2,4 +2,5 @@ @import "~@blueprintjs/core/lib/css/blueprint.css"; @import "./blueprint.css"; @import "./application.css"; +@import "./playground.css"; @import "./navigationBar.css"; diff --git a/src/styles/playground.css b/src/styles/playground.css new file mode 100644 index 0000000000..5810939f9d --- /dev/null +++ b/src/styles/playground.css @@ -0,0 +1,11 @@ +.Playground { + display: flex; + flex-direction: column; + height: 100vh; + width: 100vh; +} + +.Playground__main { + margin-top: 1rem; + margin-left: 1rem; +} From 376ec46ec34ac8e9793d1b5c70308d6500f0878e Mon Sep 17 00:00:00 2001 From: ning Date: Tue, 15 May 2018 23:00:53 +0800 Subject: [PATCH 023/129] Add JSDoc style comments for playground --- src/actions/playground.ts | 14 ++++++++++++++ src/components/Playground.tsx | 8 ++++++++ src/containers/PlaygroundContainer.ts | 7 +++++++ src/reducers/playground.ts | 12 ++++++++++++ 4 files changed, 41 insertions(+) diff --git a/src/actions/playground.ts b/src/actions/playground.ts index 4464eb6a2d..813a98c9bd 100644 --- a/src/actions/playground.ts +++ b/src/actions/playground.ts @@ -1,12 +1,26 @@ import { Action, ActionCreator } from 'redux' +/** + * The `type` attribute for an `Action` which updates the `IPlaygroundState` + * `editorValue` + */ export const UPDATE_EDITOR_VALUE: string = 'UPDATE_EDITOR_VALUE' +/** + * Represents an `Action` which updates the `editorValue` of a + * `IPlaygroundState` + * @property type - Unique string identifier for this `Action` + * @property newEditorValue - The new string value for `editorValue` + */ export interface IUpdateEditorValue extends Action { type: string newEditorValue: string } +/** + * An `ActionCreator` returning an `IUpdateEditorValue` `Action` + * @param newEditorValue - The new string value for `editorValue` + */ export const updateEditorValue: ActionCreator = (newEditorValue: string) => ({ type: UPDATE_EDITOR_VALUE, newEditorValue diff --git a/src/components/Playground.tsx b/src/components/Playground.tsx index 3d94ab5438..b6b1cc3317 100644 --- a/src/components/Playground.tsx +++ b/src/components/Playground.tsx @@ -4,11 +4,19 @@ import AceEditor from 'react-ace' import 'brace/mode/javascript' import 'brace/theme/github' +/** + * A prop that is passed to the Playground + * @property editorValue - The string content of the react-ace editor + * @property updateCode - A callback function for the react-ace editor's `onChange` + */ export interface IPlaygroundProps { editorValue: string updateCode: (newCode: string) => void } +/** + * A component representing the Playground + */ export default class Playground extends React.Component { public render() { return ( diff --git a/src/containers/PlaygroundContainer.ts b/src/containers/PlaygroundContainer.ts index 94a59b557d..e4313444ad 100644 --- a/src/containers/PlaygroundContainer.ts +++ b/src/containers/PlaygroundContainer.ts @@ -11,12 +11,19 @@ import { IState } from '../reducers' type StateProps = Pick type DispatchProps = Pick +/** Provides the editorValue of the `IPlaygroundState` of the `IState` as a + * `StateProps` to the Playground component + */ const mapStateToProps: MapStateToProps = state => { return { editorValue: state.playground.editorValue } } +/** Provides a callback function `updateCode` which supplies the `Action` + * `updateEditorValue` with `newCode`, the updated contents of the react-ace + * editor. + */ const mapDispatchToProps: MapDispatchToProps = (dispatch: Dispatch) => { return { updateCode: (newCode: string) => { diff --git a/src/reducers/playground.ts b/src/reducers/playground.ts index d0a032e4c4..59dbed520d 100644 --- a/src/reducers/playground.ts +++ b/src/reducers/playground.ts @@ -1,14 +1,26 @@ import { Action, Reducer } from 'redux' import { IUpdateEditorValue, UPDATE_EDITOR_VALUE } from '../actions/playground' +/** + * A state for the playground container + * @property editorValue - The string content of the react-ace editor + */ export interface IPlaygroundState { editorValue: string } +/** + * The default (initial) state of the `IPlaygroundState` + */ const defaultState: IPlaygroundState = { editorValue: '' } +/** + * The reducer for `IPlaygroundState` + * + * UPDATE_EDITOR_VALUE: Update the `editorValue` property + */ export const reducer: Reducer = (state = defaultState, action: Action) => { switch (action.type) { case UPDATE_EDITOR_VALUE: From eb37db75bcb935db17207e23d700dba5fe5182f3 Mon Sep 17 00:00:00 2001 From: ning Date: Tue, 15 May 2018 23:04:00 +0800 Subject: [PATCH 024/129] Fix some tests --- src/components/__tests__/Playground.tsx | 2 +- src/components/__tests__/__snapshots__/Playground.tsx.snap | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/__tests__/Playground.tsx b/src/components/__tests__/Playground.tsx index 8e685bf0c4..865082b051 100644 --- a/src/components/__tests__/Playground.tsx +++ b/src/components/__tests__/Playground.tsx @@ -7,7 +7,7 @@ import { IPlaygroundProps as PlaygroundProps } from '../Playground' test('Playground renders correctly', () => { const props: PlaygroundProps = { - editorValue: 'Hello the world', + editorValue: '', updateCode: (newCode: string) => { return } diff --git a/src/components/__tests__/__snapshots__/Playground.tsx.snap b/src/components/__tests__/__snapshots__/Playground.tsx.snap index 185b829640..1ea9aabd01 100644 --- a/src/components/__tests__/__snapshots__/Playground.tsx.snap +++ b/src/components/__tests__/__snapshots__/Playground.tsx.snap @@ -5,6 +5,6 @@ exports[`Playground renders correctly 1`] = `

Playground

- + " `; From 931a204d251b752798721959f79df9eda0b94616 Mon Sep 17 00:00:00 2001 From: remo5000 Date: Wed, 16 May 2018 09:53:23 +0800 Subject: [PATCH 025/129] Fix semantics of mapStateToProps for ApplicationContainer --- src/components/Application.tsx | 9 +++------ src/components/__tests__/Application.tsx | 9 +-------- src/containers/ApplicationContainer.ts | 15 ++++++++++----- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/components/Application.tsx b/src/components/Application.tsx index 16afa571d5..9ae4625916 100644 --- a/src/components/Application.tsx +++ b/src/components/Application.tsx @@ -4,22 +4,19 @@ import { Redirect, Route, RouteComponentProps, Switch } from 'react-router' import DashboardContainer from '../containers/DashboardContainer' import PlaygroundContainer from '../containers/PlaygroundContainer' -import { IApplicationState } from '../reducers/application' -import { IPlaygroundState } from '../reducers/playground' import NavigationBar from './NavigationBar' import NotFound from './NotFound' export interface IApplicationProps extends RouteComponentProps<{}> { - application: IApplicationState - playground: IPlaygroundState + title: string } -const Application: React.SFC = ({ application }) => { +const Application: React.SFC = (props: IApplicationProps) => { const redirectToDashboard = () => return (
- +
diff --git a/src/components/__tests__/Application.tsx b/src/components/__tests__/Application.tsx index b0a4348428..addbb8adf7 100644 --- a/src/components/__tests__/Application.tsx +++ b/src/components/__tests__/Application.tsx @@ -3,19 +3,12 @@ import * as React from 'react' import { shallow } from 'enzyme' import { mockRouterProps } from '../../mocks/components' -import { ApplicationEnvironment } from '../../reducers/application' import Application, { IApplicationProps } from '../Application' test('Application renders correctly', () => { const props: IApplicationProps = { ...mockRouterProps('/dashboard', {}), - application: { - title: 'Cadet', - environment: ApplicationEnvironment.Development - }, - playground: { - editorValue: '' - } + title: 'Cadet' } const app = const tree = shallow(app) diff --git a/src/containers/ApplicationContainer.ts b/src/containers/ApplicationContainer.ts index 7e9aab4839..357b369d3f 100644 --- a/src/containers/ApplicationContainer.ts +++ b/src/containers/ApplicationContainer.ts @@ -1,12 +1,17 @@ import { connect, MapStateToProps } from 'react-redux' import { withRouter } from 'react-router' - -import Application, { IApplicationProps } from '../components/Application' +import Application from '../components/Application' import { IState } from '../reducers' -const mapStateToProps: MapStateToProps = state => ({ - application: state.application, - playground: state.playground +/** + * Provides the title of the application for display. + * An object with the relevant properties must be + * returned instead of an object of type @type {IApplicationProps}, + * as the routing properties of @type {RouteComponentProps} are + * provided using the withRouter() method below. + */ +const mapStateToProps: MapStateToProps<{ title: string }, {}, IState> = state => ({ + title: state.application.title }) export default withRouter(connect(mapStateToProps)(Application)) From b451e08dae9329b527c3cf02329addfabd46501e Mon Sep 17 00:00:00 2001 From: remo5000 Date: Wed, 16 May 2018 09:54:38 +0800 Subject: [PATCH 026/129] Do trivial formatting --- src/actions/playground.ts | 4 ++-- src/components/Playground.tsx | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/actions/playground.ts b/src/actions/playground.ts index 813a98c9bd..97d541f4bd 100644 --- a/src/actions/playground.ts +++ b/src/actions/playground.ts @@ -8,7 +8,7 @@ export const UPDATE_EDITOR_VALUE: string = 'UPDATE_EDITOR_VALUE' /** * Represents an `Action` which updates the `editorValue` of a - * `IPlaygroundState` + * `IPlaygroundState` * @property type - Unique string identifier for this `Action` * @property newEditorValue - The new string value for `editorValue` */ @@ -18,7 +18,7 @@ export interface IUpdateEditorValue extends Action { } /** - * An `ActionCreator` returning an `IUpdateEditorValue` `Action` + * An `ActionCreator` returning an `IUpdateEditorValue` `Action` * @param newEditorValue - The new string value for `editorValue` */ export const updateEditorValue: ActionCreator = (newEditorValue: string) => ({ diff --git a/src/components/Playground.tsx b/src/components/Playground.tsx index b6b1cc3317..d0f6b332e1 100644 --- a/src/components/Playground.tsx +++ b/src/components/Playground.tsx @@ -22,14 +22,14 @@ export default class Playground extends React.Component { return (

Playground

- +
) } From 71d8c2dac145b022d557b2aae0e483fdf96cd493 Mon Sep 17 00:00:00 2001 From: ning Date: Wed, 16 May 2018 13:46:25 +0800 Subject: [PATCH 027/129] Add interpreter toolchain as 'slang' This is a copy over of the current state of `./frontend/src/toolchain` at source-academy/source-academy2 67d9184. It includes only source chapter 1, a new specification (see [PR 6 at the source-academy/source-academy2](https://github.com/source-academy/source-academy2/pull/6) repository). --- src/slang/cfg.ts | 157 ++++++ src/slang/constants.ts | 21 + src/slang/createContext.ts | 145 +++++ src/slang/index.ts | 60 ++ src/slang/interop.ts | 82 +++ src/slang/interpreter-errors.ts | 138 +++++ src/slang/interpreter.ts | 532 ++++++++++++++++++ src/slang/parser.ts | 205 +++++++ src/slang/rules/bracesAroundIfElse.ts | 95 ++++ src/slang/rules/bracesAroundWhile.ts | 38 ++ src/slang/rules/index.ts | 31 + src/slang/rules/noBlockArrowFunction.ts | 37 ++ src/slang/rules/noDeclareMutable.ts | 44 ++ src/slang/rules/noDeclareReserved.ts | 89 +++ src/slang/rules/noIfWithoutElse.ts | 45 ++ src/slang/rules/noImplicitDeclareUndefined.ts | 49 ++ src/slang/rules/noImplicitReturnUndefined.ts | 44 ++ src/slang/rules/noNonEmptyList.ts | 40 ++ src/slang/rules/singleVariableDeclaration.ts | 52 ++ src/slang/rules/strictEquality.ts | 42 ++ src/slang/schedulers.ts | 64 +++ src/slang/stdlib/list.ts | 425 ++++++++++++++ src/slang/stdlib/misc.ts | 66 +++ src/slang/stdlib/object.ts | 5 + src/slang/syntaxTypes.ts | 53 ++ src/slang/types.ts | 195 +++++++ src/slang/typings/acorn.d.ts | 53 ++ src/slang/typings/astring.d.ts | 1 + src/slang/typings/estree.d.ts | 8 + src/slang/utils/node.ts | 124 ++++ src/slang/utils/rttc.ts | 91 +++ 31 files changed, 3031 insertions(+) create mode 100644 src/slang/cfg.ts create mode 100644 src/slang/constants.ts create mode 100644 src/slang/createContext.ts create mode 100644 src/slang/index.ts create mode 100644 src/slang/interop.ts create mode 100644 src/slang/interpreter-errors.ts create mode 100644 src/slang/interpreter.ts create mode 100644 src/slang/parser.ts create mode 100644 src/slang/rules/bracesAroundIfElse.ts create mode 100644 src/slang/rules/bracesAroundWhile.ts create mode 100644 src/slang/rules/index.ts create mode 100644 src/slang/rules/noBlockArrowFunction.ts create mode 100644 src/slang/rules/noDeclareMutable.ts create mode 100644 src/slang/rules/noDeclareReserved.ts create mode 100644 src/slang/rules/noIfWithoutElse.ts create mode 100644 src/slang/rules/noImplicitDeclareUndefined.ts create mode 100644 src/slang/rules/noImplicitReturnUndefined.ts create mode 100644 src/slang/rules/noNonEmptyList.ts create mode 100644 src/slang/rules/singleVariableDeclaration.ts create mode 100644 src/slang/rules/strictEquality.ts create mode 100644 src/slang/schedulers.ts create mode 100644 src/slang/stdlib/list.ts create mode 100644 src/slang/stdlib/misc.ts create mode 100644 src/slang/stdlib/object.ts create mode 100644 src/slang/syntaxTypes.ts create mode 100644 src/slang/types.ts create mode 100644 src/slang/typings/acorn.d.ts create mode 100644 src/slang/typings/astring.d.ts create mode 100644 src/slang/typings/estree.d.ts create mode 100644 src/slang/utils/node.ts create mode 100644 src/slang/utils/rttc.ts diff --git a/src/slang/cfg.ts b/src/slang/cfg.ts new file mode 100644 index 0000000000..56873d560a --- /dev/null +++ b/src/slang/cfg.ts @@ -0,0 +1,157 @@ +import * as invariant from 'invariant' +import * as es from 'estree' +import { recursive, Walkers, Walker, base } from 'acorn/dist/walk' + +import { composeWalker } from './utils/node' +import { Context, CFG } from './types' +import { Types } from './constants' + +const freshLambda = (() => { + let id = 0 + return () => { + id++ + return 'lambda_' + id + } +})() + +const walkers: Walkers<{}> = {} + +let nodeStack: Array = [] +let scopeQueue: CFG.Scope[] = [] +let edgeLabel: CFG.EdgeLabel = 'next' + +const currentScope = () => scopeQueue[0]! + +const connect = (node: es.Node, context: Context) => { + // Empty node stack, connect all of them with node + let lastNode = nodeStack.pop() + const vertex = context.cfg.nodes[node.__id!] + + // If there is no last node, this is the first node in the scope. + if (!lastNode) { + currentScope().entry = vertex + } + while (lastNode) { + // Connect previously visited node with this node + context.cfg.edges[lastNode.__id!].push({ + type: edgeLabel, + to: vertex + }) + lastNode = nodeStack.pop() + } + // Reset edge label + edgeLabel = 'next' + nodeStack.push(node) +} + +const exitScope = (context: Context) => { + while (nodeStack.length > 0) { + const node = nodeStack.shift()! + const vertex = context.cfg.nodes[node.__id!] + currentScope().exits.push(vertex) + } +} + +walkers.ExpressionStatement = composeWalker(connect, base.ExpressionStatement) +walkers.VariableDeclaration = composeWalker(connect, base.VariableDeclaration) + +const walkIfStatement: Walker = ( + node, + context, + recurse +) => { + const test = node.test as es.Node + let consequentExit + let alternateExit + // Connect test with previous node + connect(test, context) + + // Process the consequent branch + edgeLabel = 'consequent' + recurse(node.consequent, context) + // Remember exits from consequent + consequentExit = nodeStack + // Process the alternate branch + if (node.alternate) { + const alternate = node.alternate + edgeLabel = 'alternate' + nodeStack = [test] + recurse(alternate, context) + alternateExit = nodeStack + } + // Restore node Stack to consequent exits + nodeStack = consequentExit + // Add alternate exits if any + if (alternateExit) { + nodeStack = nodeStack.concat(alternateExit) + } +} +walkers.IfStatement = walkIfStatement + +const walkReturnStatement: Walker = ( + node, + state +) => { + connect(node, state) + exitScope(state) +} +walkers.ReturnStatement = composeWalker( + base.ReturnStatement, + walkReturnStatement +) + +const walkFunction: Walker< + es.FunctionDeclaration | es.FunctionExpression, + Context +> = (node, context, recurse) => { + // Check whether function declaration is from outer scope or its own + if (scopeQueue[0].node !== node) { + connect(node, context) + const name = node.id ? node.id.name : freshLambda() + const scope: CFG.Scope = { + name, + type: Types.ANY, + node, + parent: currentScope(), + exits: [], + env: {} + } + scopeQueue.push(scope!) + context.cfg.scopes.push(scope) + } else { + node.body.body.forEach(child => { + recurse(child, context) + }) + exitScope(context) + } +} +walkers.FunctionDeclaration = walkers.FunctionExpression = walkFunction + +const walkProgram: Walker = (node, context, recurse) => { + exitScope(context) +} +walkers.Program = composeWalker(base.Program, walkProgram) + +export const generateCFG = (context: Context) => { + invariant( + context.cfg.scopes.length >= 1, + `context.cfg.scopes must contain + exactly the global scope before generating CFG` + ) + invariant( + context.cfg.scopes[0].node, + `context.cfg.scopes[0] node + must be a program from the parser` + ) + // Reset states + nodeStack = [] + scopeQueue = [context.cfg.scopes[0]] + edgeLabel = 'next' + + // Process Node BFS style + while (scopeQueue.length > 0) { + const current = scopeQueue[0].node! + recursive(current, context, walkers) + scopeQueue.shift() + } +} diff --git a/src/slang/constants.ts b/src/slang/constants.ts new file mode 100644 index 0000000000..67fa07955f --- /dev/null +++ b/src/slang/constants.ts @@ -0,0 +1,21 @@ +import * as es from 'estree' +import { CFG } from './types' + +export const Types = { + NUMBER: { name: 'number' } as CFG.Type, + STRING: { name: 'string' } as CFG.Type, + UNDEFINED: { name: 'undefined' } as CFG.Type, + BOOLEAN: { name: 'boolean' } as CFG.Type, + ANY: { name: 'any' } as CFG.Type +} +export const MAX_LIST_DISPLAY_LENGTH = 100 +export const UNKNOWN_LOCATION: es.SourceLocation = { + start: { + line: -1, + column: -1 + }, + end: { + line: -1, + column: -1 + } +} diff --git a/src/slang/createContext.ts b/src/slang/createContext.ts new file mode 100644 index 0000000000..d1b03859f0 --- /dev/null +++ b/src/slang/createContext.ts @@ -0,0 +1,145 @@ +import { Context, Value } from './types' +import * as list from './stdlib/list' +import * as misc from './stdlib/misc' + +const GLOBAL = typeof window === 'undefined' ? global : window + +const createEmptyCFG = () => ({ + nodes: {}, + edges: {}, + scopes: [] +}) + +const createEmptyRuntime = () => ({ + isRunning: false, + frames: [], + value: undefined, + nodes: [] +}) + +export const createEmptyContext = (week: number): Context => ({ + week, + errors: [], + cfg: createEmptyCFG(), + runtime: createEmptyRuntime() +}) + +export const ensureGlobalEnvironmentExist = (context: Context) => { + if (!context.runtime) { + context.runtime = createEmptyRuntime() + } + if (!context.runtime.frames) { + context.runtime.frames = [] + } + if (context.runtime.frames.length === 0) { + context.runtime.frames.push({ + parent: null, + name: 'global', + environment: {} + }) + } +} + +const defineSymbol = (context: Context, name: string, value: Value) => { + const globalFrame = context.runtime.frames[0] + globalFrame.environment[name] = value +} + +export const importExternals = (context: Context, externals: string[]) => { + ensureGlobalEnvironmentExist(context) + + externals.forEach(symbol => { + defineSymbol(context, symbol, GLOBAL[symbol]) + }) +} + +export const importBuiltins = (context: Context) => { + ensureGlobalEnvironmentExist(context) + + if (context.week >= 3) { + defineSymbol(context, 'math_PI', Math.PI) + defineSymbol(context, 'math_sqrt', Math.sqrt) + defineSymbol(context, 'runtime', misc.runtime) + defineSymbol(context, 'display', misc.display) + defineSymbol(context, 'error', misc.error_message) + defineSymbol(context, 'prompt', prompt) + defineSymbol(context, 'parse_int', misc.parse_int) + defineSymbol(context, 'undefined', undefined) + } + + if (context.week >= 4) { + defineSymbol(context, 'math_log', Math.log) + defineSymbol(context, 'math_exp', Math.exp) + defineSymbol(context, 'alert', alert) + defineSymbol(context, 'math_floor', Math.floor) + defineSymbol(context, 'timed', misc.timed) + + // Define all Math libraries + let objs = Object.getOwnPropertyNames(Math) + for (let i in objs) { + if (objs.hasOwnProperty(i)) { + const val = objs[i] + if (typeof Math[val] === 'function') { + defineSymbol(context, 'math_' + val, Math[val].bind()) + } else { + defineSymbol(context, 'math_' + val, Math[val]) + } + } + } + } + + if (context.week >= 5) { + defineSymbol(context, 'list', list.list) + defineSymbol(context, 'pair', list.pair) + defineSymbol(context, 'is_pair', list.is_pair) + defineSymbol(context, 'is_list', list.is_list) + defineSymbol(context, 'is_empty_list', list.is_empty_list) + defineSymbol(context, 'head', list.head) + defineSymbol(context, 'tail', list.tail) + defineSymbol(context, 'length', list.length) + defineSymbol(context, 'map', list.map) + defineSymbol(context, 'build_list', list.build_list) + defineSymbol(context, 'for_each', list.for_each) + defineSymbol(context, 'list_to_string', list.list_to_string) + defineSymbol(context, 'reverse', list.reverse) + defineSymbol(context, 'append', list.append) + defineSymbol(context, 'member', list.member) + defineSymbol(context, 'remove', list.remove) + defineSymbol(context, 'remove_all', list.remove_all) + defineSymbol(context, 'equal', list.equal) + defineSymbol(context, 'assoc', list.assoc) + defineSymbol(context, 'filter', list.filter) + defineSymbol(context, 'enum_list', list.enum_list) + defineSymbol(context, 'list_ref', list.list_ref) + defineSymbol(context, 'accumulate', list.accumulate) + if (window.ListVisualizer) { + defineSymbol(context, 'draw', window.ListVisualizer.draw) + } else { + defineSymbol(context, 'draw', function() { + throw new Error('List visualizer is not enabled') + }) + } + } + if (context.week >= 6) { + defineSymbol(context, 'is_number', misc.is_number) + } + if (context.week >= 8) { + defineSymbol(context, 'undefined', undefined) + defineSymbol(context, 'set_head', list.set_head) + defineSymbol(context, 'set_tail', list.set_tail) + } + if (context.week >= 9) { + defineSymbol(context, 'array_length', misc.array_length) + } +} + +const createContext = (week = 3, externals = []) => { + const context = createEmptyContext(week) + + importBuiltins(context) + importExternals(context, externals) + + return context +} + +export default createContext diff --git a/src/slang/index.ts b/src/slang/index.ts new file mode 100644 index 0000000000..0a20c89f4e --- /dev/null +++ b/src/slang/index.ts @@ -0,0 +1,60 @@ +import { Context, Scheduler, SourceError, Result } from './types' +import createContext from './createContext' +import { evaluate } from './interpreter' +import { InterruptedError } from './interpreter-errors' +import { parse } from './parser' +import { AsyncScheduler, PreemptiveScheduler } from './schedulers' +import { toString } from './interop' + +export type Options = { + scheduler: 'preemptive' | 'async', + steps: number +} + +const DEFAULT_OPTIONS: Options = { + scheduler: 'async', + steps: 1000 +} + +export class ParseError { + constructor(public errors: SourceError[]) { + } +} + +export function runInContext( + code: string, + context: Context, + options: Partial = {}): Promise { + const theOptions: Options = {...options, ...DEFAULT_OPTIONS} + context.errors = [] + const program = parse(code, context) + if (program) { + const it = evaluate(program, context) + let scheduler: Scheduler + if (options.scheduler === 'async') { + scheduler = new AsyncScheduler() + } else { + scheduler = new PreemptiveScheduler(theOptions.steps) + } + return scheduler.run(it, context) + } else { + return Promise.resolve({ status: 'error' } as Result) + } +} + +export function resume(result: Result) { + if (result.status === 'finished' || result.status === 'error') { + return result + } else { + return result.scheduler.run(result.it, result.context) + } +} + +export function interrupt(context: Context) { + const globalFrame = context.runtime.frames[context.runtime.frames.length - 1] + context.runtime.frames = [globalFrame] + context.runtime.isRunning = false + context.errors.push(new InterruptedError(context.runtime.nodes[0])) +} + +export { createContext, toString, Context, Result } diff --git a/src/slang/interop.ts b/src/slang/interop.ts new file mode 100644 index 0000000000..b9535d7860 --- /dev/null +++ b/src/slang/interop.ts @@ -0,0 +1,82 @@ +import { generate } from 'astring' +import { Closure, Context, Value } from './types' +import { apply } from './interpreter' +import { MAX_LIST_DISPLAY_LENGTH } from './constants' + +export const closureToJS = (value: Value, context: Context, klass: string) => { + function DummyClass(this: Value) { + const args: Value[] = Array.prototype.slice.call(arguments) + const gen = apply(context, value, args, undefined, this) + let it = gen.next() + while (!it.done) { + it = gen.next() + } + return it.value + } + Object.defineProperty(DummyClass, 'name', { + value: klass + }) + DummyClass.Inherits = function(Parent: Value) { + DummyClass.prototype = Object.create(Parent.prototype) + DummyClass.prototype.constructor = DummyClass + } + DummyClass.call = function(thisArg: Value, ...args: Value[]) { + return DummyClass.apply(thisArg, args) + } + return DummyClass +} + +export const toJS = (value: Value, context: Context, klass?: string) => { + if (value instanceof Closure) { + return value.fun + } else { + return value + } +} + +const stripBody = (body: string) => { + const lines = body.split(/\n/) + if (lines.length >= 2) { + return lines[0] + '\n\t[implementation hidden]\n' + lines[lines.length - 1] + } else { + return body + } +} + +const arrayToString = (value: Value[], length: number) => { + // Normal Array + if (value.length > 2 || value.length === 1) { + return `[${value}]` + } else if (value.length === 0) { + return '[]' + } else { + return `[${toString(value[0], length + 1)}, ${toString( + value[1], + length + 1 + )}]` + } +} + +export const toString = (value: Value, length = 0): string => { + if (value instanceof Closure) { + return generate(value.node) + } else if (Array.isArray(value)) { + if (length > MAX_LIST_DISPLAY_LENGTH) { + return '...' + } else { + return arrayToString(value, length) + } + } else if (typeof value === 'string') { + return `\"${value}\"` + } else if (typeof value === 'undefined') { + return 'undefined' + } else if (typeof value === 'function') { + if (value.__SOURCE__) { + return `function ${value.__SOURCE__} {\n\t[implementation hidden]\n}` + } else { + return stripBody(value.toString()) + } + } else { + return value.toString() + } +} diff --git a/src/slang/interpreter-errors.ts b/src/slang/interpreter-errors.ts new file mode 100644 index 0000000000..3468ee06a7 --- /dev/null +++ b/src/slang/interpreter-errors.ts @@ -0,0 +1,138 @@ +import * as es from 'estree' +import { generate } from 'astring' +import { Value, SourceError, ErrorType, ErrorSeverity } from './types' +import { toString } from './interop' +import { UNKNOWN_LOCATION } from './constants' + +export class InterruptedError implements SourceError { + type = ErrorType.RUNTIME + severity = ErrorSeverity.ERROR + location: es.SourceLocation + + constructor(node: es.Node) { + this.location = node.loc! + } + + explain() { + return 'Execution aborted by user.' + } + + elaborate() { + return 'TODO' + } +} + +export class ExceptionError implements SourceError { + type = ErrorType.RUNTIME + severity = ErrorSeverity.ERROR + + constructor(public error: Error, public location: es.SourceLocation) {} + + explain() { + return this.error.toString() + } + + elaborate() { + return 'TODO' + } +} + +export class MaximumStackLimitExceeded implements SourceError { + type = ErrorType.RUNTIME + severity = ErrorSeverity.ERROR + + location: es.SourceLocation + + constructor(node: es.Node, private calls: es.CallExpression[]) { + this.location = node ? node.loc! : UNKNOWN_LOCATION + } + + explain() { + return ` + Infinite recursion + ${generate(this.calls[0])}..${generate(this.calls[1])}..${generate( + this.calls[2] + )}.. + ` + } + + elaborate() { + return 'TODO' + } +} + +export class CallingNonFunctionValue implements SourceError { + type = ErrorType.RUNTIME + severity = ErrorSeverity.ERROR + location: es.SourceLocation + + constructor(private callee: Value, node?: es.Node) { + if (node) { + this.location = node.loc! + } else { + this.location = UNKNOWN_LOCATION + } + } + + explain() { + return `Calling non-function value ${toString(this.callee)}` + } + + elaborate() { + return 'TODO' + } +} + +export class UndefinedVariable implements SourceError { + type = ErrorType.RUNTIME + severity = ErrorSeverity.ERROR + location: es.SourceLocation + + constructor(public name: string, node: es.Node) { + this.location = node.loc! + } + + explain() { + return `Undefined Variable ${this.name}` + } + + elaborate() { + return 'TODO' + } +} + +export class InvalidNumberOfArguments implements SourceError { + type = ErrorType.RUNTIME + severity = ErrorSeverity.ERROR + location: es.SourceLocation + + constructor(node: es.Node, private expected: number, private got: number) { + this.location = node.loc! + } + + explain() { + return `Expected ${this.expected} arguments, but got ${this.got}` + } + + elaborate() { + return 'TODO' + } +} + +export class VariableRedeclaration implements SourceError { + type = ErrorType.RUNTIME + severity = ErrorSeverity.ERROR + location: es.SourceLocation + + constructor(node: es.Node, private name: string) { + this.location = node.loc! + } + + explain() { + return `Redeclaring variable ${this.name}` + } + + elaborate() { + return 'TODO' + } +} diff --git a/src/slang/interpreter.ts b/src/slang/interpreter.ts new file mode 100644 index 0000000000..bdee475a17 --- /dev/null +++ b/src/slang/interpreter.ts @@ -0,0 +1,532 @@ +import * as es from 'estree' +import { + ArrowClosure, + Closure, + Frame, + Value, + Context, + SourceError, + ErrorSeverity +} from './types' +import { toJS } from './interop' +import { createNode } from './utils/node' +import * as constants from './constants' +import * as rttc from './utils/rttc' +import * as errors from './interpreter-errors' + +class ReturnValue { + constructor(public value: Value) {} +} + +class BreakValue {} + +class ContinueValue {} + +class TailCallReturnValue { + constructor( + public callee: Closure, + public args: Value[], + public node: es.CallExpression + ) {} +} + +const createFrame = ( + closure: ArrowClosure | Closure, + args: Value[], + callExpression?: es.CallExpression +): Frame => { + const frame: Frame = { + name: closure.name, // TODO: Change this + parent: closure.frame, + environment: {} + } + if (callExpression) { + frame.callExpression = { + ...callExpression, + arguments: args.map(a => createNode(a) as es.Expression) + } + } + closure.node.params.forEach((param, index) => { + const ident = param as es.Identifier + frame.environment[ident.name] = args[index] + }) + return frame +} + +const handleError = (context: Context, error: SourceError) => { + context.errors.push(error) + if (error.severity === ErrorSeverity.ERROR) { + const globalFrame = + context.runtime.frames[context.runtime.frames.length - 1] + context.runtime.frames = [globalFrame] + throw error + } else { + return context + } +} + +function defineVariable(context: Context, name: string, value: Value) { + const frame = context.runtime.frames[0] + + if (frame.environment.hasOwnProperty(name)) { + handleError( + context, + new errors.VariableRedeclaration(context.runtime.nodes[0]!, name) + ) + } + + frame.environment[name] = value + + return frame +} +function* visit(context: Context, node: es.Node) { + context.runtime.nodes.unshift(node) + yield context +} +function* leave(context: Context) { + context.runtime.nodes.shift() + yield context +} +const currentFrame = (context: Context) => context.runtime.frames[0] +const replaceFrame = (context: Context, frame: Frame) => + (context.runtime.frames[0] = frame) +const popFrame = (context: Context) => context.runtime.frames.shift() +const pushFrame = (context: Context, frame: Frame) => + context.runtime.frames.unshift(frame) + +const getVariable = (context: Context, name: string) => { + let frame: Frame | null = context.runtime.frames[0] + while (frame) { + if (frame.environment.hasOwnProperty(name)) { + return frame.environment[name] + } else { + frame = frame.parent + } + } + handleError( + context, + new errors.UndefinedVariable(name, context.runtime.nodes[0]) + ) +} + +const setVariable = (context: Context, name: string, value: any) => { + let frame: Frame | null = context.runtime.frames[0] + while (frame) { + if (frame.environment.hasOwnProperty(name)) { + frame.environment[name] = value + return + } else { + frame = frame.parent + } + } + handleError( + context, + new errors.UndefinedVariable(name, context.runtime.nodes[0]) + ) +} + +const checkNumberOfArguments = ( + context: Context, + callee: ArrowClosure | Closure, + args: Value[], + exp: es.CallExpression +) => { + if (callee.node.params.length !== args.length) { + const error = new errors.InvalidNumberOfArguments( + exp, + callee.node.params.length, + args.length + ) + handleError(context, error) + } +} + +function* getArgs(context: Context, call: es.CallExpression) { + const args = [] + for (const arg of call.arguments) { + args.push(yield* evaluate(arg, context)) + } + return args +} + +export type Evaluator = ( + node: T, + context: Context +) => IterableIterator + +export const evaluators: { [nodeType: string]: Evaluator } = { + /** Simple Values */ + Literal: function*(node: es.Literal, context: Context) { + return node.value + }, + ThisExpression: function*(node: es.ThisExpression, context: Context) { + return context.runtime.frames[0].thisContext + }, + ArrayExpression: function*(node: es.ArrayExpression, context: Context) { + const res = [] + for (const n of node.elements) { + res.push(yield* evaluate(n, context)) + } + return res + }, + FunctionExpression: function*(node: es.FunctionExpression, context: Context) { + return new Closure(node, currentFrame(context), context) + }, + ArrowFunctionExpression: function*(node: es.Function, context: Context) { + return new ArrowClosure(node, currentFrame(context), context) + }, + Identifier: function*(node: es.Identifier, context: Context) { + return getVariable(context, node.name) + }, + CallExpression: function*(node: es.CallExpression, context: Context) { + const callee = yield* evaluate(node.callee, context) + const args = yield* getArgs(context, node) + let thisContext = undefined + if (node.callee.type === 'MemberExpression') { + thisContext = yield* evaluate(node.callee.object, context) + } + const result = yield* apply(context, callee, args, node, thisContext) + return result + }, + NewExpression: function*(node: es.NewExpression, context: Context) { + const callee = yield* evaluate(node.callee, context) + const args = [] + for (const arg of node.arguments) { + args.push(yield* evaluate(arg, context)) + } + const obj: Value = {} + if (callee instanceof Closure) { + obj.__proto__ = callee.fun.prototype + callee.fun.apply(obj, args) + } else { + obj.__proto__ = callee.prototype + callee.apply(obj, args) + } + return obj + }, + UnaryExpression: function*(node: es.UnaryExpression, context: Context) { + const value = yield* evaluate(node.argument, context) + if (node.operator === '!') { + return !value + } else if (node.operator === '-') { + return -value + } else { + return +value + } + }, + BinaryExpression: function*(node: es.BinaryExpression, context: Context) { + const left = yield* evaluate(node.left, context) + const right = yield* evaluate(node.right, context) + + rttc.checkBinaryExpression(context, node.operator, left, right) + + let result + switch (node.operator) { + case '+': + result = left + right + break + case '-': + result = left - right + break + case '*': + result = left * right + break + case '/': + result = left / right + break + case '%': + result = left % right + break + case '===': + result = left === right + break + case '!==': + result = left !== right + break + case '<=': + result = left <= right + break + case '<': + result = left < right + break + case '>': + result = left > right + break + case '>=': + result = left >= right + break + default: + result = undefined + } + return result + }, + ConditionalExpression: function*( + node: es.ConditionalExpression, + context: Context + ) { + return yield* this.IfStatement(node, context) + }, + LogicalExpression: function*(node: es.LogicalExpression, context: Context) { + const left = yield* evaluate(node.left, context) + if ((node.operator === '&&' && left) || (node.operator === '||' && !left)) { + return yield* evaluate(node.right, context) + } else { + return left + } + }, + VariableDeclaration: function*( + node: es.VariableDeclaration, + context: Context + ) { + const declaration = node.declarations[0] + const kind = node.kind + const id = declaration.id as es.Identifier + const value = yield* evaluate(declaration.init!, context) + defineVariable(context, kind, id.name, value) + return undefined + }, + ContinueStatement: function*(node: es.ContinueStatement, context: Context) { + return new ContinueValue() + }, + BreakStatement: function*(node: es.BreakStatement, context: Context) { + return new BreakValue() + }, + ForStatement: function*(node: es.ForStatement, context: Context) { + if (node.init) { + yield* evaluate(node.init, context) + } + let test = node.test ? yield* evaluate(node.test, context) : true + let value + while (test) { + value = yield* evaluate(node.body, context) + if (value instanceof ContinueValue) { + value = undefined + } + if (value instanceof BreakValue) { + value = undefined + break + } + if (value instanceof ReturnValue) { + break + } + if (node.update) { + yield* evaluate(node.update, context) + } + test = node.test ? yield* evaluate(node.test, context) : true + } + if (value instanceof BreakValue) { + return undefined + } + return value + }, + MemberExpression: function*(node: es.MemberExpression, context: Context) { + let obj = yield* evaluate(node.object, context) + if (obj instanceof Closure) { + obj = obj.fun + } + if (node.computed) { + const prop = yield* evaluate(node.property, context) + return obj[prop] + } else { + const name = (node.property as es.Identifier).name + if (name === 'prototype') { + return obj.prototype + } else { + return obj[name] + } + } + }, + AssignmentExpression: function*( + node: es.AssignmentExpression, + context: Context + ) { + if (node.left.type === 'MemberExpression') { + const left = node.left + let obj = yield* evaluate(left.object, context) + let prop + if (left.computed) { + prop = yield* evaluate(left.property, context) + } else { + prop = (left.property as es.Identifier).name + } + const val = yield* evaluate(node.right, context) + obj[prop] = val + return val + } + const id = node.left as es.Identifier + // Make sure it exist + const value = yield* evaluate(node.right, context) + setVariable(context, id.name, value) + return value + }, + FunctionDeclaration: function*( + node: es.FunctionDeclaration, + context: Context + ) { + const id = node.id as es.Identifier + // tslint:disable-next-line:no-any + const closure = new Closure(node as any, currentFrame(context), context) + defineVariable(context, undefined, id.name, closure) + return undefined + }, + IfStatement: function*(node: es.IfStatement, context: Context) { + const test = yield* evaluate(node.test, context) + if (test) { + return yield* evaluate(node.consequent, context) + } else if (node.alternate) { + return yield* evaluate(node.alternate, context) + } else { + return undefined + } + }, + ExpressionStatement: function*( + node: es.ExpressionStatement, + context: Context + ) { + return yield* evaluate(node.expression, context) + }, + ReturnStatement: function*(node: es.ReturnStatement, context: Context) { + if (node.argument) { + if (node.argument.type === 'CallExpression') { + const callee = yield* evaluate(node.argument.callee, context) + const args = yield* getArgs(context, node.argument) + return new TailCallReturnValue(callee, args, node.argument) + } else { + return new ReturnValue(yield* evaluate(node.argument, context)) + } + } else { + return new ReturnValue(undefined) + } + }, + WhileStatement: function*(node: es.WhileStatement, context: Context) { + let value: any // tslint:disable-line + let test + while ( + (test = yield* evaluate(node.test, context)) && + !(value instanceof ReturnValue) && + !(value instanceof BreakValue) && + !(value instanceof TailCallReturnValue) + ) { + value = yield* evaluate(node.body, context) + } + if (value instanceof BreakValue) { + return undefined + } + return value + }, + ObjectExpression: function*(node: es.ObjectExpression, context: Context) { + const obj = {} + for (let prop of node.properties) { + let key + if (prop.key.type === 'Identifier') { + key = prop.key.name + } else { + key = yield* evaluate(prop.key, context) + } + obj[key] = yield* evaluate(prop.value, context) + } + return obj + }, + BlockStatement: function*(node: es.BlockStatement, context: Context) { + let result: Value + for (const statement of node.body) { + result = yield* evaluate(statement, context) + if ( + result instanceof ReturnValue || + result instanceof BreakValue || + result instanceof ContinueValue + ) { + break + } + } + return result + }, + Program: function*(node: es.BlockStatement, context: Context) { + let result: Value + for (const statement of node.body) { + result = yield* evaluate(statement, context) + if (result instanceof ReturnValue) { + break + } + } + return result + } +} + +export function* evaluate(node: es.Node, context: Context) { + yield* visit(context, node) + const result = yield* evaluators[node.type](node, context) + yield* leave(context) + return result +} + +export function* apply( + context: Context, + fun: ArrowClosure | Closure | Value, + args: Value[], + node?: es.CallExpression, + thisContext?: Value +) { + let result: Value + let total = 0 + + while (!(result instanceof ReturnValue)) { + if (fun instanceof Closure) { + checkNumberOfArguments(context, fun, args, node!) + const frame = createFrame(fun, args, node) + frame.thisContext = thisContext + if (result instanceof TailCallReturnValue) { + replaceFrame(context, frame) + } else { + pushFrame(context, frame) + total++ + } + result = yield* evaluate(fun.node.body, context) + if (result instanceof TailCallReturnValue) { + fun = result.callee + node = result.node + args = result.args + } else if (!(result instanceof ReturnValue)) { + // No Return Value, set it as undefined + result = new ReturnValue(undefined) + } + } else if (fun instanceof ArrowClosure) { + checkNumberOfArguments(context, fun, args, node!) + const frame = createFrame(fun, args, node) + frame.thisContext = thisContext + if (result instanceof TailCallReturnValue) { + replaceFrame(context, frame) + } else { + pushFrame(context, frame) + total++ + } + result = new ReturnValue(yield* evaluate(fun.node.body, context)) + } else if (typeof fun === 'function') { + try { + const as = args.map(a => toJS(a, context)) + result = fun.apply(thisContext, as) + break + } catch (e) { + // Recover from exception + const globalFrame = + context.runtime.frames[context.runtime.frames.length - 1] + context.runtime.frames = [globalFrame] + const loc = node ? node.loc! : constants.UNKNOWN_LOCATION + handleError(context, new errors.ExceptionError(e, loc)) + result = undefined + } + } else { + handleError(context, new errors.CallingNonFunctionValue(fun, node)) + result = undefined + break + } + } + // Unwraps return value and release stack frame + if (result instanceof ReturnValue) { + result = result.value + } + for (let i = 1; i <= total; i++) { + popFrame(context) + } + return result +} diff --git a/src/slang/parser.ts b/src/slang/parser.ts new file mode 100644 index 0000000000..f7e81d1cab --- /dev/null +++ b/src/slang/parser.ts @@ -0,0 +1,205 @@ +import * as es from 'estree' +import { parse as acornParse, Options as AcornOptions, Position } from 'acorn' +import { stripIndent } from 'common-tags' +import { simple } from 'acorn/dist/walk' +import { SourceError, ErrorType, ErrorSeverity, Context } from './types' +import syntaxTypes from './syntaxTypes' +import rules from './rules' + +export type ParserOptions = { + week: number +} + +export class DisallowedConstructError implements SourceError { + type = ErrorType.SYNTAX + severity = ErrorSeverity.ERROR + nodeType: string + + constructor(public node: es.Node) { + this.nodeType = this.splitNodeType() + } + + get location() { + return this.node.loc! + } + + explain() { + return `${this.nodeType} is not allowed` + } + + elaborate() { + return stripIndent` + You are trying to use ${this.nodeType}, which is not yet allowed (yet). + ` + } + + private splitNodeType() { + const nodeType = this.node.type + const tokens: string[] = [] + let soFar = '' + for (let i = 0; i < nodeType.length; i++) { + const isUppercase = nodeType[i] === nodeType[i].toUpperCase() + if (isUppercase && i > 0) { + tokens.push(soFar) + soFar = '' + } else { + soFar += nodeType[i] + } + } + return tokens.join(' ') + } +} + +export class FatalSyntaxError implements SourceError { + type = ErrorType.SYNTAX + severity = ErrorSeverity.ERROR + constructor(public location: es.SourceLocation, public message: string) {} + + explain() { + return this.message + } + + elaborate() { + return 'There is a syntax error in your program' + } +} + +export class MissingSemicolonError implements SourceError { + type = ErrorType.SYNTAX + severity = ErrorSeverity.ERROR + constructor(public location: es.SourceLocation) {} + + explain() { + return 'Missing semicolon at the end of statement' + } + + elaborate() { + return 'Every statement must be terminated by a semicolon.' + } +} + +export class TrailingCommaError implements SourceError { + type: ErrorType.SYNTAX + severity: ErrorSeverity.WARNING + constructor(public location: es.SourceLocation) {} + + explain() { + return 'Trailing comma' + } + + elaborate() { + return 'Please remove the trailing comma' + } +} + +export const freshId = (() => { + let id = 0 + return () => { + id++ + return 'node_' + id + } +})() + +function compose( + w1: (node: T, state: S) => void, + w2: (node: T, state: S) => void +) { + return (node: T, state: S) => { + w1(node, state) + w2(node, state) + } +} + +const walkers: { + [name: string]: (node: es.Node, context: Context) => void +} = {} + +for (const type of Object.keys(syntaxTypes)) { + walkers[type] = (node: es.Node, context: Context) => { + const id = freshId() + Object.defineProperty(node, '__id', { + enumerable: true, + configurable: false, + writable: false, + value: id + }) + context.cfg.nodes[id] = { + id, + node, + scope: undefined, + usages: [] + } + context.cfg.edges[id] = [] + if (syntaxTypes[node.type] > context.week) { + context.errors.push(new DisallowedConstructError(node)) + } + } +} + +const createAcornParserOptions = (context: Context): AcornOptions => ({ + sourceType: 'script', + ecmaVersion: 6, + locations: true, + // tslint:disable-next-line:no-any + onInsertedSemicolon(end: any, loc: any) { + context.errors.push( + new MissingSemicolonError({ + end: { line: loc.line, column: loc.column + 1 }, + start: loc + }) + ) + }, + // tslint:disable-next-line:no-any + onTrailingComma(end: any, loc: Position) { + context.errors.push( + new TrailingCommaError({ + end: { line: loc.line, column: loc.column + 1 }, + start: loc + }) + ) + } +}) + +rules.forEach(rule => { + const keys = Object.keys(rule.checkers) + keys.forEach(key => { + walkers[key] = compose(walkers[key], (node, context) => { + if ( + typeof rule.disableOn !== 'undefined' && + context.week >= rule.disableOn + ) { + return + } + const checker = rule.checkers[key] + const errors = checker(node) + errors.forEach(e => context.errors.push(e)) + }) + }) +}) + +export const parse = (source: string, context: Context) => { + let program: es.Program | undefined = undefined + try { + program = acornParse(source, createAcornParserOptions(context)) + simple(program, walkers, undefined, context) + } catch (error) { + if (error instanceof SyntaxError) { + // tslint:disable-next-line:no-any + const loc = (error as any).loc + const location = { + start: { line: loc.line, column: loc.column }, + end: { line: loc.line, column: loc.column + 1 } + } + context.errors.push(new FatalSyntaxError(location, error.toString())) + } else { + throw error + } + } + const hasErrors = context.errors.find(m => m.severity === ErrorSeverity.ERROR) + if (program && !hasErrors) { + // context.cfg.scopes[0].node = program + return program + } else { + return undefined + } +} diff --git a/src/slang/rules/bracesAroundIfElse.ts b/src/slang/rules/bracesAroundIfElse.ts new file mode 100644 index 0000000000..e8649a2bfb --- /dev/null +++ b/src/slang/rules/bracesAroundIfElse.ts @@ -0,0 +1,95 @@ +import * as es from 'estree' +import { generate } from 'astring' +import { stripIndent } from 'common-tags' + +import { SourceError, Rule, ErrorSeverity, ErrorType } from '../types' + +export class BracesAroundIfElseError implements SourceError { + type = ErrorType.SYNTAX + severity = ErrorSeverity.ERROR + + constructor( + public node: es.IfStatement, + private branch: 'consequent' | 'alternate' + ) {} + + get location() { + return this.node.loc! + } + + explain() { + if (this.branch === 'consequent') { + return 'Missing curly braces around "if" block' + } else { + return 'Missing curly braces around "else" block' + } + } + + elaborate() { + let ifOrElse + let header + let body + if (this.branch === 'consequent') { + ifOrElse = 'if' + header = `if (${generate(this.node.test)})` + body = this.node.consequent + } else { + ifOrElse = header = 'else' + body = this.node.alternate + } + + return stripIndent` + ${ifOrElse} block need to be enclosed with a pair of curly braces. + + ${header} { + ${generate(body)} + } + + An exception is when you have an "if" followed by "else if", in this case + "else if" block does not need to be surrounded by curly braces. + + if (someCondition) { + // ... + } else /* notice missing { here */ if (someCondition) { + // ... + } else { + // ... + } + + Rationale: Readability in dense packed code. + + In the snippet below, for instance, with poor indentation it is easy to + mistaken hello() and world() to belong to the same branch of logic. + + if (someCondition) { + 2; + } else + hello(); + world(); + + ` + } +} + +const bracesAroundIfElse: Rule = { + name: 'braces-around-if-else', + + checkers: { + IfStatement(node: es.IfStatement) { + const errors: SourceError[] = [] + if (node.consequent && node.consequent.type !== 'BlockStatement') { + errors.push(new BracesAroundIfElseError(node, 'consequent')) + } + if (node.alternate) { + const notBlock = node.alternate.type !== 'BlockStatement' + const notIf = node.alternate.type !== 'IfStatement' + if (notBlock && notIf) { + errors.push(new BracesAroundIfElseError(node, 'alternate')) + } + } + return errors + } + } +} + +export default bracesAroundIfElse diff --git a/src/slang/rules/bracesAroundWhile.ts b/src/slang/rules/bracesAroundWhile.ts new file mode 100644 index 0000000000..97507c7764 --- /dev/null +++ b/src/slang/rules/bracesAroundWhile.ts @@ -0,0 +1,38 @@ +import * as es from 'estree' + +import { SourceError, Rule, ErrorSeverity, ErrorType } from '../types' + +export class BracesAroundWhileError implements SourceError { + type = ErrorType.SYNTAX + severity = ErrorSeverity.ERROR + + constructor(public node: es.WhileStatement) {} + + get location() { + return this.node.loc! + } + + explain() { + return 'Missing curly braces around "while" block' + } + + elaborate() { + return 'TODO' + } +} + +const bracesAroundWhile: Rule = { + name: 'braces-around-while', + + checkers: { + WhileStatement(node: es.WhileStatement) { + if (node.body.type !== 'BlockStatement') { + return [new BracesAroundWhileError(node)] + } else { + return [] + } + } + } +} + +export default bracesAroundWhile diff --git a/src/slang/rules/index.ts b/src/slang/rules/index.ts new file mode 100644 index 0000000000..83488a8d0d --- /dev/null +++ b/src/slang/rules/index.ts @@ -0,0 +1,31 @@ +import * as es from 'estree' + +import { Rule } from '../types' + +import bracesAroundIfElse from './bracesAroundIfElse' +import bracesAroundWhile from './bracesAroundWhile' +import noIfWithoutElse from './noIfWithoutElse' +import singleVariableDeclaration from './singleVariableDeclaration' +import strictEquality from './strictEquality' +import noImplicitDeclareUndefined from './noImplicitDeclareUndefined' +import noImplicitReturnUndefined from './noImplicitReturnUndefined' +import noNonEmptyList from './noNonEmptyList' +import noBlockArrowFunction from './noBlockArrowFunction' +import noDeclareReserved from './noDeclareReserved' +import noDeclareMutable from './noDeclareMutable' + +const rules: Array> = [ + bracesAroundIfElse, + bracesAroundWhile, + singleVariableDeclaration, + strictEquality, + noIfWithoutElse, + noImplicitDeclareUndefined, + noImplicitReturnUndefined, + noNonEmptyList, + noBlockArrowFunction, + noDeclareReserved, + noDeclareMutable +] + +export default rules diff --git a/src/slang/rules/noBlockArrowFunction.ts b/src/slang/rules/noBlockArrowFunction.ts new file mode 100644 index 0000000000..984b9e4e41 --- /dev/null +++ b/src/slang/rules/noBlockArrowFunction.ts @@ -0,0 +1,37 @@ +import * as es from 'estree' + +import { SourceError, Rule, ErrorSeverity, ErrorType } from '../types' + +export class NoBlockArrowFunction implements SourceError { + type = ErrorType.SYNTAX + severity = ErrorSeverity.ERROR + constructor(public node: es.ArrowFunctionExpression) {} + + get location() { + return this.node.loc! + } + + explain() { + return 'Function definition expressions may only end with an expression.' + } + + elaborate() { + return this.explain() + } +} + +const noBlockArrowFunction: Rule = { + name: 'no-block-arrow-function', + + checkers: { + ArrowFunctionExpression(node: es.ArrowFunctionExpression) { + if (node.body.type === 'BlockStatement') { + return [new NoBlockArrowFunction(node)] + } else { + return [] + } + } + } +} + +export default noBlockArrowFunction diff --git a/src/slang/rules/noDeclareMutable.ts b/src/slang/rules/noDeclareMutable.ts new file mode 100644 index 0000000000..5ff67fb035 --- /dev/null +++ b/src/slang/rules/noDeclareMutable.ts @@ -0,0 +1,44 @@ +import * as es from 'estree' + +import { SourceError, Rule, ErrorSeverity, ErrorType } from '../types' + +const mutableDeclarators = ['let', 'var'] + +export class noDeclareMutableError implements SourceError { + type = ErrorType.SYNTAX + severity = ErrorSeverity.ERROR + + constructor(public node: es.VariableDeclaration) {} + + get location() { + return this.node.loc! + } + + explain() { + return ( + 'Mutable variable declaration using keyword ' + + `'${this.node.kind}'` + + ' is not allowed.' + ) + } + + elaborate() { + return this.explain() + } +} + +const noDeclareMutable: Rule = { + name: 'no-declare-mutable', + + checkers: { + VariableDeclaration(node: es.VariableDeclaration) { + if (mutableDeclarators.includes(node.kind)) { + return [new noDeclareMutableError(node)] + } else { + return [] + } + } + } +} + +export default noDeclareMutable diff --git a/src/slang/rules/noDeclareReserved.ts b/src/slang/rules/noDeclareReserved.ts new file mode 100644 index 0000000000..56736966a6 --- /dev/null +++ b/src/slang/rules/noDeclareReserved.ts @@ -0,0 +1,89 @@ +import * as es from 'estree' + +import { SourceError, Rule, ErrorSeverity, ErrorType } from '../types' + +const reservedNames = [ + 'break', + 'case', + 'catch', + 'continue', + 'debugger', + 'default', + 'delete', + 'do', + 'else', + 'finally', + 'for', + 'function', + 'if', + 'in', + 'instanceof', + 'new', + 'return', + 'switch', + 'this', + 'throw', + 'try', + 'typeof', + 'var', + 'void', + 'while', + 'with', + 'class', + 'const', + 'enum', + 'export', + 'extends', + 'import', + 'super', + 'implements', + 'interface', + 'let', + 'package', + 'private', + 'protected', + 'public', + 'static', + 'yield', + 'null', + 'true', + 'false' +] + +export class noDeclareReservedError implements SourceError { + type = ErrorType.SYNTAX + severity = ErrorSeverity.ERROR + + constructor(public node: es.VariableDeclaration) {} + + get location() { + return this.node.loc! + } + + explain() { + return ( + `Reserved word '${this.node.declarations[0].id.name}'` + + ' is not allowed as a name' + ) + } + + elaborate() { + return this.explain() + } +} + +const noDeclareReserved: Rule = { + name: 'no-declare-reserved', + + checkers: { + VariableDeclaration(node: es.VariableDeclaration) { + if (reservedNames.includes(node.declarations[0].id.name)) { + return [new noDeclareReservedError(node)] + } else { + return [] + } + } + } +} + +export default noDeclareReserved diff --git a/src/slang/rules/noIfWithoutElse.ts b/src/slang/rules/noIfWithoutElse.ts new file mode 100644 index 0000000000..c4d170d42c --- /dev/null +++ b/src/slang/rules/noIfWithoutElse.ts @@ -0,0 +1,45 @@ +import { stripIndent } from 'common-tags' +import * as es from 'estree' +import { generate } from 'astring' + +import { SourceError, Rule, ErrorSeverity, ErrorType } from '../types' + +export class NoIfWithoutElseError implements SourceError { + type = ErrorType.SYNTAX + severity = ErrorSeverity.ERROR + constructor(public node: es.IfStatement) {} + + get location() { + return this.node.loc! + } + + explain() { + return 'Missing "else" in "if-else" statement' + } + + elaborate() { + return stripIndent` + This "if" block requires corresponding "else" block which will be + evaluated when ${generate(this.node.test)} expression evaluates to false. + + Later in the course we will lift this restriction and allow "if" without + else. + ` + } +} + +const noIfWithoutElse: Rule = { + name: 'no-if-without-else', + + checkers: { + IfStatement(node: es.IfStatement) { + if (!node.alternate) { + return [new NoIfWithoutElseError(node)] + } else { + return [] + } + } + } +} + +export default noIfWithoutElse diff --git a/src/slang/rules/noImplicitDeclareUndefined.ts b/src/slang/rules/noImplicitDeclareUndefined.ts new file mode 100644 index 0000000000..db04668c8f --- /dev/null +++ b/src/slang/rules/noImplicitDeclareUndefined.ts @@ -0,0 +1,49 @@ +import * as es from 'estree' +import { stripIndent } from 'common-tags' + +import { SourceError, Rule, ErrorSeverity, ErrorType } from '../types' + +export class NoImplicitDeclareUndefinedError implements SourceError { + type = ErrorType.SYNTAX + severity = ErrorSeverity.ERROR + constructor(public node: es.Identifier) {} + + get location() { + return this.node.loc! + } + + explain() { + return 'Missing value in variable declaration' + } + + elaborate() { + return stripIndent` + A variable declaration assigns a value to a name. + For instance, to assign 20 to ${this.node.name}, you can write: + + var ${this.node.name} = 20; + + ${this.node.name} + ${this.node.name}; // 40 + ` + } +} + +const noImplicitDeclareUndefined: Rule = { + name: 'no-implicit-declare-undefined', + + checkers: { + VariableDeclaration(node: es.VariableDeclaration) { + const errors: SourceError[] = [] + for (const decl of node.declarations) { + if (!decl.init) { + errors.push( + new NoImplicitDeclareUndefinedError(decl.id as es.Identifier) + ) + } + } + return errors + } + } +} + +export default noImplicitDeclareUndefined diff --git a/src/slang/rules/noImplicitReturnUndefined.ts b/src/slang/rules/noImplicitReturnUndefined.ts new file mode 100644 index 0000000000..480b0ad5fc --- /dev/null +++ b/src/slang/rules/noImplicitReturnUndefined.ts @@ -0,0 +1,44 @@ +import * as es from 'estree' +import { stripIndent } from 'common-tags' + +import { SourceError, Rule, ErrorSeverity, ErrorType } from '../types' + +export class NoImplicitReturnUndefinedError implements SourceError { + type = ErrorType.SYNTAX + severity = ErrorSeverity.ERROR + + constructor(public node: es.ReturnStatement) {} + + get location() { + return this.node.loc! + } + + explain() { + return 'Missing value in return statement' + } + + elaborate() { + return stripIndent` + This return statement is missing a value. + For instance, to return the value 42, you can write + + return 42; + ` + } +} + +const noImplicitReturnUndefined: Rule = { + name: 'no-implicit-return-undefined', + + checkers: { + ReturnStatement(node: es.ReturnStatement) { + if (!node.argument) { + return [new NoImplicitReturnUndefinedError(node)] + } else { + return [] + } + } + } +} + +export default noImplicitReturnUndefined diff --git a/src/slang/rules/noNonEmptyList.ts b/src/slang/rules/noNonEmptyList.ts new file mode 100644 index 0000000000..43cf220fe3 --- /dev/null +++ b/src/slang/rules/noNonEmptyList.ts @@ -0,0 +1,40 @@ +import * as es from 'estree' + +import { SourceError, Rule, ErrorSeverity, ErrorType } from '../types' + +export class NoNonEmptyListError implements SourceError { + type = ErrorType.SYNTAX + severity = ErrorSeverity.ERROR + + constructor(public node: es.ArrayExpression) {} + + get location() { + return this.node.loc! + } + + explain() { + return 'Only empty list notation ([]) is allowed' + } + + elaborate() { + return 'TODO' + } +} + +const noNonEmptyList: Rule = { + name: 'no-non-empty-list', + + disableOn: 9, + + checkers: { + ArrayExpression(node: es.ArrayExpression) { + if (node.elements.length > 0) { + return [new NoNonEmptyListError(node)] + } else { + return [] + } + } + } +} + +export default noNonEmptyList diff --git a/src/slang/rules/singleVariableDeclaration.ts b/src/slang/rules/singleVariableDeclaration.ts new file mode 100644 index 0000000000..e4fe619e89 --- /dev/null +++ b/src/slang/rules/singleVariableDeclaration.ts @@ -0,0 +1,52 @@ +import * as es from 'estree' +import { generate } from 'astring' + +import { SourceError, Rule, ErrorSeverity, ErrorType } from '../types' + +export class MultipleDeclarationsError implements SourceError { + type = ErrorType.SYNTAX + severity = ErrorSeverity.ERROR + private fixs: es.VariableDeclaration[] + + constructor(public node: es.VariableDeclaration) { + this.fixs = node.declarations.map(declaration => ({ + type: 'VariableDeclaration' as 'VariableDeclaration', + kind: 'var' as 'var', + loc: declaration.loc, + declarations: [declaration] + })) + } + + get location() { + return this.node.loc! + } + + explain() { + return 'Multiple declaration in a single statement' + } + + elaborate() { + const fixs = this.fixs.map(n => '\t' + generate(n)).join('\n') + return ( + 'Split the variable declaration into multiple lines as follows\n\n' + + fixs + + '\n' + ) + } +} + +const singleVariableDeclaration: Rule = { + name: 'single-variable-declaration', + + checkers: { + VariableDeclaration(node: es.VariableDeclaration) { + if (node.declarations.length > 1) { + return [new MultipleDeclarationsError(node)] + } else { + return [] + } + } + } +} + +export default singleVariableDeclaration diff --git a/src/slang/rules/strictEquality.ts b/src/slang/rules/strictEquality.ts new file mode 100644 index 0000000000..58bd7f7a7e --- /dev/null +++ b/src/slang/rules/strictEquality.ts @@ -0,0 +1,42 @@ +import * as es from 'estree' + +import { SourceError, Rule, ErrorSeverity, ErrorType } from '../types' + +export class StrictEqualityError implements SourceError { + type = ErrorType.SYNTAX + severity = ErrorSeverity.ERROR + + constructor(public node: es.BinaryExpression) {} + + get location() { + return this.node.loc! + } + + explain() { + if (this.node.operator === '==') { + return 'Use === instead of ==' + } else { + return 'Use !== instead of !=' + } + } + + elaborate() { + return '== and != is not a valid operator' + } +} + +const strictEquality: Rule = { + name: 'strict-equality', + + checkers: { + BinaryExpression(node: es.BinaryExpression) { + if (node.operator === '==' || node.operator === '!=') { + return [new StrictEqualityError(node)] + } else { + return [] + } + } + } +} + +export default strictEquality diff --git a/src/slang/schedulers.ts b/src/slang/schedulers.ts new file mode 100644 index 0000000000..bfbcdee972 --- /dev/null +++ b/src/slang/schedulers.ts @@ -0,0 +1,64 @@ +import * as es from 'estree' +import { Scheduler, Value, Context, Result } from './types' +import { MaximumStackLimitExceeded } from './interpreter-errors' + +export class AsyncScheduler implements Scheduler { + run(it: IterableIterator, context: Context): Promise { + return new Promise((resolve, reject) => { + context.runtime.isRunning = true + let itValue = it.next() + try { + while (!itValue.done) { + itValue = it.next() + } + } catch (e) { + resolve({ status: 'error' }) + } finally { + context.runtime.isRunning = false + } + resolve({ + status: 'finished', + value: itValue.value + }) + }) + } +} + +export class PreemptiveScheduler implements Scheduler { + constructor(public steps: number) {} + + run(it: IterableIterator, context: Context): Promise { + return new Promise((resolve, reject) => { + context.runtime.isRunning = true + let itValue = it.next() + let interval: number + interval = setInterval(() => { + let step = 0 + try { + while (!itValue.done && step < this.steps) { + itValue = it.next() + step++ + } + } catch (e) { + if (/Maximum call stack/.test(e.toString())) { + const stacks: es.CallExpression[] = [] + for (let i = 1; i <= 3; i++) { + stacks.push(context.runtime.frames[i - 1].callExpression!) + } + context.errors.push( + new MaximumStackLimitExceeded(context.runtime.nodes[0], stacks) + ) + } + context.runtime.isRunning = false + clearInterval(interval) + resolve({ status: 'error' }) + } + if (itValue.done) { + context.runtime.isRunning = false + clearInterval(interval) + resolve({ status: 'finished', value: itValue.value }) + } + }) + }) + } +} diff --git a/src/slang/stdlib/list.ts b/src/slang/stdlib/list.ts new file mode 100644 index 0000000000..16223ca0a8 --- /dev/null +++ b/src/slang/stdlib/list.ts @@ -0,0 +1,425 @@ +import { toString } from '../interop' +import { Value } from '../types' + +declare global { + interface Function { + __SOURCE__?: string + } +} + +// tslint:disable +// list.js: Supporting lists in the Scheme style, using pairs made +// up of two-element JavaScript array (vector) +// Author: Martin Henz +// Translated to TypeScript by Evan Sebastian + +type List = Value[] + +// array test works differently for Rhino and +// the Firefox environment (especially Web Console) +function array_test(x: Value) { + if (Array.isArray === undefined) { + return x instanceof Array + } else { + return Array.isArray(x) + } +} +array_test.__SOURCE__ = 'array_test(x)' + +// pair constructs a pair using a two-element array +// LOW-LEVEL FUNCTION, NOT SOURCE +export function pair(x: Value, xs: Value) { + return [x, xs] +} +pair.__SOURCE__ = 'pair(x, xs)' + +// is_pair returns true iff arg is a two-element array +// LOW-LEVEL FUNCTION, NOT SOURCE +export function is_pair(x: Value) { + return array_test(x) && x.length === 2 +} +is_pair.__SOURCE__ = 'is_pair(x)' + +// head returns the first component of the given pair, +// throws an exception if the argument is not a pair +// LOW-LEVEL FUNCTION, NOT SOURCE +export function head(xs: List) { + if (is_pair(xs)) { + return xs[0] + } else { + throw new Error( + 'head(xs) expects a pair as ' + + 'argument xs, but encountered ' + + toString(xs) + ) + } +} +head.__SOURCE__ = 'head(xs)' + +// tail returns the second component of the given pair +// throws an exception if the argument is not a pair +// LOW-LEVEL FUNCTION, NOT SOURCE +export function tail(xs: List) { + if (is_pair(xs)) { + return xs[1] + } else { + throw new Error( + 'tail(xs) expects a pair as ' + + 'argument xs, but encountered ' + + toString(xs) + ) + } +} +tail.__SOURCE__ = 'tail(xs)' + +// is_empty_list returns true if arg is [] +// LOW-LEVEL FUNCTION, NOT SOURCE +export function is_empty_list(xs: List) { + if (array_test(xs)) { + if (xs.length === 0) { + return true + } else if (xs.length === 2) { + return false + } else { + return false + } + } else { + return false + } +} +is_empty_list.__SOURCE__ = 'is_empty_list(xs)' + +// is_list recurses down the list and checks that it ends with the empty list [] +// does not throw Value exceptions +// LOW-LEVEL FUNCTION, NOT SOURCE +export function is_list(xs: List) { + for (; ; xs = tail(xs)) { + if (is_empty_list(xs)) { + return true + } else if (!is_pair(xs)) { + return false + } + } +} +is_list.__SOURCE__ = 'is_list(xs)' + +// list makes a list out of its arguments +// LOW-LEVEL FUNCTION, NOT SOURCE +export function list() { + var the_list = [] + for (var i = arguments.length - 1; i >= 0; i--) { + the_list = pair(arguments[i], the_list) + } + return the_list +} +list.__SOURCE__ = 'list(x, y, ...)' + +// list_to_vector returns vector that contains the elements of the argument list +// in the given order. +// list_to_vector throws an exception if the argument is not a list +// LOW-LEVEL FUNCTION, NOT SOURCE +export function list_to_vector(lst: List) { + var vector = [] + while (!is_empty_list(lst)) { + vector.push(head(lst)) + lst = tail(lst) + } + return vector +} +list_to_vector.__SOURCE__ = 'list_to_vector(xs)' + +// vector_to_list returns a list that contains the elements of the argument vector +// in the given order. +// vector_to_list throws an exception if the argument is not a vector +// LOW-LEVEL FUNCTION, NOT SOURCE +export function vector_to_list(vector: Value[]) { + if (vector.length === 0) { + return [] + } + + var result = [] + for (var i = vector.length - 1; i >= 0; i = i - 1) { + result = pair(vector[i], result) + } + return result +} +vector_to_list.__SOURCE__ = 'vector_to_list(vs)' + +// returns the length of a given argument list +// throws an exception if the argument is not a list +export function length(xs: List) { + for (var i = 0; !is_empty_list(xs); ++i) { + xs = tail(xs) + } + return i +} +length.__SOURCE__ = 'length(xs)' + +// map applies first arg f to the elements of the second argument, +// assumed to be a list. +// f is applied element-by-element: +// map(f,[1,[2,[]]]) results in [f(1),[f(2),[]]] +// map throws an exception if the second argument is not a list, +// and if the second argument is a non-empty list and the first +// argument is not a function. +export function map(f: Function, xs: List): List { + return is_empty_list(xs) ? [] : pair(f(head(xs)), map(f, tail(xs))) +} +map.__SOURCE__ = 'map(f, xs)' + +// build_list takes a non-negative integer n as first argument, +// and a function fun as second argument. +// build_list returns a list of n elements, that results from +// applying fun to the numbers from 0 to n-1. +export function build_list(n: number, fun: Function) { + function build(i: number, fun: Function, already_built: List): List { + if (i < 0) { + return already_built + } else { + return build(i - 1, fun, pair(fun(i), already_built)) + } + } + return build(n - 1, fun, []) +} +build_list.__SOURCE__ = 'build_list(n, fun)' + +// for_each applies first arg fun to the elements of the list passed as +// second argument. fun is applied element-by-element: +// for_each(fun,[1,[2,[]]]) results in the calls fun(1) and fun(2). +// for_each returns true. +// for_each throws an exception if the second argument is not a list, +// and if the second argument is a non-empty list and the +// first argument is not a function. +export function for_each(fun: Function, xs: List) { + if (!is_list(xs)) { + throw new Error( + 'for_each expects a list as argument xs, but ' + 'encountered ' + xs + ) + } + for (; !is_empty_list(xs); xs = tail(xs)) { + fun(head(xs)) + } + return true +} +for_each.__SOURCE__ = 'for_each(fun, xs)' + +// list_to_string returns a string that represents the argument list. +// It applies itself recursively on the elements of the given list. +// When it encounters a non-list, it applies toString to it. +export function list_to_string(l: List): string { + return toString(l) +} +list_to_string.__SOURCE__ = 'list_to_string(xs)' + +// reverse reverses the argument list +// reverse throws an exception if the argument is not a list. +export function reverse(xs: List) { + if (!is_list(xs)) { + throw new Error( + 'reverse(xs) expects a list as argument xs, but ' + 'encountered ' + xs + ) + } + var result = [] + for (; !is_empty_list(xs); xs = tail(xs)) { + result = pair(head(xs), result) + } + return result +} +reverse.__SOURCE__ = 'reverse(xs)' + +// append first argument list and second argument list. +// In the result, the [] at the end of the first argument list +// is replaced by the second argument list +// append throws an exception if the first argument is not a list +export function append(xs: List, ys: List): List { + if (is_empty_list(xs)) { + return ys + } else { + return pair(head(xs), append(tail(xs), ys)) + } +} +append.__SOURCE__ = 'append(xs, ys)' + +// member looks for a given first-argument element in a given +// second argument list. It returns the first postfix sublist +// that starts with the given element. It returns [] if the +// element does not occur in the list +export function member(v: Value, xs: List) { + for (; !is_empty_list(xs); xs = tail(xs)) { + if (head(xs) === v) { + return xs + } + } + return [] +} +member.__SOURCE__ = 'member(x, xs)' + +// removes the first occurrence of a given first-argument element +// in a given second-argument list. Returns the original list +// if there is no occurrence. +export function remove(v: Value, xs: List): List { + if (is_empty_list(xs)) { + return [] + } else { + if (v === head(xs)) { + return tail(xs) + } else { + return pair(head(xs), remove(v, tail(xs))) + } + } +} +remove.__SOURCE__ = 'remove(x, xs)' + +// Similar to remove. But removes all instances of v instead of just the first +export function remove_all(v: Value, xs: List): List { + if (is_empty_list(xs)) { + return [] + } else { + if (v === head(xs)) { + return remove_all(v, tail(xs)) + } else { + return pair(head(xs), remove_all(v, tail(xs))) + } + } +} +remove_all.__SOURCE__ = 'remove_all(x, xs)' +// for backwards-compatibility +export const removeAll = remove_all + +// equal computes the structural equality +// over its arguments +export function equal(item1: Value, item2: Value): boolean { + if (is_pair(item1) && is_pair(item2)) { + return equal(head(item1), head(item2)) && equal(tail(item1), tail(item2)) + } else if ( + array_test(item1) && + item1.length === 0 && + array_test(item2) && + item2.length === 0 + ) { + return true + } else { + return item1 === item2 + } +} +equal.__SOURCE__ = 'equal(x, y)' + +// assoc treats the second argument as an association, +// a list of (index,value) pairs. +// assoc returns the first (index,value) pair whose +// index equal (using structural equality) to the given +// first argument v. Returns false if there is no such +// pair +export function assoc(v: Value, xs: List): boolean { + if (is_empty_list(xs)) { + return false + } else if (equal(v, head(head(xs)))) { + return head(xs) + } else { + return assoc(v, tail(xs)) + } +} +assoc.__SOURCE__ = 'assoc(v, xs)' + +// filter returns the sublist of elements of given list xs +// for which the given predicate function returns true. +export function filter(pred: Function, xs: List): List { + if (is_empty_list(xs)) { + return xs + } else { + if (pred(head(xs))) { + return pair(head(xs), filter(pred, tail(xs))) + } else { + return filter(pred, tail(xs)) + } + } +} +filter.__SOURCE__ = 'filter(pred, xs)' + +// enumerates numbers starting from start, +// using a step size of 1, until the number +// exceeds end. +export function enum_list(start: number, end: number): List { + if (start > end) { + return [] + } else { + return pair(start, enum_list(start + 1, end)) + } +} +enum_list.__SOURCE__ = 'enum_list(start, end)' + +// Returns the item in list lst at index n (the first item is at position 0) +export function list_ref(xs: List, n: number) { + if (n < 0) { + throw new Error( + 'list_ref(xs, n) expects a positive integer as ' + + 'argument n, but encountered ' + + n + ) + } + + for (; n > 0; --n) { + xs = tail(xs) + } + return head(xs) +} +list_ref.__SOURCE__ = 'list_ref(xs, n)' + +// accumulate applies given operation op to elements of a list +// in a right-to-left order, first apply op to the last element +// and an initial element, resulting in r1, then to the +// second-last element and r1, resulting in r2, etc, and finally +// to the first element and r_n-1, where n is the length of the +// list. +// accumulate(op,zero,list(1,2,3)) results in +// op(1, op(2, op(3, zero))) + +export function accumulate( + op: (value: Value, acc: T) => T, + initial: T, + sequence: List +): T { + if (is_empty_list(sequence)) { + return initial + } else { + return op(head(sequence), accumulate(op, initial, tail(sequence))) + } +} +accumulate.__SOURCE__ = 'accumulate(op, initial, xs)' + +// set_head(xs,x) changes the head of given pair xs to be x, +// throws an exception if the argument is not a pair +// LOW-LEVEL FUNCTION, NOT SOURCE + +export function set_head(xs: List, x: Value) { + if (is_pair(xs)) { + xs[0] = x + return undefined + } else { + throw new Error( + 'set_head(xs,x) expects a pair as ' + + 'argument xs, but encountered ' + + toString(xs) + ) + } +} +set_head.__SOURCE__ = 'set_head(xs, x)' + +// set_tail(xs,x) changes the tail of given pair xs to be x, +// throws an exception if the argument is not a pair +// LOW-LEVEL FUNCTION, NOT SOURCE + +export function set_tail(xs: List, x: Value) { + if (is_pair(xs)) { + xs[1] = x + return undefined + } else { + throw new Error( + 'set_tail(xs,x) expects a pair as ' + + 'argument xs, but encountered ' + + toString(xs) + ) + } +} +set_tail.__SOURCE__ = 'set_tail(xs, x)' +// tslint:enable diff --git a/src/slang/stdlib/misc.ts b/src/slang/stdlib/misc.ts new file mode 100644 index 0000000000..5358955604 --- /dev/null +++ b/src/slang/stdlib/misc.ts @@ -0,0 +1,66 @@ +import { Value } from '../types' +import { toString } from '../interop' + +export function display(value: Value) { + const output = toString(value) + if (typeof window.__REDUX_STORE__ !== 'undefined') { + window.__REDUX_STORE__.dispatch({ + type: 'CREATE_INTERPRETER_OUTPUT', + payload: output + }) + } else { + // tslint:disable-next-line:no-console + console.log(output) + } +} +window.display = display +display.__SOURCE__ = 'display(a)' + +export function error_message(value: Value) { + const output = toString(value) + throw new Error(output) +} +error_message.__SOURCE__ = 'error(a)' + +// tslint:disable-next-line:no-any +export function timed(this: any, f: Function) { + var self = this + var timerType = Date + + return function() { + var start = timerType.now() + var result = f.apply(self, arguments) + var diff = timerType.now() - start + display('Duration: ' + Math.round(diff) + 'ms') + return result + } +} +timed.__SOURCE__ = 'timed(f)' + +export function is_number(v: Value) { + return typeof v === 'number' +} +is_number.__SOURCE__ = 'is_number(v)' + +export function array_length(xs: Value[]) { + return xs.length +} +array_length.__SOURCE__ = 'array_length(xs)' + +export function parse_int(inputString: string, radix: number) { + const parsed = parseInt(inputString, radix) + if (inputString && radix && parsed) { + // the two arguments are provided, and parsed is not NaN + return parsed + } else { + throw new Error( + 'parseInt expects two arguments a string s, and a positive integer i' + ) + } +} +parse_int.__SOURCE__ = 'parse_int(s, i)' + +export function runtime() { + return new Date().getTime() +} +runtime.__SOURCE__ = 'runtime()' diff --git a/src/slang/stdlib/object.ts b/src/slang/stdlib/object.ts new file mode 100644 index 0000000000..5d7e09900b --- /dev/null +++ b/src/slang/stdlib/object.ts @@ -0,0 +1,5 @@ +import { Value } from '../types' + +export function is_instance_of(a: Value, b: Value) { + return a instanceof b +} diff --git a/src/slang/syntaxTypes.ts b/src/slang/syntaxTypes.ts new file mode 100644 index 0000000000..0efe756b42 --- /dev/null +++ b/src/slang/syntaxTypes.ts @@ -0,0 +1,53 @@ +const syntaxTypes: { [nodeName: string]: number } = { + // Week 3 + Program: 3, + ExpressionStatement: 3, + IfStatement: 3, + FunctionDeclaration: 3, + VariableDeclaration: 3, + ReturnStatement: 3, + CallExpression: 3, + UnaryExpression: 3, + BinaryExpression: 3, + LogicalExpression: 3, + ConditionalExpression: 3, + FunctionExpression: 3, + ArrowFunctionExpression: 3, + Identifier: 3, + Literal: 3, + + // Week 5 + EmptyStatement: 5, + ArrayExpression: 5, + + // Week 8 + AssignmentExpression: 8, + WhileStatement: 8, + + // Week 9 + ForStatement: 9, + BreakStatement: 9, + ContinueStatement: 9, + MemberExpression: 9, + + // Week 10 + ThisExpression: 10, + ObjectExpression: 10, + Property: 10, + UpdateExpression: 10, + NewExpression: 10, + + // Disallowed Forever + SwitchStatement: Infinity, + DebuggerStatement: Infinity, + WithStatement: Infinity, + LabeledStatement: Infinity, + SwitchCase: Infinity, + ThrowStatement: Infinity, + CatchClause: Infinity, + DoWhileStatement: Infinity, + ForInStatement: Infinity, + SequenceExpression: Infinity +} + +export default syntaxTypes diff --git a/src/slang/types.ts b/src/slang/types.ts new file mode 100644 index 0000000000..cee236ee80 --- /dev/null +++ b/src/slang/types.ts @@ -0,0 +1,195 @@ +import * as es from 'estree' +import { SourceLocation } from 'acorn' + +import { closureToJS } from './interop' + +export enum ErrorType { + SYNTAX = 'Syntax', + TYPE = 'Type', + RUNTIME = 'Runtime' +} + +export enum ErrorSeverity { + WARNING = 'Warning', + ERROR = 'Error' +} + +export interface SourceError { + type: ErrorType + severity: ErrorSeverity + location: es.SourceLocation + explain(): string + elaborate(): string +} + +export interface Rule { + name: string + disableOn?: number + checkers: { + [name: string]: (node: T) => SourceError[] + } +} + +export namespace CFG { + // tslint:disable-next-line:no-shadowed-variable + export type Scope = { + name: string + parent?: Scope + entry?: Vertex + exits: Vertex[] + node?: es.Node + proof?: es.Node + type: Type + env: { + [name: string]: Sym + } + } + + export type Vertex = { + id: string + node: es.Node + scope?: Scope + usages: Sym[] + } + + export type Sym = { + name: string + defined?: boolean + definedAt?: es.SourceLocation + type: Type + proof?: es.Node + } + + export type Type = { + name: 'number' | 'string' | 'boolean' | 'function' | 'undefined' | 'any' + params?: Type[] + returnType?: Type + } + + export type EdgeLabel = 'next' | 'alternate' | 'consequent' + + export type Edge = { + type: EdgeLabel + to: Vertex + } +} + +export type Comment = { + type: 'Line' | 'Block' + value: string + start: number + end: number + loc: SourceLocation | undefined +} + +export interface TypeError extends SourceError { + expected: CFG.Type[] + got: CFG.Type + proof?: es.Node +} + +export type Context = { + /** The source version used */ + week: number + + /** All the errors gathered */ + errors: SourceError[] + + /** CFG Specific State */ + cfg: { + nodes: { [id: string]: CFG.Vertex } + edges: { [from: string]: CFG.Edge[] } + scopes: CFG.Scope[] + } + + /** Runtime Sepecific state */ + runtime: { + isRunning: boolean + frames: Frame[] + nodes: es.Node[] + } +} + +// tslint:disable:no-any +export type Environment = { [name: string]: any } +export type Value = any +// tslint:enable:no-any + +export interface Frame { + name: string + parent: Frame | null + callExpression?: es.CallExpression + environment: Environment + thisContext?: Value +} + +/** + * Models function value in the interpreter environment. + */ +export class Closure { + /** Keep track how many lambdas are created */ + private static lambdaCtr = 0 + + /** Unique ID defined for anonymous closure */ + public name: string + + /** Fake closure function */ + public fun: Function + + constructor( + public node: es.FunctionExpression, + public frame: Frame, + context: Context + ) { + this.node = node + try { + if (this.node.id) { + this.name = this.node.id.name + } + } catch (e) { + this.name = `Anonymous${++Closure.lambdaCtr}` + } + this.fun = closureToJS(this, context, this.name) + } +} + +/** + * Modified from class Closure, for construction of arrow functions. + */ +export class ArrowClosure { + /** Keep track how many lambdas are created */ + private static arrowCtr = 0 + + /** Unique ID defined for anonymous closure */ + public name: string + + /** Fake closure function */ + public fun: Function + + constructor(public node: es.Function, public frame: Frame, context: Context) { + this.name = `Anonymous${++ArrowClosure.arrowCtr}` + this.fun = closureToJS(this, context, this.name) + } +} + +type Error = { + status: 'error' +} + +type Finished = { + status: 'finished' + value: Value +} + +type Suspended = { + status: 'suspended' + it: IterableIterator + scheduler: Scheduler + context: Context +} + +export type Result = Suspended | Finished | Error + +export interface Scheduler { + run(it: IterableIterator, context: Context): Promise +} diff --git a/src/slang/typings/acorn.d.ts b/src/slang/typings/acorn.d.ts new file mode 100644 index 0000000000..cec251fd9e --- /dev/null +++ b/src/slang/typings/acorn.d.ts @@ -0,0 +1,53 @@ +declare module 'acorn/dist/walk' { + import * as es from 'estree' + + namespace AcornWalk { + export type SimpleWalker = (node: es.Node, state?: S) => void + export type SimpleWalkers = { [name: string]: SimpleWalker } + export type Walker = ( + node: T, + state: S, + callback: SimpleWalker + ) => void + export type Walkers = { [name: string]: Walker } + type NodeTest = (nodeType: string, node: es.Node) => boolean + + export const base: Walkers + + export function simple( + node: es.Node, + visitors: SimpleWalkers, + base?: SimpleWalkers, + state?: S + ): void + export function recursive( + node: es.Node, + state: S, + functions: Walkers + ): void + export function findNodeAt( + node: es.Node, + start: null | number, + end: null | number, + test: string | NodeTest, + base?: SimpleWalkers, + state?: S + ): void + export function findNodeAround( + node: es.Node, + pos: es.Position, + test: string | NodeTest, + base?: SimpleWalkers, + state?: S + ): void + export function findNodeAfter( + node: es.Node, + pos: es.Position, + test: string | NodeTest, + base?: SimpleWalkers, + state?: S + ): void + } + + export = AcornWalk +} diff --git a/src/slang/typings/astring.d.ts b/src/slang/typings/astring.d.ts new file mode 100644 index 0000000000..7f3a6fd2f2 --- /dev/null +++ b/src/slang/typings/astring.d.ts @@ -0,0 +1 @@ +declare module 'astring' diff --git a/src/slang/typings/estree.d.ts b/src/slang/typings/estree.d.ts new file mode 100644 index 0000000000..33c844f71f --- /dev/null +++ b/src/slang/typings/estree.d.ts @@ -0,0 +1,8 @@ +import estree from 'estree' + +declare module 'estree' { + interface BaseNode { + __id?: string + __call?: string + } +} diff --git a/src/slang/utils/node.ts b/src/slang/utils/node.ts new file mode 100644 index 0000000000..a2c1aec02e --- /dev/null +++ b/src/slang/utils/node.ts @@ -0,0 +1,124 @@ +/** + * Utility functions to work with the AST (Abstract Syntax Tree) + */ +import * as es from 'estree' +import { Walker, SimpleWalker } from 'acorn/dist/walk' +import { Closure, Value } from '../types' + +/** + * Check whether two nodes are equal. + * + * Two nodes are equal if their `__id` field are equal, or + * if they have `__call`, the `__call` field is checked instead. + * + * @param n1 First node + * @param n2 Second node + */ +export const isNodeEqual = (n1: es.Node, n2: es.Node) => { + if (n1.hasOwnProperty('__id') && n2.hasOwnProperty('__id')) { + const first = n1.__id === n2.__id + if (!first) { + return false + } + if (n1.hasOwnProperty('__call') && n2.hasOwnProperty('__call')) { + return n1.__call === n2.__call + } else { + return true + } + } else { + return n1 === n2 + } +} + +/** + * Non-destructively search for a node in a parent node and replace it with another node. + * + * @param node The root node to be searched + * @param before Node to be replaced + * @param after Replacement node + */ +export const replaceAST = (node: es.Node, before: es.Node, after: es.Node) => { + let found = false + + const go = (n: es.Node): {} => { + if (found) { + return n + } + + if (isNodeEqual(n, before)) { + found = true + return after + } + + if (n.type === 'CallExpression') { + return { ...n, callee: go(n.callee), arguments: n.arguments.map(go) } + } else if (n.type === 'ConditionalExpression') { + return { + ...n, + test: go(n.test), + consequent: go(n.consequent), + alternate: go(n.alternate) + } + } else if (n.type === 'UnaryExpression') { + return { ...n, argument: go(n.argument) } + } else if ( + n.type === 'BinaryExpression' || + n.type === 'LogicalExpression' + ) { + return { ...n, left: go(n.left), right: go(n.right) } + } else { + return n + } + } + + return go(node) +} + +const createLiteralNode = (value: {}): es.Node => { + if (typeof value === 'undefined') { + return { + type: 'Identifier', + name: 'undefined', + __id: freshId() + } + } else { + return { + type: 'Literal', + value, + raw: value, + __id: freshId() + } + } +} + +const freshId = (() => { + let id = 0 + + return () => { + id++ + return '__syn' + id + } +})() + +/** + * Create an AST node from a Source value. + * + * @param value any valid Source value (number/string/boolean/Closure) + * @returns {Node} + */ +export const createNode = (value: Value): es.Node => { + if (value instanceof Closure) { + return value.node + } + return createLiteralNode(value) +} + +export const composeWalker = ( + w1: Walker, + w2: Walker +) => { + return (node: T, state: S, recurse: SimpleWalker) => { + w1(node, state, recurse) + w2(node, state, recurse) + } +} diff --git a/src/slang/utils/rttc.ts b/src/slang/utils/rttc.ts new file mode 100644 index 0000000000..51ad8a10e6 --- /dev/null +++ b/src/slang/utils/rttc.ts @@ -0,0 +1,91 @@ +import * as es from 'estree' +import { SourceError, Value, Context, ErrorSeverity, + ErrorType } from '../types' + +class TypeError implements SourceError { + type: ErrorType.RUNTIME + severity: ErrorSeverity.WARNING + location: es.SourceLocation + + constructor( + node: es.Node, + public context: string, + public expected: string, + public got: string) { + this.location = node.loc! + } + + explain() { + return `TypeError: Expected ${this.expected} in ${this.context}, got ${this.got}.` + } + + elaborate() { + return 'TODO' + } +} + +const isNumber = (v: Value) => typeof v === 'number' +const isString = (v: Value) => typeof v === 'string' + +const checkAdditionAndComparison = (context: Context, left: Value, right: Value) => { + if (!(isNumber(left) || isString(left))) { + context.errors.push(new TypeError( + context.runtime.nodes[0], + 'left hand side of operation', + 'number or string', + typeof left + )) + } + if (!(isNumber(right) || isString(right))) { + context.errors.push(new TypeError( + context.runtime.nodes[0], + 'right hand side of operation', + 'number or string', + typeof right + )) + } +} + +const checkBinaryArithmetic = (context: Context, left: Value, right: Value) => { + if (!isNumber(left)) { + context.errors.push(new TypeError( + context.runtime.nodes[0], + 'left hand side of operation', + 'number', + typeof left + )) + } + if (!isNumber(left)) { + context.errors.push(new TypeError( + context.runtime.nodes[0], + 'right hand side of operation', + 'number', + typeof right + )) + } +} + +export const checkBinaryExpression = ( + context: Context, + operator: es.BinaryOperator, + left: Value, + right: Value +) => { + switch (operator) { + case '-': + case '*': + case '/': + case '%': + return checkBinaryArithmetic(context, left, right) + case '<': + case '<=': + case '>': + case '>=': + case '+': + return checkAdditionAndComparison(context, left, right) + case '!==': + case '===': + default: + return + } +} From 7a23838248201830f6e35d27f60afd09326f61b1 Mon Sep 17 00:00:00 2001 From: remo5000 Date: Wed, 16 May 2018 14:11:34 +0800 Subject: [PATCH 028/129] Add react-saga and react types to yarn The react types were added as a result of the `yarn add` command. --- package.json | 1 + yarn.lock | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/package.json b/package.json index a333a53a16..a9f1d5bef4 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "react-router": "^4.2.0", "react-router-dom": "^4.2.2", "react-router-redux": "^5.0.0-alpha.9", + "react-saga": "^0.2.6", "react-transition-group": "^2.3.1", "redux": "^3.7.2", "redux-mock-store": "^1.5.1", diff --git a/yarn.lock b/yarn.lock index 153ec2a405..249e75fb82 100644 --- a/yarn.lock +++ b/yarn.lock @@ -130,12 +130,22 @@ dependencies: csstype "^2.2.0" +"@types/react@~15": + version "15.6.15" + resolved "https://registry.yarnpkg.com/@types/react/-/react-15.6.15.tgz#1856f932120311aa566f91e6d0c6e613d6448236" + "@types/redux-mock-store@^0.0.13": version "0.0.13" resolved "https://registry.yarnpkg.com/@types/redux-mock-store/-/redux-mock-store-0.0.13.tgz#f7ec160214b854f2d976ca12525997a21512b2ea" dependencies: redux "^3.6.0" +"@types/redux-saga@^0.9.31": + version "0.9.31" + resolved "https://registry.yarnpkg.com/@types/redux-saga/-/redux-saga-0.9.31.tgz#6b8de326835e179037a3bbcf658a10f60542bafc" + dependencies: + redux "^3.6.0" + abab@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e" @@ -3781,6 +3791,10 @@ is-generator-fn@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-1.0.0.tgz#969d49e1bb3329f6bb7f09089be26578b2ddd46a" +is-generator-function@^1.0.3: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.7.tgz#d2132e529bb0000a7f80794d4bdf5cd5e5813522" + is-glob@^2.0.0, is-glob@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" @@ -6101,6 +6115,14 @@ react-router@^4.2.0: prop-types "^15.5.4" warning "^3.0.0" +react-saga@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/react-saga/-/react-saga-0.2.6.tgz#52968cc0b60f351d4958fc2c3abc17af59953dee" + dependencies: + "@types/react" "~15" + "@types/redux-saga" "^0.9.31" + is-generator-function "^1.0.3" + react-scripts-ts@^2.15.1: version "2.15.1" resolved "https://registry.yarnpkg.com/react-scripts-ts/-/react-scripts-ts-2.15.1.tgz#98032840e61faff65e59047036a7fa236568d503" From aeb3c2d7e1a35e9ec9355a94364a0b9b544fb033 Mon Sep 17 00:00:00 2001 From: remo5000 Date: Wed, 16 May 2018 14:13:52 +0800 Subject: [PATCH 029/129] Add saga to middleware list --- src/createStore.ts | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/createStore.ts b/src/createStore.ts index 0a89532061..71b65a72c1 100644 --- a/src/createStore.ts +++ b/src/createStore.ts @@ -8,25 +8,32 @@ import { Store, StoreEnhancer } from 'redux' +import createSagaMiddleware from 'redux-saga' +import mainSaga from './sagas' import reducers, { IState } from './reducers' declare var __REDUX_DEVTOOLS_EXTENSION_COMPOSE__: () => StoreEnhancer -export default function createStore(history: History): Store { - const middleware = [routerMiddleware(history)] - +function createStore(history: History): Store { let composeEnhancers: any = compose + const sagaMiddleware = createSagaMiddleware() + const middleware = [sagaMiddleware, routerMiddleware(history)] + sagaMiddleware.run(mainSaga) + if (typeof __REDUX_DEVTOOLS_EXTENSION_COMPOSE__ === 'function') { composeEnhancers = __REDUX_DEVTOOLS_EXTENSION_COMPOSE__ } - + + const rootReducer = combineReducers({ + ...reducers, + router: routerReducer + }) const enchancers = composeEnhancers(applyMiddleware(...middleware)) return _createStore( - combineReducers({ - ...reducers, - router: routerReducer - }), + rootReducer, enchancers ) } + +export default createStore From f69381b1b5a7a52fc1ca6996a8c5ff1f07d81669 Mon Sep 17 00:00:00 2001 From: remo5000 Date: Wed, 16 May 2018 15:49:37 +0800 Subject: [PATCH 030/129] Segregate actionTypes from actions --- src/actions/actionTypes.ts | 5 +++++ src/actions/index.ts | 1 + src/actions/playground.ts | 9 ++------- 3 files changed, 8 insertions(+), 7 deletions(-) create mode 100644 src/actions/actionTypes.ts create mode 100644 src/actions/index.ts diff --git a/src/actions/actionTypes.ts b/src/actions/actionTypes.ts new file mode 100644 index 0000000000..69f9298aa9 --- /dev/null +++ b/src/actions/actionTypes.ts @@ -0,0 +1,5 @@ +/** + * The `type` attribute for an `Action` which updates the `IPlaygroundState` + * `editorValue` + */ +export const UPDATE_EDITOR_VALUE: string = 'UPDATE_EDITOR_VALUE' \ No newline at end of file diff --git a/src/actions/index.ts b/src/actions/index.ts new file mode 100644 index 0000000000..e689875011 --- /dev/null +++ b/src/actions/index.ts @@ -0,0 +1 @@ +export * from './playground' \ No newline at end of file diff --git a/src/actions/playground.ts b/src/actions/playground.ts index 97d541f4bd..9d45108bfd 100644 --- a/src/actions/playground.ts +++ b/src/actions/playground.ts @@ -1,10 +1,5 @@ import { Action, ActionCreator } from 'redux' - -/** - * The `type` attribute for an `Action` which updates the `IPlaygroundState` - * `editorValue` - */ -export const UPDATE_EDITOR_VALUE: string = 'UPDATE_EDITOR_VALUE' +import * as actionTypes from './actionTypes' /** * Represents an `Action` which updates the `editorValue` of a @@ -22,6 +17,6 @@ export interface IUpdateEditorValue extends Action { * @param newEditorValue - The new string value for `editorValue` */ export const updateEditorValue: ActionCreator = (newEditorValue: string) => ({ - type: UPDATE_EDITOR_VALUE, + type: actionTypes.UPDATE_EDITOR_VALUE, newEditorValue }) From df3cde8eee74806c3f66a79bce0a1b6024f6d0c4 Mon Sep 17 00:00:00 2001 From: remo5000 Date: Wed, 16 May 2018 15:52:35 +0800 Subject: [PATCH 031/129] Fix reducer actionType import --- src/actions/index.ts | 3 ++- src/reducers/playground.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/actions/index.ts b/src/actions/index.ts index e689875011..896018bda9 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -1 +1,2 @@ -export * from './playground' \ No newline at end of file +export * from './playground' +export * from './actionTypes' diff --git a/src/reducers/playground.ts b/src/reducers/playground.ts index 59dbed520d..e7791e0e6a 100644 --- a/src/reducers/playground.ts +++ b/src/reducers/playground.ts @@ -1,5 +1,5 @@ import { Action, Reducer } from 'redux' -import { IUpdateEditorValue, UPDATE_EDITOR_VALUE } from '../actions/playground' +import { IUpdateEditorValue, UPDATE_EDITOR_VALUE } from '../actions' /** * A state for the playground container From 83f206479c1db4edfc66ffce0703ed04d72afaaa Mon Sep 17 00:00:00 2001 From: remo5000 Date: Wed, 16 May 2018 16:18:05 +0800 Subject: [PATCH 032/129] Remove actionType import from action/index --- src/actions/index.ts | 1 - src/reducers/playground.ts | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/actions/index.ts b/src/actions/index.ts index 896018bda9..e3e2cd85cf 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -1,2 +1 @@ export * from './playground' -export * from './actionTypes' diff --git a/src/reducers/playground.ts b/src/reducers/playground.ts index e7791e0e6a..a7cc239941 100644 --- a/src/reducers/playground.ts +++ b/src/reducers/playground.ts @@ -1,5 +1,6 @@ import { Action, Reducer } from 'redux' -import { IUpdateEditorValue, UPDATE_EDITOR_VALUE } from '../actions' +import { IUpdateEditorValue } from '../actions' +import { UPDATE_EDITOR_VALUE } from '../actions/actionTypes' /** * A state for the playground container From 3c1705a91ddd73a78224dffa8b18b0ecd7ce696c Mon Sep 17 00:00:00 2001 From: ning Date: Wed, 16 May 2018 16:23:23 +0800 Subject: [PATCH 033/129] Fix fatal bug with defineVariable calls --- src/slang/interpreter.ts | 83 +++++++++------------------------------- 1 file changed, 18 insertions(+), 65 deletions(-) diff --git a/src/slang/interpreter.ts b/src/slang/interpreter.ts index bdee475a17..fc7d91dc55 100644 --- a/src/slang/interpreter.ts +++ b/src/slang/interpreter.ts @@ -1,13 +1,5 @@ import * as es from 'estree' -import { - ArrowClosure, - Closure, - Frame, - Value, - Context, - SourceError, - ErrorSeverity -} from './types' +import { ArrowClosure, Closure, Frame, Value, Context, SourceError, ErrorSeverity } from './types' import { toJS } from './interop' import { createNode } from './utils/node' import * as constants from './constants' @@ -23,11 +15,7 @@ class BreakValue {} class ContinueValue {} class TailCallReturnValue { - constructor( - public callee: Closure, - public args: Value[], - public node: es.CallExpression - ) {} + constructor(public callee: Closure, public args: Value[], public node: es.CallExpression) {} } const createFrame = ( @@ -56,8 +44,7 @@ const createFrame = ( const handleError = (context: Context, error: SourceError) => { context.errors.push(error) if (error.severity === ErrorSeverity.ERROR) { - const globalFrame = - context.runtime.frames[context.runtime.frames.length - 1] + const globalFrame = context.runtime.frames[context.runtime.frames.length - 1] context.runtime.frames = [globalFrame] throw error } else { @@ -69,10 +56,7 @@ function defineVariable(context: Context, name: string, value: Value) { const frame = context.runtime.frames[0] if (frame.environment.hasOwnProperty(name)) { - handleError( - context, - new errors.VariableRedeclaration(context.runtime.nodes[0]!, name) - ) + handleError(context, new errors.VariableRedeclaration(context.runtime.nodes[0]!, name)) } frame.environment[name] = value @@ -88,11 +72,9 @@ function* leave(context: Context) { yield context } const currentFrame = (context: Context) => context.runtime.frames[0] -const replaceFrame = (context: Context, frame: Frame) => - (context.runtime.frames[0] = frame) +const replaceFrame = (context: Context, frame: Frame) => (context.runtime.frames[0] = frame) const popFrame = (context: Context) => context.runtime.frames.shift() -const pushFrame = (context: Context, frame: Frame) => - context.runtime.frames.unshift(frame) +const pushFrame = (context: Context, frame: Frame) => context.runtime.frames.unshift(frame) const getVariable = (context: Context, name: string) => { let frame: Frame | null = context.runtime.frames[0] @@ -103,10 +85,7 @@ const getVariable = (context: Context, name: string) => { frame = frame.parent } } - handleError( - context, - new errors.UndefinedVariable(name, context.runtime.nodes[0]) - ) + handleError(context, new errors.UndefinedVariable(name, context.runtime.nodes[0])) } const setVariable = (context: Context, name: string, value: any) => { @@ -119,10 +98,7 @@ const setVariable = (context: Context, name: string, value: any) => { frame = frame.parent } } - handleError( - context, - new errors.UndefinedVariable(name, context.runtime.nodes[0]) - ) + handleError(context, new errors.UndefinedVariable(name, context.runtime.nodes[0])) } const checkNumberOfArguments = ( @@ -132,11 +108,7 @@ const checkNumberOfArguments = ( exp: es.CallExpression ) => { if (callee.node.params.length !== args.length) { - const error = new errors.InvalidNumberOfArguments( - exp, - callee.node.params.length, - args.length - ) + const error = new errors.InvalidNumberOfArguments(exp, callee.node.params.length, args.length) handleError(context, error) } } @@ -149,10 +121,7 @@ function* getArgs(context: Context, call: es.CallExpression) { return args } -export type Evaluator = ( - node: T, - context: Context -) => IterableIterator +export type Evaluator = (node: T, context: Context) => IterableIterator export const evaluators: { [nodeType: string]: Evaluator } = { /** Simple Values */ @@ -260,10 +229,7 @@ export const evaluators: { [nodeType: string]: Evaluator } = { } return result }, - ConditionalExpression: function*( - node: es.ConditionalExpression, - context: Context - ) { + ConditionalExpression: function*(node: es.ConditionalExpression, context: Context) { return yield* this.IfStatement(node, context) }, LogicalExpression: function*(node: es.LogicalExpression, context: Context) { @@ -274,15 +240,12 @@ export const evaluators: { [nodeType: string]: Evaluator } = { return left } }, - VariableDeclaration: function*( - node: es.VariableDeclaration, - context: Context - ) { + VariableDeclaration: function*(node: es.VariableDeclaration, context: Context) { const declaration = node.declarations[0] const kind = node.kind const id = declaration.id as es.Identifier const value = yield* evaluate(declaration.init!, context) - defineVariable(context, kind, id.name, value) + defineVariable(context, id.name, value) return undefined }, ContinueStatement: function*(node: es.ContinueStatement, context: Context) { @@ -336,10 +299,7 @@ export const evaluators: { [nodeType: string]: Evaluator } = { } } }, - AssignmentExpression: function*( - node: es.AssignmentExpression, - context: Context - ) { + AssignmentExpression: function*(node: es.AssignmentExpression, context: Context) { if (node.left.type === 'MemberExpression') { const left = node.left let obj = yield* evaluate(left.object, context) @@ -359,14 +319,11 @@ export const evaluators: { [nodeType: string]: Evaluator } = { setVariable(context, id.name, value) return value }, - FunctionDeclaration: function*( - node: es.FunctionDeclaration, - context: Context - ) { + FunctionDeclaration: function*(node: es.FunctionDeclaration, context: Context) { const id = node.id as es.Identifier // tslint:disable-next-line:no-any const closure = new Closure(node as any, currentFrame(context), context) - defineVariable(context, undefined, id.name, closure) + defineVariable(context, id.name, closure) return undefined }, IfStatement: function*(node: es.IfStatement, context: Context) { @@ -379,10 +336,7 @@ export const evaluators: { [nodeType: string]: Evaluator } = { return undefined } }, - ExpressionStatement: function*( - node: es.ExpressionStatement, - context: Context - ) { + ExpressionStatement: function*(node: es.ExpressionStatement, context: Context) { return yield* evaluate(node.expression, context) }, ReturnStatement: function*(node: es.ReturnStatement, context: Context) { @@ -508,8 +462,7 @@ export function* apply( break } catch (e) { // Recover from exception - const globalFrame = - context.runtime.frames[context.runtime.frames.length - 1] + const globalFrame = context.runtime.frames[context.runtime.frames.length - 1] context.runtime.frames = [globalFrame] const loc = node ? node.loc! : constants.UNKNOWN_LOCATION handleError(context, new errors.ExceptionError(e, loc)) From 31761b04033bb94b3edd9179bfc88caf6ea7c118 Mon Sep 17 00:00:00 2001 From: ning Date: Wed, 16 May 2018 16:24:44 +0800 Subject: [PATCH 034/129] Fix missing imports Some imports of slang are already provided as additional dependencies to other modules, e.g. acorn is already a dependency of webpack --- package.json | 2 ++ yarn.lock | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/package.json b/package.json index f419a3b32a..d1c98d2e09 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,8 @@ }, "dependencies": { "@blueprintjs/core": "^2.1.1", + "astring": "^1.3.0", + "common-tags": "^1.7.2", "normalize.css": "^8.0.0", "react": "^16.3.1", "react-dom": "^16.3.1", diff --git a/yarn.lock b/yarn.lock index 86de278828..1893873aa2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -406,6 +406,10 @@ astral-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" +astring@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/astring/-/astring-1.3.0.tgz#7ed6ff7d317df5d4a7a06a42b5097774d8d48e01" + async-each@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" @@ -1682,6 +1686,12 @@ commander@~2.13.0: version "2.13.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" +common-tags@^1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.7.2.tgz#24d9768c63d253a56ecff93845b44b4df1d52771" + dependencies: + babel-runtime "^6.26.0" + commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" From 189d25bcc12a67ebb9ff1c07015f435748615476 Mon Sep 17 00:00:00 2001 From: ning Date: Wed, 16 May 2018 16:25:36 +0800 Subject: [PATCH 035/129] Add tests for slang --- src/mocks/context.ts | 6 + .../__tests__/__snapshots__/index.ts.snap | 157 ++++++++++++++++++ .../__tests__/__snapshots__/parser.ts.snap | 135 +++++++++++++++ src/slang/__tests__/index.ts | 73 ++++++++ src/slang/__tests__/parser.ts | 20 +++ 5 files changed, 391 insertions(+) create mode 100644 src/mocks/context.ts create mode 100644 src/slang/__tests__/__snapshots__/index.ts.snap create mode 100644 src/slang/__tests__/__snapshots__/parser.ts.snap create mode 100644 src/slang/__tests__/index.ts create mode 100644 src/slang/__tests__/parser.ts diff --git a/src/mocks/context.ts b/src/mocks/context.ts new file mode 100644 index 0000000000..b0983f4c62 --- /dev/null +++ b/src/mocks/context.ts @@ -0,0 +1,6 @@ +import { Context } from '../slang/types' +import createContext from '../slang/createContext' + +export function mockContext(): Context { + return createContext() +} diff --git a/src/slang/__tests__/__snapshots__/index.ts.snap b/src/slang/__tests__/__snapshots__/index.ts.snap new file mode 100644 index 0000000000..f8ab8e97d1 --- /dev/null +++ b/src/slang/__tests__/__snapshots__/index.ts.snap @@ -0,0 +1,157 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Arrow function definition returns itself 1`] = ` +Object { + "status": "finished", + "value": ArrowClosure { + "frame": Object { + "environment": Object { + "display": [Function], + "error": [Function], + "math_PI": 3.141592653589793, + "math_sqrt": [Function], + "parse_int": [Function], + "prompt": [Function], + "runtime": [Function], + "undefined": undefined, + }, + "name": "global", + "parent": null, + }, + "fun": [Function], + "name": "Anonymous1", + "node": Node { + "__id": "node_12", + "body": Node { + "__id": "node_11", + "end": 8, + "loc": SourceLocation { + "end": Position { + "column": 8, + "line": 1, + }, + "start": Position { + "column": 6, + "line": 1, + }, + }, + "raw": "42", + "start": 6, + "type": "Literal", + "value": 42, + }, + "end": 8, + "expression": true, + "generator": false, + "id": null, + "loc": SourceLocation { + "end": Position { + "column": 8, + "line": 1, + }, + "start": Position { + "column": 0, + "line": 1, + }, + }, + "params": Array [], + "start": 0, + "type": "ArrowFunctionExpression", + }, + }, +} +`; + +exports[`Arrow function definition returns itself 2`] = ` +ArrowClosure { + "frame": Object { + "environment": Object { + "display": [Function], + "error": [Function], + "math_PI": 3.141592653589793, + "math_sqrt": [Function], + "parse_int": [Function], + "prompt": [Function], + "runtime": [Function], + "undefined": undefined, + }, + "name": "global", + "parent": null, + }, + "fun": [Function], + "name": "Anonymous1", + "node": Node { + "__id": "node_12", + "body": Node { + "__id": "node_11", + "end": 8, + "loc": SourceLocation { + "end": Position { + "column": 8, + "line": 1, + }, + "start": Position { + "column": 6, + "line": 1, + }, + }, + "raw": "42", + "start": 6, + "type": "Literal", + "value": 42, + }, + "end": 8, + "expression": true, + "generator": false, + "id": null, + "loc": SourceLocation { + "end": Position { + "column": 8, + "line": 1, + }, + "start": Position { + "column": 0, + "line": 1, + }, + }, + "params": Array [], + "start": 0, + "type": "ArrowFunctionExpression", + }, +} +`; + +exports[`Empty code returns undefined 1`] = ` +Object { + "status": "finished", + "value": undefined, +} +`; + +exports[`Factorial arrow function 1`] = ` +Object { + "status": "finished", + "value": 120, +} +`; + +exports[`Single boolean self-evaluates to itself 1`] = ` +Object { + "status": "finished", + "value": true, +} +`; + +exports[`Single number self-evaluates to itself 1`] = ` +Object { + "status": "finished", + "value": 42, +} +`; + +exports[`Single string self-evaluates to itself 1`] = ` +Object { + "status": "finished", + "value": "42", +} +`; diff --git a/src/slang/__tests__/__snapshots__/parser.ts.snap b/src/slang/__tests__/__snapshots__/parser.ts.snap new file mode 100644 index 0000000000..2cd793aa2a --- /dev/null +++ b/src/slang/__tests__/__snapshots__/parser.ts.snap @@ -0,0 +1,135 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Empty parse returns empty Program Node 1`] = ` +Node { + "__id": "node_1", + "body": Array [], + "end": 0, + "loc": SourceLocation { + "end": Position { + "column": 0, + "line": 1, + }, + "start": Position { + "column": 0, + "line": 1, + }, + }, + "sourceType": "script", + "start": 0, + "type": "Program", +} +`; + +exports[`Parse a single number 1`] = ` +Node { + "__id": "node_7", + "body": Array [ + Node { + "__id": "node_6", + "end": 3, + "expression": Node { + "__id": "node_5", + "end": 2, + "loc": SourceLocation { + "end": Position { + "column": 2, + "line": 1, + }, + "start": Position { + "column": 0, + "line": 1, + }, + }, + "raw": "42", + "start": 0, + "type": "Literal", + "value": 42, + }, + "loc": SourceLocation { + "end": Position { + "column": 3, + "line": 1, + }, + "start": Position { + "column": 0, + "line": 1, + }, + }, + "start": 0, + "type": "ExpressionStatement", + }, + ], + "end": 3, + "loc": SourceLocation { + "end": Position { + "column": 3, + "line": 1, + }, + "start": Position { + "column": 0, + "line": 1, + }, + }, + "sourceType": "script", + "start": 0, + "type": "Program", +} +`; + +exports[`Parse a single string 1`] = ` +Node { + "__id": "node_4", + "body": Array [ + Node { + "__id": "node_3", + "directive": "42", + "end": 5, + "expression": Node { + "__id": "node_2", + "end": 4, + "loc": SourceLocation { + "end": Position { + "column": 4, + "line": 1, + }, + "start": Position { + "column": 0, + "line": 1, + }, + }, + "raw": "'42'", + "start": 0, + "type": "Literal", + "value": "42", + }, + "loc": SourceLocation { + "end": Position { + "column": 5, + "line": 1, + }, + "start": Position { + "column": 0, + "line": 1, + }, + }, + "start": 0, + "type": "ExpressionStatement", + }, + ], + "end": 5, + "loc": SourceLocation { + "end": Position { + "column": 5, + "line": 1, + }, + "start": Position { + "column": 0, + "line": 1, + }, + }, + "sourceType": "script", + "start": 0, + "type": "Program", +} +`; diff --git a/src/slang/__tests__/index.ts b/src/slang/__tests__/index.ts new file mode 100644 index 0000000000..f2970cb2d4 --- /dev/null +++ b/src/slang/__tests__/index.ts @@ -0,0 +1,73 @@ +import { stripIndent } from 'common-tags' + +import { mockContext } from '../../mocks/context' +import { runInContext } from '../index' + +test('Empty code returns undefined', () => { + const code = '' + const context = mockContext() + const promise = runInContext(code, context) + return promise.then(obj => { + expect(obj).toMatchSnapshot() + expect(obj.status).toBe('finished') + expect(obj.value).toBe(undefined) + }) +}) + +test('Single string self-evaluates to itself', () => { + const code = "'42';" + const context = mockContext() + const promise = runInContext(code, context) + return promise.then(obj => { + expect(obj).toMatchSnapshot() + expect(obj.status).toBe('finished') + expect(obj.value).toBe('42') + }) +}) + +test('Single number self-evaluates to itself', () => { + const code = '42;' + const context = mockContext() + const promise = runInContext(code, context) + return promise.then(obj => { + expect(obj).toMatchSnapshot() + expect(obj.status).toBe('finished') + expect(obj.value).toBe(42) + }) +}) + +test('Single boolean self-evaluates to itself', () => { + const code = 'true;' + const context = mockContext() + const promise = runInContext(code, context) + return promise.then(obj => { + expect(obj).toMatchSnapshot() + expect(obj.status).toBe('finished') + expect(obj.value).toBe(true) + }) +}) + +test('Arrow function definition returns itself', () => { + const code = '() => 42;' + const context = mockContext() + const promise = runInContext(code, context) + return promise.then(obj => { + expect(obj).toMatchSnapshot() + expect(obj.status).toBe('finished') + expect(obj.value).toMatchSnapshot() + }) +}) + +test('Factorial arrow function', () => { + const code = stripIndent` + const fac = (i) => i === 1 ? 1 : i * fac(i-1); + fac(5); + ` + const context = mockContext() + const promise = runInContext(code, context) + return promise.then(obj => { + expect(obj).toMatchSnapshot() + expect(obj.status).toBe('finished') + expect(obj.value).toBe(120) + }) +}) diff --git a/src/slang/__tests__/parser.ts b/src/slang/__tests__/parser.ts new file mode 100644 index 0000000000..e9f1562486 --- /dev/null +++ b/src/slang/__tests__/parser.ts @@ -0,0 +1,20 @@ +import { mockContext } from '../../mocks/context' +import { parse } from '../parser' + +test('Empty parse returns empty Program Node', () => { + const context = mockContext() + const program = parse('', context) + expect(program).toMatchSnapshot() +}) + +test('Parse a single string', () => { + const context = mockContext() + const program = parse("'42';", context) + expect(program).toMatchSnapshot() +}) + +test('Parse a single number', () => { + const context = mockContext() + const program = parse('42;', context) + expect(program).toMatchSnapshot() +}) From e18f2ae3e1bd86380cc12285652ac204d01e6f65 Mon Sep 17 00:00:00 2001 From: ning Date: Wed, 16 May 2018 16:25:55 +0800 Subject: [PATCH 036/129] Remove unused type definitions --- src/slang/typings/acorn.d.ts | 53 ---------------------------------- src/slang/typings/astring.d.ts | 1 - src/slang/typings/estree.d.ts | 8 ----- 3 files changed, 62 deletions(-) delete mode 100644 src/slang/typings/acorn.d.ts delete mode 100644 src/slang/typings/astring.d.ts delete mode 100644 src/slang/typings/estree.d.ts diff --git a/src/slang/typings/acorn.d.ts b/src/slang/typings/acorn.d.ts deleted file mode 100644 index cec251fd9e..0000000000 --- a/src/slang/typings/acorn.d.ts +++ /dev/null @@ -1,53 +0,0 @@ -declare module 'acorn/dist/walk' { - import * as es from 'estree' - - namespace AcornWalk { - export type SimpleWalker = (node: es.Node, state?: S) => void - export type SimpleWalkers = { [name: string]: SimpleWalker } - export type Walker = ( - node: T, - state: S, - callback: SimpleWalker - ) => void - export type Walkers = { [name: string]: Walker } - type NodeTest = (nodeType: string, node: es.Node) => boolean - - export const base: Walkers - - export function simple( - node: es.Node, - visitors: SimpleWalkers, - base?: SimpleWalkers, - state?: S - ): void - export function recursive( - node: es.Node, - state: S, - functions: Walkers - ): void - export function findNodeAt( - node: es.Node, - start: null | number, - end: null | number, - test: string | NodeTest, - base?: SimpleWalkers, - state?: S - ): void - export function findNodeAround( - node: es.Node, - pos: es.Position, - test: string | NodeTest, - base?: SimpleWalkers, - state?: S - ): void - export function findNodeAfter( - node: es.Node, - pos: es.Position, - test: string | NodeTest, - base?: SimpleWalkers, - state?: S - ): void - } - - export = AcornWalk -} diff --git a/src/slang/typings/astring.d.ts b/src/slang/typings/astring.d.ts deleted file mode 100644 index 7f3a6fd2f2..0000000000 --- a/src/slang/typings/astring.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module 'astring' diff --git a/src/slang/typings/estree.d.ts b/src/slang/typings/estree.d.ts deleted file mode 100644 index 33c844f71f..0000000000 --- a/src/slang/typings/estree.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -import estree from 'estree' - -declare module 'estree' { - interface BaseNode { - __id?: string - __call?: string - } -} From a36959c8ea5f1f98792b798243b212dd39c76b8f Mon Sep 17 00:00:00 2001 From: ning Date: Wed, 16 May 2018 16:26:16 +0800 Subject: [PATCH 037/129] Lint typescript files of slang --- src/slang/cfg.ts | 27 +++------ src/slang/index.ts | 10 ++-- src/slang/interop.ts | 5 +- src/slang/interpreter-errors.ts | 4 +- src/slang/parser.ts | 5 +- src/slang/rules/bracesAroundIfElse.ts | 5 +- src/slang/rules/noDeclareMutable.ts | 4 +- src/slang/rules/noDeclareReserved.ts | 5 +- src/slang/rules/noImplicitDeclareUndefined.ts | 4 +- src/slang/rules/singleVariableDeclaration.ts | 6 +- src/slang/schedulers.ts | 4 +- src/slang/stdlib/list.ts | 45 +++----------- src/slang/stdlib/misc.ts | 4 +- src/slang/types.ts | 6 +- src/slang/utils/node.ts | 10 +--- src/slang/utils/rttc.ts | 60 +++++++++---------- 16 files changed, 65 insertions(+), 139 deletions(-) diff --git a/src/slang/cfg.ts b/src/slang/cfg.ts index 56873d560a..8741451b2e 100644 --- a/src/slang/cfg.ts +++ b/src/slang/cfg.ts @@ -55,11 +55,7 @@ const exitScope = (context: Context) => { walkers.ExpressionStatement = composeWalker(connect, base.ExpressionStatement) walkers.VariableDeclaration = composeWalker(connect, base.VariableDeclaration) -const walkIfStatement: Walker = ( - node, - context, - recurse -) => { +const walkIfStatement: Walker = (node, context, recurse) => { const test = node.test as es.Node let consequentExit let alternateExit @@ -88,22 +84,17 @@ const walkIfStatement: Walker = ( } walkers.IfStatement = walkIfStatement -const walkReturnStatement: Walker = ( - node, - state -) => { +const walkReturnStatement: Walker = (node, state) => { connect(node, state) exitScope(state) } -walkers.ReturnStatement = composeWalker( - base.ReturnStatement, - walkReturnStatement -) - -const walkFunction: Walker< - es.FunctionDeclaration | es.FunctionExpression, - Context -> = (node, context, recurse) => { +walkers.ReturnStatement = composeWalker(base.ReturnStatement, walkReturnStatement) + +const walkFunction: Walker = ( + node, + context, + recurse +) => { // Check whether function declaration is from outer scope or its own if (scopeQueue[0].node !== node) { connect(node, context) diff --git a/src/slang/index.ts b/src/slang/index.ts index 0a20c89f4e..9ad263ea4f 100644 --- a/src/slang/index.ts +++ b/src/slang/index.ts @@ -7,7 +7,7 @@ import { AsyncScheduler, PreemptiveScheduler } from './schedulers' import { toString } from './interop' export type Options = { - scheduler: 'preemptive' | 'async', + scheduler: 'preemptive' | 'async' steps: number } @@ -17,15 +17,15 @@ const DEFAULT_OPTIONS: Options = { } export class ParseError { - constructor(public errors: SourceError[]) { - } + constructor(public errors: SourceError[]) {} } export function runInContext( code: string, context: Context, - options: Partial = {}): Promise { - const theOptions: Options = {...options, ...DEFAULT_OPTIONS} + options: Partial = {} +): Promise { + const theOptions: Options = { ...options, ...DEFAULT_OPTIONS } context.errors = [] const program = parse(code, context) if (program) { diff --git a/src/slang/interop.ts b/src/slang/interop.ts index b9535d7860..1ecd5e444b 100644 --- a/src/slang/interop.ts +++ b/src/slang/interop.ts @@ -50,10 +50,7 @@ const arrayToString = (value: Value[], length: number) => { } else if (value.length === 0) { return '[]' } else { - return `[${toString(value[0], length + 1)}, ${toString( - value[1], - length + 1 - )}]` + return `[${toString(value[0], length + 1)}, ${toString(value[1], length + 1)}]` } } diff --git a/src/slang/interpreter-errors.ts b/src/slang/interpreter-errors.ts index 3468ee06a7..ab98438a0b 100644 --- a/src/slang/interpreter-errors.ts +++ b/src/slang/interpreter-errors.ts @@ -50,9 +50,7 @@ export class MaximumStackLimitExceeded implements SourceError { explain() { return ` Infinite recursion - ${generate(this.calls[0])}..${generate(this.calls[1])}..${generate( - this.calls[2] - )}.. + ${generate(this.calls[0])}..${generate(this.calls[1])}..${generate(this.calls[2])}.. ` } diff --git a/src/slang/parser.ts b/src/slang/parser.ts index f7e81d1cab..2f6b733f6b 100644 --- a/src/slang/parser.ts +++ b/src/slang/parser.ts @@ -164,10 +164,7 @@ rules.forEach(rule => { const keys = Object.keys(rule.checkers) keys.forEach(key => { walkers[key] = compose(walkers[key], (node, context) => { - if ( - typeof rule.disableOn !== 'undefined' && - context.week >= rule.disableOn - ) { + if (typeof rule.disableOn !== 'undefined' && context.week >= rule.disableOn) { return } const checker = rule.checkers[key] diff --git a/src/slang/rules/bracesAroundIfElse.ts b/src/slang/rules/bracesAroundIfElse.ts index e8649a2bfb..3ba74bfa6e 100644 --- a/src/slang/rules/bracesAroundIfElse.ts +++ b/src/slang/rules/bracesAroundIfElse.ts @@ -8,10 +8,7 @@ export class BracesAroundIfElseError implements SourceError { type = ErrorType.SYNTAX severity = ErrorSeverity.ERROR - constructor( - public node: es.IfStatement, - private branch: 'consequent' | 'alternate' - ) {} + constructor(public node: es.IfStatement, private branch: 'consequent' | 'alternate') {} get location() { return this.node.loc! diff --git a/src/slang/rules/noDeclareMutable.ts b/src/slang/rules/noDeclareMutable.ts index 5ff67fb035..1bdd37f153 100644 --- a/src/slang/rules/noDeclareMutable.ts +++ b/src/slang/rules/noDeclareMutable.ts @@ -16,9 +16,7 @@ export class noDeclareMutableError implements SourceError { explain() { return ( - 'Mutable variable declaration using keyword ' + - `'${this.node.kind}'` + - ' is not allowed.' + 'Mutable variable declaration using keyword ' + `'${this.node.kind}'` + ' is not allowed.' ) } diff --git a/src/slang/rules/noDeclareReserved.ts b/src/slang/rules/noDeclareReserved.ts index 56736966a6..8ca5f10b58 100644 --- a/src/slang/rules/noDeclareReserved.ts +++ b/src/slang/rules/noDeclareReserved.ts @@ -61,10 +61,7 @@ export class noDeclareReservedError implements SourceError { } explain() { - return ( - `Reserved word '${this.node.declarations[0].id.name}'` + - ' is not allowed as a name' - ) + return `Reserved word '${this.node.declarations[0].id.name}'` + ' is not allowed as a name' } elaborate() { diff --git a/src/slang/rules/noImplicitDeclareUndefined.ts b/src/slang/rules/noImplicitDeclareUndefined.ts index db04668c8f..6cb6759b6e 100644 --- a/src/slang/rules/noImplicitDeclareUndefined.ts +++ b/src/slang/rules/noImplicitDeclareUndefined.ts @@ -36,9 +36,7 @@ const noImplicitDeclareUndefined: Rule = { const errors: SourceError[] = [] for (const decl of node.declarations) { if (!decl.init) { - errors.push( - new NoImplicitDeclareUndefinedError(decl.id as es.Identifier) - ) + errors.push(new NoImplicitDeclareUndefinedError(decl.id as es.Identifier)) } } return errors diff --git a/src/slang/rules/singleVariableDeclaration.ts b/src/slang/rules/singleVariableDeclaration.ts index e4fe619e89..d86d5ab4a1 100644 --- a/src/slang/rules/singleVariableDeclaration.ts +++ b/src/slang/rules/singleVariableDeclaration.ts @@ -27,11 +27,7 @@ export class MultipleDeclarationsError implements SourceError { elaborate() { const fixs = this.fixs.map(n => '\t' + generate(n)).join('\n') - return ( - 'Split the variable declaration into multiple lines as follows\n\n' + - fixs + - '\n' - ) + return 'Split the variable declaration into multiple lines as follows\n\n' + fixs + '\n' } } diff --git a/src/slang/schedulers.ts b/src/slang/schedulers.ts index bfbcdee972..8573d313ef 100644 --- a/src/slang/schedulers.ts +++ b/src/slang/schedulers.ts @@ -45,9 +45,7 @@ export class PreemptiveScheduler implements Scheduler { for (let i = 1; i <= 3; i++) { stacks.push(context.runtime.frames[i - 1].callExpression!) } - context.errors.push( - new MaximumStackLimitExceeded(context.runtime.nodes[0], stacks) - ) + context.errors.push(new MaximumStackLimitExceeded(context.runtime.nodes[0], stacks)) } context.runtime.isRunning = false clearInterval(interval) diff --git a/src/slang/stdlib/list.ts b/src/slang/stdlib/list.ts index 16223ca0a8..dc1e1478ab 100644 --- a/src/slang/stdlib/list.ts +++ b/src/slang/stdlib/list.ts @@ -47,11 +47,7 @@ export function head(xs: List) { if (is_pair(xs)) { return xs[0] } else { - throw new Error( - 'head(xs) expects a pair as ' + - 'argument xs, but encountered ' + - toString(xs) - ) + throw new Error('head(xs) expects a pair as ' + 'argument xs, but encountered ' + toString(xs)) } } head.__SOURCE__ = 'head(xs)' @@ -63,11 +59,7 @@ export function tail(xs: List) { if (is_pair(xs)) { return xs[1] } else { - throw new Error( - 'tail(xs) expects a pair as ' + - 'argument xs, but encountered ' + - toString(xs) - ) + throw new Error('tail(xs) expects a pair as ' + 'argument xs, but encountered ' + toString(xs)) } } tail.__SOURCE__ = 'tail(xs)' @@ -192,9 +184,7 @@ build_list.__SOURCE__ = 'build_list(n, fun)' // first argument is not a function. export function for_each(fun: Function, xs: List) { if (!is_list(xs)) { - throw new Error( - 'for_each expects a list as argument xs, but ' + 'encountered ' + xs - ) + throw new Error('for_each expects a list as argument xs, but ' + 'encountered ' + xs) } for (; !is_empty_list(xs); xs = tail(xs)) { fun(head(xs)) @@ -215,9 +205,7 @@ list_to_string.__SOURCE__ = 'list_to_string(xs)' // reverse throws an exception if the argument is not a list. export function reverse(xs: List) { if (!is_list(xs)) { - throw new Error( - 'reverse(xs) expects a list as argument xs, but ' + 'encountered ' + xs - ) + throw new Error('reverse(xs) expects a list as argument xs, but ' + 'encountered ' + xs) } var result = [] for (; !is_empty_list(xs); xs = tail(xs)) { @@ -291,12 +279,7 @@ export const removeAll = remove_all export function equal(item1: Value, item2: Value): boolean { if (is_pair(item1) && is_pair(item2)) { return equal(head(item1), head(item2)) && equal(tail(item1), tail(item2)) - } else if ( - array_test(item1) && - item1.length === 0 && - array_test(item2) && - item2.length === 0 - ) { + } else if (array_test(item1) && item1.length === 0 && array_test(item2) && item2.length === 0) { return true } else { return item1 === item2 @@ -352,9 +335,7 @@ enum_list.__SOURCE__ = 'enum_list(start, end)' export function list_ref(xs: List, n: number) { if (n < 0) { throw new Error( - 'list_ref(xs, n) expects a positive integer as ' + - 'argument n, but encountered ' + - n + 'list_ref(xs, n) expects a positive integer as ' + 'argument n, but encountered ' + n ) } @@ -374,11 +355,7 @@ list_ref.__SOURCE__ = 'list_ref(xs, n)' // accumulate(op,zero,list(1,2,3)) results in // op(1, op(2, op(3, zero))) -export function accumulate( - op: (value: Value, acc: T) => T, - initial: T, - sequence: List -): T { +export function accumulate(op: (value: Value, acc: T) => T, initial: T, sequence: List): T { if (is_empty_list(sequence)) { return initial } else { @@ -397,9 +374,7 @@ export function set_head(xs: List, x: Value) { return undefined } else { throw new Error( - 'set_head(xs,x) expects a pair as ' + - 'argument xs, but encountered ' + - toString(xs) + 'set_head(xs,x) expects a pair as ' + 'argument xs, but encountered ' + toString(xs) ) } } @@ -415,9 +390,7 @@ export function set_tail(xs: List, x: Value) { return undefined } else { throw new Error( - 'set_tail(xs,x) expects a pair as ' + - 'argument xs, but encountered ' + - toString(xs) + 'set_tail(xs,x) expects a pair as ' + 'argument xs, but encountered ' + toString(xs) ) } } diff --git a/src/slang/stdlib/misc.ts b/src/slang/stdlib/misc.ts index 5358955604..fa4593d826 100644 --- a/src/slang/stdlib/misc.ts +++ b/src/slang/stdlib/misc.ts @@ -53,9 +53,7 @@ export function parse_int(inputString: string, radix: number) { // the two arguments are provided, and parsed is not NaN return parsed } else { - throw new Error( - 'parseInt expects two arguments a string s, and a positive integer i' - ) + throw new Error('parseInt expects two arguments a string s, and a positive integer i') } } parse_int.__SOURCE__ = 'parse_int(s, i)' diff --git a/src/slang/types.ts b/src/slang/types.ts index cee236ee80..de5aa5a0bd 100644 --- a/src/slang/types.ts +++ b/src/slang/types.ts @@ -136,11 +136,7 @@ export class Closure { /** Fake closure function */ public fun: Function - constructor( - public node: es.FunctionExpression, - public frame: Frame, - context: Context - ) { + constructor(public node: es.FunctionExpression, public frame: Frame, context: Context) { this.node = node try { if (this.node.id) { diff --git a/src/slang/utils/node.ts b/src/slang/utils/node.ts index a2c1aec02e..72ab2ad20a 100644 --- a/src/slang/utils/node.ts +++ b/src/slang/utils/node.ts @@ -61,10 +61,7 @@ export const replaceAST = (node: es.Node, before: es.Node, after: es.Node) => { } } else if (n.type === 'UnaryExpression') { return { ...n, argument: go(n.argument) } - } else if ( - n.type === 'BinaryExpression' || - n.type === 'LogicalExpression' - ) { + } else if (n.type === 'BinaryExpression' || n.type === 'LogicalExpression') { return { ...n, left: go(n.left), right: go(n.right) } } else { return n @@ -113,10 +110,7 @@ export const createNode = (value: Value): es.Node => { return createLiteralNode(value) } -export const composeWalker = ( - w1: Walker, - w2: Walker -) => { +export const composeWalker = (w1: Walker, w2: Walker) => { return (node: T, state: S, recurse: SimpleWalker) => { w1(node, state, recurse) w2(node, state, recurse) diff --git a/src/slang/utils/rttc.ts b/src/slang/utils/rttc.ts index 51ad8a10e6..8d3817fcd3 100644 --- a/src/slang/utils/rttc.ts +++ b/src/slang/utils/rttc.ts @@ -1,17 +1,12 @@ import * as es from 'estree' -import { SourceError, Value, Context, ErrorSeverity, - ErrorType } from '../types' +import { SourceError, Value, Context, ErrorSeverity, ErrorType } from '../types' class TypeError implements SourceError { type: ErrorType.RUNTIME severity: ErrorSeverity.WARNING location: es.SourceLocation - constructor( - node: es.Node, - public context: string, - public expected: string, - public got: string) { + constructor(node: es.Node, public context: string, public expected: string, public got: string) { this.location = node.loc! } @@ -29,39 +24,42 @@ const isString = (v: Value) => typeof v === 'string' const checkAdditionAndComparison = (context: Context, left: Value, right: Value) => { if (!(isNumber(left) || isString(left))) { - context.errors.push(new TypeError( - context.runtime.nodes[0], - 'left hand side of operation', - 'number or string', - typeof left - )) + context.errors.push( + new TypeError( + context.runtime.nodes[0], + 'left hand side of operation', + 'number or string', + typeof left + ) + ) } if (!(isNumber(right) || isString(right))) { - context.errors.push(new TypeError( - context.runtime.nodes[0], - 'right hand side of operation', - 'number or string', - typeof right - )) + context.errors.push( + new TypeError( + context.runtime.nodes[0], + 'right hand side of operation', + 'number or string', + typeof right + ) + ) } } const checkBinaryArithmetic = (context: Context, left: Value, right: Value) => { if (!isNumber(left)) { - context.errors.push(new TypeError( - context.runtime.nodes[0], - 'left hand side of operation', - 'number', - typeof left - )) + context.errors.push( + new TypeError(context.runtime.nodes[0], 'left hand side of operation', 'number', typeof left) + ) } if (!isNumber(left)) { - context.errors.push(new TypeError( - context.runtime.nodes[0], - 'right hand side of operation', - 'number', - typeof right - )) + context.errors.push( + new TypeError( + context.runtime.nodes[0], + 'right hand side of operation', + 'number', + typeof right + ) + ) } } From 7f374bc9b6f45187079c90ff979520eb31d88aaa Mon Sep 17 00:00:00 2001 From: remo5000 Date: Wed, 16 May 2018 16:26:32 +0800 Subject: [PATCH 038/129] Add interruption actionType --- src/actions/actionTypes.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/actions/actionTypes.ts b/src/actions/actionTypes.ts index 69f9298aa9..b0551d69f3 100644 --- a/src/actions/actionTypes.ts +++ b/src/actions/actionTypes.ts @@ -1,5 +1,5 @@ -/** - * The `type` attribute for an `Action` which updates the `IPlaygroundState` - * `editorValue` - */ -export const UPDATE_EDITOR_VALUE: string = 'UPDATE_EDITOR_VALUE' \ No newline at end of file +/** Playground */ +export const UPDATE_EDITOR_VALUE: string = 'UPDATE_EDITOR_VALUE' + +/** Interpreter */ +export const INTERRUPT_EXECUTION: string = 'INTERRUPT_EXECUTION' From 74df49fbd8f4791c9afdde4321b6f6f43ee51605 Mon Sep 17 00:00:00 2001 From: remo5000 Date: Wed, 16 May 2018 16:32:13 +0800 Subject: [PATCH 039/129] Add sagas WIP For ny's reference --- src/sagas/index.ts | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/sagas/index.ts diff --git a/src/sagas/index.ts b/src/sagas/index.ts new file mode 100644 index 0000000000..29c426ca94 --- /dev/null +++ b/src/sagas/index.ts @@ -0,0 +1,43 @@ +import { SagaIterator, delay, takeEvery } from 'redux-saga' +import { select, call, put, take, race } from 'redux-saga/effects' + +// import { Shape } from '../shape' +import { Context, createContext, runInContext, interrupt } from '../slang' + +import * as actions from '../actions' +import * as actionTypes from '../actions/actionTypes' + +function* evalCode(code: string, context: Context) { + const {result, interrupted} = yield race({ + result: call(runInContext, code, context), + interrupted: take(actionTypes.INTERRUPT_EXECUTION) + }) + if (result) { + if (result.status === 'finished') { + yield put(actions.evalInterpreterSuccess(result.value)) + } else { + yield put(actions.evalInterpreterError(context.errors)) + } + } else if (interrupted) { + interrupt(context) + yield call(showWarningMessage, 'Execution aborted by user') + } +} + +function* interpreterSaga(): SagaIterator { + // let library = yield select((state: Shape) => state.config.library) + let context: Context + + yield takeEvery(actionTypes.EVAL_EDITOR, function*() { + const code = yield select((state: IState) => state.playground.editorValue) + // context = createContext(library.week, library.externals) + context = createContext(3, "") + yield* evalCode(code, context) + }) +} + +function* mainSaga() { + yield* interpreterSaga() +} + +export default mainSaga From aedffa31fffae453e499b91c5e72e9778a21b982 Mon Sep 17 00:00:00 2001 From: remo5000 Date: Wed, 16 May 2018 16:40:38 +0800 Subject: [PATCH 040/129] Add more interpreter actions --- src/actions/actionTypes.ts | 3 +++ src/actions/index.ts | 1 + src/actions/interpreter.ts | 17 +++++++++++++++++ 3 files changed, 21 insertions(+) create mode 100644 src/actions/interpreter.ts diff --git a/src/actions/actionTypes.ts b/src/actions/actionTypes.ts index b0551d69f3..969818dfdf 100644 --- a/src/actions/actionTypes.ts +++ b/src/actions/actionTypes.ts @@ -2,4 +2,7 @@ export const UPDATE_EDITOR_VALUE: string = 'UPDATE_EDITOR_VALUE' /** Interpreter */ +export const EVAL_INTERPRETER = 'EVAL_INTERPRETER' +export const EVAL_INTERPRETER_SUCCESS = 'EVAL_INTERPRETER_SUCCESS' +export const EVAL_INTERPRETER_ERROR = 'EVAL_INTERPRETER_ERROR' export const INTERRUPT_EXECUTION: string = 'INTERRUPT_EXECUTION' diff --git a/src/actions/index.ts b/src/actions/index.ts index e3e2cd85cf..70c274749c 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -1 +1,2 @@ +export * from './interpreter' export * from './playground' diff --git a/src/actions/interpreter.ts b/src/actions/interpreter.ts new file mode 100644 index 0000000000..6d035d97b1 --- /dev/null +++ b/src/actions/interpreter.ts @@ -0,0 +1,17 @@ +import { SourceError, Value } from '../slang/types' +import * as actionTypes from './actionTypes' + +export const evalInterpreter = (code: string) => ({ + type: actionTypes.EVAL_INTERPRETER, + payload: code +}) + +export const evalInterpreterSuccess = (value: Value) => ({ + type: actionTypes.EVAL_INTERPRETER_SUCCESS, + payload: value +}) + +export const evalInterpreterError = (errors: SourceError[]) => ({ + type: actionTypes.EVAL_INTERPRETER_ERROR, + payload: errors +}) From 01c3514e7cc9cc692b7499bb454ffda4b02ace5a Mon Sep 17 00:00:00 2001 From: remo5000 Date: Wed, 16 May 2018 16:51:57 +0800 Subject: [PATCH 041/129] Change to older version of saga --- package.json | 2 +- src/sagas/index.ts | 6 ++++-- yarn.lock | 26 ++++---------------------- 3 files changed, 9 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index a9f1d5bef4..9a3a5fc502 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "react-router": "^4.2.0", "react-router-dom": "^4.2.2", "react-router-redux": "^5.0.0-alpha.9", - "react-saga": "^0.2.6", + "redux-saga": "^0.15.6", "react-transition-group": "^2.3.1", "redux": "^3.7.2", "redux-mock-store": "^1.5.1", diff --git a/src/sagas/index.ts b/src/sagas/index.ts index 29c426ca94..d110ec53da 100644 --- a/src/sagas/index.ts +++ b/src/sagas/index.ts @@ -1,5 +1,7 @@ -import { SagaIterator, delay, takeEvery } from 'redux-saga' -import { select, call, put, take, race } from 'redux-saga/effects' +import { SagaIterator, delay } from 'redux-saga' +import { takeEvery, select, call, put, take, race } from 'redux-saga/effects' +import { showSuccessMessage, showWarningMessage } from '../notification' + // import { Shape } from '../shape' import { Context, createContext, runInContext, interrupt } from '../slang' diff --git a/yarn.lock b/yarn.lock index 249e75fb82..fec4862c60 100644 --- a/yarn.lock +++ b/yarn.lock @@ -130,22 +130,12 @@ dependencies: csstype "^2.2.0" -"@types/react@~15": - version "15.6.15" - resolved "https://registry.yarnpkg.com/@types/react/-/react-15.6.15.tgz#1856f932120311aa566f91e6d0c6e613d6448236" - "@types/redux-mock-store@^0.0.13": version "0.0.13" resolved "https://registry.yarnpkg.com/@types/redux-mock-store/-/redux-mock-store-0.0.13.tgz#f7ec160214b854f2d976ca12525997a21512b2ea" dependencies: redux "^3.6.0" -"@types/redux-saga@^0.9.31": - version "0.9.31" - resolved "https://registry.yarnpkg.com/@types/redux-saga/-/redux-saga-0.9.31.tgz#6b8de326835e179037a3bbcf658a10f60542bafc" - dependencies: - redux "^3.6.0" - abab@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e" @@ -3791,10 +3781,6 @@ is-generator-fn@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-1.0.0.tgz#969d49e1bb3329f6bb7f09089be26578b2ddd46a" -is-generator-function@^1.0.3: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.7.tgz#d2132e529bb0000a7f80794d4bdf5cd5e5813522" - is-glob@^2.0.0, is-glob@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" @@ -6115,14 +6101,6 @@ react-router@^4.2.0: prop-types "^15.5.4" warning "^3.0.0" -react-saga@^0.2.6: - version "0.2.6" - resolved "https://registry.yarnpkg.com/react-saga/-/react-saga-0.2.6.tgz#52968cc0b60f351d4958fc2c3abc17af59953dee" - dependencies: - "@types/react" "~15" - "@types/redux-saga" "^0.9.31" - is-generator-function "^1.0.3" - react-scripts-ts@^2.15.1: version "2.15.1" resolved "https://registry.yarnpkg.com/react-scripts-ts/-/react-scripts-ts-2.15.1.tgz#98032840e61faff65e59047036a7fa236568d503" @@ -6301,6 +6279,10 @@ redux-mock-store@^1.5.1: dependencies: lodash.isplainobject "^4.0.6" +redux-saga@^0.15.6: + version "0.15.6" + resolved "https://registry.yarnpkg.com/redux-saga/-/redux-saga-0.15.6.tgz#8638dc522de6c6c0a496fe8b2b5466287ac2dc4d" + redux@^3.6.0, redux@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/redux/-/redux-3.7.2.tgz#06b73123215901d25d065be342eb026bc1c8537b" From 10662f73868d59ed0966360f5f9b3c3722ea9301 Mon Sep 17 00:00:00 2001 From: remo5000 Date: Wed, 16 May 2018 17:55:28 +0800 Subject: [PATCH 042/129] Add notification file --- src/actions/actionTypes.ts | 1 + src/notification.ts | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 src/notification.ts diff --git a/src/actions/actionTypes.ts b/src/actions/actionTypes.ts index 969818dfdf..5f58803d08 100644 --- a/src/actions/actionTypes.ts +++ b/src/actions/actionTypes.ts @@ -1,5 +1,6 @@ /** Playground */ export const UPDATE_EDITOR_VALUE: string = 'UPDATE_EDITOR_VALUE' +export const EVAL_EDITOR = 'EVAL_EDITOR' /** Interpreter */ export const EVAL_INTERPRETER = 'EVAL_INTERPRETER' diff --git a/src/notification.ts b/src/notification.ts new file mode 100644 index 0000000000..58e3a7afa4 --- /dev/null +++ b/src/notification.ts @@ -0,0 +1,21 @@ +import { Toaster, Position, Intent } from '@blueprintjs/core' + +const notification = Toaster.create({ + position: Position.TOP +}) + +export const showSuccessMessage = (message: string, timeout = 500) => { + notification.show({ + intent: Intent.SUCCESS, + message, + timeout + }) +} + +export const showWarningMessage = (message: string, timeout = 500) => { + notification.show({ + intent: Intent.WARNING, + message, + timeout + }) +} From 9b53194dfc98d0817ba92951b1672079c39c9540 Mon Sep 17 00:00:00 2001 From: remo5000 Date: Wed, 16 May 2018 18:23:01 +0800 Subject: [PATCH 043/129] Change es target to es2016 This was the target in sourceacademy-2 as well. Without this, there was a problem with casting an `IterableIterator` to `yield*`, that expected an array. --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 342f99e29e..a76d54832a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "outDir": "build/dist", "module": "esnext", - "target": "es5", + "target": "es2016", "lib": ["es6", "dom", "es2015"], "sourceMap": true, "allowJs": true, From 56f9981586ac5ebc0fa0db42546b7c3ab096fceb Mon Sep 17 00:00:00 2001 From: remo5000 Date: Wed, 16 May 2018 19:16:00 +0800 Subject: [PATCH 044/129] Revert "Remove unused type definitions" This reverts commit e18f2ae3e1bd86380cc12285652ac204d01e6f65. "Oops" - @ningyuansg --- src/slang/typings/acorn.d.ts | 53 ++++++++++++++++++++++++++++++++++ src/slang/typings/astring.d.ts | 1 + src/slang/typings/estree.d.ts | 8 +++++ 3 files changed, 62 insertions(+) create mode 100644 src/slang/typings/acorn.d.ts create mode 100644 src/slang/typings/astring.d.ts create mode 100644 src/slang/typings/estree.d.ts diff --git a/src/slang/typings/acorn.d.ts b/src/slang/typings/acorn.d.ts new file mode 100644 index 0000000000..cec251fd9e --- /dev/null +++ b/src/slang/typings/acorn.d.ts @@ -0,0 +1,53 @@ +declare module 'acorn/dist/walk' { + import * as es from 'estree' + + namespace AcornWalk { + export type SimpleWalker = (node: es.Node, state?: S) => void + export type SimpleWalkers = { [name: string]: SimpleWalker } + export type Walker = ( + node: T, + state: S, + callback: SimpleWalker + ) => void + export type Walkers = { [name: string]: Walker } + type NodeTest = (nodeType: string, node: es.Node) => boolean + + export const base: Walkers + + export function simple( + node: es.Node, + visitors: SimpleWalkers, + base?: SimpleWalkers, + state?: S + ): void + export function recursive( + node: es.Node, + state: S, + functions: Walkers + ): void + export function findNodeAt( + node: es.Node, + start: null | number, + end: null | number, + test: string | NodeTest, + base?: SimpleWalkers, + state?: S + ): void + export function findNodeAround( + node: es.Node, + pos: es.Position, + test: string | NodeTest, + base?: SimpleWalkers, + state?: S + ): void + export function findNodeAfter( + node: es.Node, + pos: es.Position, + test: string | NodeTest, + base?: SimpleWalkers, + state?: S + ): void + } + + export = AcornWalk +} diff --git a/src/slang/typings/astring.d.ts b/src/slang/typings/astring.d.ts new file mode 100644 index 0000000000..7f3a6fd2f2 --- /dev/null +++ b/src/slang/typings/astring.d.ts @@ -0,0 +1 @@ +declare module 'astring' diff --git a/src/slang/typings/estree.d.ts b/src/slang/typings/estree.d.ts new file mode 100644 index 0000000000..33c844f71f --- /dev/null +++ b/src/slang/typings/estree.d.ts @@ -0,0 +1,8 @@ +import estree from 'estree' + +declare module 'estree' { + interface BaseNode { + __id?: string + __call?: string + } +} From 430bb4a811e81d37ca4874aba3b0aebde97da918 Mon Sep 17 00:00:00 2001 From: remo5000 Date: Wed, 16 May 2018 20:12:02 +0800 Subject: [PATCH 045/129] Fix some slang errors --- package.json | 6 +++++- src/createStore.ts | 7 ++----- src/sagas/index.ts | 12 ++++++------ src/slang/__tests__/index.ts | 14 +++++++------- src/slang/createContext.ts | 4 ++-- src/slang/interop.ts | 11 +++++++---- src/slang/interpreter.ts | 1 - src/slang/rules/noDeclareReserved.ts | 6 ++++-- src/slang/types.ts | 2 +- src/slang/typings/acorn.d.ts | 6 +----- tsconfig.json | 2 +- yarn.lock | 18 ++++++++++++++++++ 12 files changed, 54 insertions(+), 35 deletions(-) diff --git a/package.json b/package.json index 6e5558febc..1590e9f409 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ }, "dependencies": { "@blueprintjs/core": "^2.1.1", + "@types/acorn": "^4.0.3", "astring": "^1.3.0", "common-tags": "^1.7.2", "normalize.css": "^8.0.0", @@ -29,17 +30,20 @@ "react-router": "^4.2.0", "react-router-dom": "^4.2.2", "react-router-redux": "^5.0.0-alpha.9", - "redux-saga": "^0.15.6", "react-transition-group": "^2.3.1", "redux": "^3.7.2", "redux-mock-store": "^1.5.1", + "redux-saga": "^0.15.6", "typesafe-actions": "^1.1.2", "utility-types": "^2.0.0" }, "devDependencies": { "@types/classnames": "^2.2.3", + "@types/common-tags": "^1.4.0", "@types/enzyme": "^3.1.9", "@types/enzyme-adapter-react-16": "^1.0.2", + "@types/estree": "^0.0.39", + "@types/invariant": "^2.2.29", "@types/jest": "^22.2.3", "@types/node": "^9.6.5", "@types/react": "^16.3.10", diff --git a/src/createStore.ts b/src/createStore.ts index 71b65a72c1..0eb787c5ae 100644 --- a/src/createStore.ts +++ b/src/createStore.ts @@ -23,17 +23,14 @@ function createStore(history: History): Store { if (typeof __REDUX_DEVTOOLS_EXTENSION_COMPOSE__ === 'function') { composeEnhancers = __REDUX_DEVTOOLS_EXTENSION_COMPOSE__ } - + const rootReducer = combineReducers({ ...reducers, router: routerReducer }) const enchancers = composeEnhancers(applyMiddleware(...middleware)) - return _createStore( - rootReducer, - enchancers - ) + return _createStore(rootReducer, enchancers) } export default createStore diff --git a/src/sagas/index.ts b/src/sagas/index.ts index d110ec53da..700ecc078e 100644 --- a/src/sagas/index.ts +++ b/src/sagas/index.ts @@ -1,7 +1,7 @@ -import { SagaIterator, delay } from 'redux-saga' +import { SagaIterator } from 'redux-saga' import { takeEvery, select, call, put, take, race } from 'redux-saga/effects' -import { showSuccessMessage, showWarningMessage } from '../notification' - +import { showWarningMessage } from '../notification' +import { IState } from '../reducers' // import { Shape } from '../shape' import { Context, createContext, runInContext, interrupt } from '../slang' @@ -10,7 +10,7 @@ import * as actions from '../actions' import * as actionTypes from '../actions/actionTypes' function* evalCode(code: string, context: Context) { - const {result, interrupted} = yield race({ + const { result, interrupted } = yield race({ result: call(runInContext, code, context), interrupted: take(actionTypes.INTERRUPT_EXECUTION) }) @@ -31,9 +31,9 @@ function* interpreterSaga(): SagaIterator { let context: Context yield takeEvery(actionTypes.EVAL_EDITOR, function*() { - const code = yield select((state: IState) => state.playground.editorValue) + const code: string = yield select((state: IState) => state.playground.editorValue) // context = createContext(library.week, library.externals) - context = createContext(3, "") + context = createContext() yield* evalCode(code, context) }) } diff --git a/src/slang/__tests__/index.ts b/src/slang/__tests__/index.ts index f2970cb2d4..6be5d2d17b 100644 --- a/src/slang/__tests__/index.ts +++ b/src/slang/__tests__/index.ts @@ -1,7 +1,7 @@ import { stripIndent } from 'common-tags' - import { mockContext } from '../../mocks/context' import { runInContext } from '../index' +import { Finished } from '../types' test('Empty code returns undefined', () => { const code = '' @@ -10,7 +10,7 @@ test('Empty code returns undefined', () => { return promise.then(obj => { expect(obj).toMatchSnapshot() expect(obj.status).toBe('finished') - expect(obj.value).toBe(undefined) + expect((obj as Finished).value).toBe(undefined) }) }) @@ -21,7 +21,7 @@ test('Single string self-evaluates to itself', () => { return promise.then(obj => { expect(obj).toMatchSnapshot() expect(obj.status).toBe('finished') - expect(obj.value).toBe('42') + expect((obj as Finished).value).toBe('42') }) }) @@ -32,7 +32,7 @@ test('Single number self-evaluates to itself', () => { return promise.then(obj => { expect(obj).toMatchSnapshot() expect(obj.status).toBe('finished') - expect(obj.value).toBe(42) + expect((obj as Finished).value).toBe(42) }) }) @@ -43,7 +43,7 @@ test('Single boolean self-evaluates to itself', () => { return promise.then(obj => { expect(obj).toMatchSnapshot() expect(obj.status).toBe('finished') - expect(obj.value).toBe(true) + expect((obj as Finished).value).toBe(true) }) }) @@ -54,7 +54,7 @@ test('Arrow function definition returns itself', () => { return promise.then(obj => { expect(obj).toMatchSnapshot() expect(obj.status).toBe('finished') - expect(obj.value).toMatchSnapshot() + expect((obj as Finished).value).toMatchSnapshot() }) }) @@ -68,6 +68,6 @@ test('Factorial arrow function', () => { return promise.then(obj => { expect(obj).toMatchSnapshot() expect(obj.status).toBe('finished') - expect(obj.value).toBe(120) + expect((obj as Finished).value).toBe(120) }) }) diff --git a/src/slang/createContext.ts b/src/slang/createContext.ts index d1b03859f0..7953fa3968 100644 --- a/src/slang/createContext.ts +++ b/src/slang/createContext.ts @@ -112,8 +112,8 @@ export const importBuiltins = (context: Context) => { defineSymbol(context, 'enum_list', list.enum_list) defineSymbol(context, 'list_ref', list.list_ref) defineSymbol(context, 'accumulate', list.accumulate) - if (window.ListVisualizer) { - defineSymbol(context, 'draw', window.ListVisualizer.draw) + if (window.hasOwnProperty('ListVisualizer')) { + defineSymbol(context, 'draw', (window as any).ListVisualizer.draw) } else { defineSymbol(context, 'draw', function() { throw new Error('List visualizer is not enabled') diff --git a/src/slang/interop.ts b/src/slang/interop.ts index 1ecd5e444b..8f754e8324 100644 --- a/src/slang/interop.ts +++ b/src/slang/interop.ts @@ -16,10 +16,13 @@ export const closureToJS = (value: Value, context: Context, klass: string) => { Object.defineProperty(DummyClass, 'name', { value: klass }) - DummyClass.Inherits = function(Parent: Value) { - DummyClass.prototype = Object.create(Parent.prototype) - DummyClass.prototype.constructor = DummyClass - } + Object.setPrototypeOf(DummyClass, () => {}) + Object.defineProperty(DummyClass, 'Inherits', { + value: (Parent: Value) => { + DummyClass.prototype = Object.create(Parent.prototype) + DummyClass.prototype.constructor = DummyClass + } + }) DummyClass.call = function(thisArg: Value, ...args: Value[]) { return DummyClass.apply(thisArg, args) } diff --git a/src/slang/interpreter.ts b/src/slang/interpreter.ts index fc7d91dc55..fd2465155f 100644 --- a/src/slang/interpreter.ts +++ b/src/slang/interpreter.ts @@ -242,7 +242,6 @@ export const evaluators: { [nodeType: string]: Evaluator } = { }, VariableDeclaration: function*(node: es.VariableDeclaration, context: Context) { const declaration = node.declarations[0] - const kind = node.kind const id = declaration.id as es.Identifier const value = yield* evaluate(declaration.init!, context) defineVariable(context, id.name, value) diff --git a/src/slang/rules/noDeclareReserved.ts b/src/slang/rules/noDeclareReserved.ts index 8ca5f10b58..bc51b7351e 100644 --- a/src/slang/rules/noDeclareReserved.ts +++ b/src/slang/rules/noDeclareReserved.ts @@ -61,7 +61,9 @@ export class noDeclareReservedError implements SourceError { } explain() { - return `Reserved word '${this.node.declarations[0].id.name}'` + ' is not allowed as a name' + return ( + `Reserved word '${(this.node.declarations[0].id as any).name}'` + ' is not allowed as a name' + ) } elaborate() { @@ -74,7 +76,7 @@ const noDeclareReserved: Rule = { checkers: { VariableDeclaration(node: es.VariableDeclaration) { - if (reservedNames.includes(node.declarations[0].id.name)) { + if (reservedNames.includes((node.declarations[0].id as any).name)) { return [new noDeclareReservedError(node)] } else { return [] diff --git a/src/slang/types.ts b/src/slang/types.ts index de5aa5a0bd..6b053f0c10 100644 --- a/src/slang/types.ts +++ b/src/slang/types.ts @@ -172,7 +172,7 @@ type Error = { status: 'error' } -type Finished = { +export type Finished = { status: 'finished' value: Value } diff --git a/src/slang/typings/acorn.d.ts b/src/slang/typings/acorn.d.ts index cec251fd9e..3e3e948ff6 100644 --- a/src/slang/typings/acorn.d.ts +++ b/src/slang/typings/acorn.d.ts @@ -20,11 +20,7 @@ declare module 'acorn/dist/walk' { base?: SimpleWalkers, state?: S ): void - export function recursive( - node: es.Node, - state: S, - functions: Walkers - ): void + export function recursive(node: es.Node, state: S, functions: Walkers): void export function findNodeAt( node: es.Node, start: null | number, diff --git a/tsconfig.json b/tsconfig.json index a76d54832a..aab220573a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ "outDir": "build/dist", "module": "esnext", "target": "es2016", - "lib": ["es6", "dom", "es2015"], + "lib": ["es6", "dom", "es2015", "es2017"], "sourceMap": true, "allowJs": true, "jsx": "react", diff --git a/yarn.lock b/yarn.lock index f39b6e9d82..f2be953ae2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -36,6 +36,12 @@ classnames "^2.2" tslib "^1.9.0" +"@types/acorn@^4.0.3": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@types/acorn/-/acorn-4.0.3.tgz#d1f3e738dde52536f9aad3d3380d14e448820afd" + dependencies: + "@types/estree" "*" + "@types/cheerio@*": version "0.22.7" resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.7.tgz#4a92eafedfb2b9f4437d3a4410006d81114c66ce" @@ -44,6 +50,10 @@ version "2.2.3" resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.3.tgz#3f0ff6873da793870e20a260cada55982f38a9e5" +"@types/common-tags@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@types/common-tags/-/common-tags-1.4.0.tgz#28c1be61e352dde38936018984e2885caef087c1" + "@types/dom4@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@types/dom4/-/dom4-2.0.0.tgz#00dc42fed6b36a7a6dabb8f7a9c9e678ee644e05" @@ -68,10 +78,18 @@ "@types/cheerio" "*" "@types/react" "*" +"@types/estree@*", "@types/estree@^0.0.39": + version "0.0.39" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" + "@types/history@*": version "4.6.2" resolved "https://registry.yarnpkg.com/@types/history/-/history-4.6.2.tgz#12cfaba693ba20f114ed5765467ff25fdf67ddb0" +"@types/invariant@^2.2.29": + version "2.2.29" + resolved "https://registry.yarnpkg.com/@types/invariant/-/invariant-2.2.29.tgz#aa845204cd0a289f65d47e0de63a6a815e30cc66" + "@types/jest@^22.2.3": version "22.2.3" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-22.2.3.tgz#0157c0316dc3722c43a7b71de3fdf3acbccef10d" From 7ac707cd6b8db0b303de58206d0efacc089e4947 Mon Sep 17 00:00:00 2001 From: remo5000 Date: Wed, 16 May 2018 23:38:31 +0800 Subject: [PATCH 046/129] Make 16/05 code review changes Changes not made: - Change of `Playground` component to an `SFC` --- src/actions/playground.ts | 7 +++---- src/components/Application.tsx | 2 +- src/components/Playground.tsx | 4 ++-- src/components/__tests__/Playground.tsx | 2 +- src/containers/PlaygroundContainer.ts | 4 ++-- src/reducers/playground.ts | 6 +----- 6 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/actions/playground.ts b/src/actions/playground.ts index 97d541f4bd..4f3153f54c 100644 --- a/src/actions/playground.ts +++ b/src/actions/playground.ts @@ -4,7 +4,7 @@ import { Action, ActionCreator } from 'redux' * The `type` attribute for an `Action` which updates the `IPlaygroundState` * `editorValue` */ -export const UPDATE_EDITOR_VALUE: string = 'UPDATE_EDITOR_VALUE' +export const UPDATE_EDITOR_VALUE = 'UPDATE_EDITOR_VALUE' /** * Represents an `Action` which updates the `editorValue` of a @@ -13,8 +13,7 @@ export const UPDATE_EDITOR_VALUE: string = 'UPDATE_EDITOR_VALUE' * @property newEditorValue - The new string value for `editorValue` */ export interface IUpdateEditorValue extends Action { - type: string - newEditorValue: string + payload: string } /** @@ -23,5 +22,5 @@ export interface IUpdateEditorValue extends Action { */ export const updateEditorValue: ActionCreator = (newEditorValue: string) => ({ type: UPDATE_EDITOR_VALUE, - newEditorValue + payload: newEditorValue }) diff --git a/src/components/Application.tsx b/src/components/Application.tsx index 9ae4625916..0c670f16ce 100644 --- a/src/components/Application.tsx +++ b/src/components/Application.tsx @@ -11,7 +11,7 @@ export interface IApplicationProps extends RouteComponentProps<{}> { title: string } -const Application: React.SFC = (props: IApplicationProps) => { +const Application: React.SFC = (props) => { const redirectToDashboard = () => return ( diff --git a/src/components/Playground.tsx b/src/components/Playground.tsx index d0f6b332e1..f5215b2501 100644 --- a/src/components/Playground.tsx +++ b/src/components/Playground.tsx @@ -11,7 +11,7 @@ import 'brace/theme/github' */ export interface IPlaygroundProps { editorValue: string - updateCode: (newCode: string) => void + handleEditorChange: (newCode: string) => void } /** @@ -28,7 +28,7 @@ export default class Playground extends React.Component { mode="javascript" theme="github" value={this.props.editorValue} - onChange={this.props.updateCode} + onChange={this.props.handleEditorChange} />
) diff --git a/src/components/__tests__/Playground.tsx b/src/components/__tests__/Playground.tsx index 865082b051..4c05c820ba 100644 --- a/src/components/__tests__/Playground.tsx +++ b/src/components/__tests__/Playground.tsx @@ -8,7 +8,7 @@ import { IPlaygroundProps as PlaygroundProps } from '../Playground' test('Playground renders correctly', () => { const props: PlaygroundProps = { editorValue: '', - updateCode: (newCode: string) => { + handleEditorChange: (newCode: string) => { return } } diff --git a/src/containers/PlaygroundContainer.ts b/src/containers/PlaygroundContainer.ts index e4313444ad..07dbfaf9e8 100644 --- a/src/containers/PlaygroundContainer.ts +++ b/src/containers/PlaygroundContainer.ts @@ -9,7 +9,7 @@ import { import { IState } from '../reducers' type StateProps = Pick -type DispatchProps = Pick +type DispatchProps = Pick /** Provides the editorValue of the `IPlaygroundState` of the `IState` as a * `StateProps` to the Playground component @@ -26,7 +26,7 @@ const mapStateToProps: MapStateToProps = state => { */ const mapDispatchToProps: MapDispatchToProps = (dispatch: Dispatch) => { return { - updateCode: (newCode: string) => { + handleEditorChange: (newCode: string) => { dispatch(updateEditorValue(newCode)) } } diff --git a/src/reducers/playground.ts b/src/reducers/playground.ts index 59dbed520d..4fe3f957c4 100644 --- a/src/reducers/playground.ts +++ b/src/reducers/playground.ts @@ -1,10 +1,6 @@ import { Action, Reducer } from 'redux' import { IUpdateEditorValue, UPDATE_EDITOR_VALUE } from '../actions/playground' -/** - * A state for the playground container - * @property editorValue - The string content of the react-ace editor - */ export interface IPlaygroundState { editorValue: string } @@ -26,7 +22,7 @@ export const reducer: Reducer = (state = defaultState, action: case UPDATE_EDITOR_VALUE: return { ...state, - editorValue: (action as IUpdateEditorValue).newEditorValue + editorValue: (action as IUpdateEditorValue).payload } default: return state From b2a3d6018f017d88e63ebec4960065f5b5e42835 Mon Sep 17 00:00:00 2001 From: ning Date: Thu, 17 May 2018 10:15:45 +0800 Subject: [PATCH 047/129] Fix test snapshot (name of onChange for ReactAce) Due to 7ac707c. --- src/components/__tests__/__snapshots__/Playground.tsx.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/__tests__/__snapshots__/Playground.tsx.snap b/src/components/__tests__/__snapshots__/Playground.tsx.snap index 1ea9aabd01..9950f617c9 100644 --- a/src/components/__tests__/__snapshots__/Playground.tsx.snap +++ b/src/components/__tests__/__snapshots__/Playground.tsx.snap @@ -5,6 +5,6 @@ exports[`Playground renders correctly 1`] = `

Playground

- +
" `; From caf551b149f2f508df049a078abb9771f8f07e97 Mon Sep 17 00:00:00 2001 From: ning Date: Thu, 17 May 2018 11:07:28 +0800 Subject: [PATCH 048/129] Revert Playground as SFC, make Editor container --- src/components/Application.tsx | 2 +- src/components/Playground.tsx | 37 +++++++++++++------------ src/components/__tests__/Playground.tsx | 11 ++------ src/containers/PlaygroundContainer.ts | 16 +++++------ 4 files changed, 30 insertions(+), 36 deletions(-) diff --git a/src/components/Application.tsx b/src/components/Application.tsx index 0c670f16ce..fe9801c29a 100644 --- a/src/components/Application.tsx +++ b/src/components/Application.tsx @@ -11,7 +11,7 @@ export interface IApplicationProps extends RouteComponentProps<{}> { title: string } -const Application: React.SFC = (props) => { +const Application: React.SFC = props => { const redirectToDashboard = () => return ( diff --git a/src/components/Playground.tsx b/src/components/Playground.tsx index f5215b2501..61cdffb39e 100644 --- a/src/components/Playground.tsx +++ b/src/components/Playground.tsx @@ -1,36 +1,39 @@ import * as React from 'react' import AceEditor from 'react-ace' +import { EditorContainer } from '../containers/PlaygroundContainer' import 'brace/mode/javascript' import 'brace/theme/github' /** - * A prop that is passed to the Playground * @property editorValue - The string content of the react-ace editor * @property updateCode - A callback function for the react-ace editor's `onChange` */ -export interface IPlaygroundProps { +export interface IEditorProps { editorValue: string handleEditorChange: (newCode: string) => void } -/** - * A component representing the Playground - */ -export default class Playground extends React.Component { +export const Playground: React.SFC<{}> = ()=> { + return ( +
+

Playground

+ +
+ ) +} + +export class Editor extends React.Component { public render() { return ( -
-

Playground

- -
+ ) } } diff --git a/src/components/__tests__/Playground.tsx b/src/components/__tests__/Playground.tsx index 4c05c820ba..0468cda30f 100644 --- a/src/components/__tests__/Playground.tsx +++ b/src/components/__tests__/Playground.tsx @@ -2,17 +2,10 @@ import * as React from 'react' import { shallow } from 'enzyme' -import Playground from '../Playground' -import { IPlaygroundProps as PlaygroundProps } from '../Playground' +import { Playground } from '../Playground' test('Playground renders correctly', () => { - const props: PlaygroundProps = { - editorValue: '', - handleEditorChange: (newCode: string) => { - return - } - } - const app = + const app = const tree = shallow(app) expect(tree.debug()).toMatchSnapshot() }) diff --git a/src/containers/PlaygroundContainer.ts b/src/containers/PlaygroundContainer.ts index 07dbfaf9e8..390ff9041a 100644 --- a/src/containers/PlaygroundContainer.ts +++ b/src/containers/PlaygroundContainer.ts @@ -2,19 +2,16 @@ import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux' import { Dispatch } from 'redux' import { updateEditorValue } from '../actions/playground' -import { - default as PlaygroundComponent, - IPlaygroundProps as PlaygroundProps -} from '../components/Playground' +import { Editor, IEditorProps, Playground } from '../components/Playground' import { IState } from '../reducers' -type StateProps = Pick -type DispatchProps = Pick +type StateProps = Pick +type DispatchProps = Pick /** Provides the editorValue of the `IPlaygroundState` of the `IState` as a * `StateProps` to the Playground component */ -const mapStateToProps: MapStateToProps = state => { +const mapEditorStateToProps: MapStateToProps = state => { return { editorValue: state.playground.editorValue } @@ -24,7 +21,7 @@ const mapStateToProps: MapStateToProps = state => { * `updateEditorValue` with `newCode`, the updated contents of the react-ace * editor. */ -const mapDispatchToProps: MapDispatchToProps = (dispatch: Dispatch) => { +const mapEditorDispatchToProps: MapDispatchToProps = (dispatch: Dispatch) => { return { handleEditorChange: (newCode: string) => { dispatch(updateEditorValue(newCode)) @@ -32,4 +29,5 @@ const mapDispatchToProps: MapDispatchToProps = (dispatch: Dis } } -export default connect(mapStateToProps, mapDispatchToProps)(PlaygroundComponent) +export default connect()(Playground) +export const EditorContainer = connect(mapEditorStateToProps, mapEditorDispatchToProps)(Editor) From 51ea83d76d0fe16eca9504e89d78c47cea8bf511 Mon Sep 17 00:00:00 2001 From: remo5000 Date: Thu, 17 May 2018 11:37:47 +0800 Subject: [PATCH 049/129] Make Playground stateless This is done by abstracting out the ace editor into its own component , `Editor`, and its own container to handle redux calls, `EditorContainer`. --- src/components/Application.tsx | 5 +-- src/components/Editor.tsx | 29 +++++++++++++++++ src/components/Playground.tsx | 32 ++----------------- src/components/__tests__/Playground.tsx | 2 +- ...ygroundContainer.ts => EditorContainer.ts} | 9 +++--- 5 files changed, 40 insertions(+), 37 deletions(-) create mode 100644 src/components/Editor.tsx rename src/containers/{PlaygroundContainer.ts => EditorContainer.ts} (66%) diff --git a/src/components/Application.tsx b/src/components/Application.tsx index fe9801c29a..add397856e 100644 --- a/src/components/Application.tsx +++ b/src/components/Application.tsx @@ -3,9 +3,10 @@ import * as React from 'react' import { Redirect, Route, RouteComponentProps, Switch } from 'react-router' import DashboardContainer from '../containers/DashboardContainer' -import PlaygroundContainer from '../containers/PlaygroundContainer' + import NavigationBar from './NavigationBar' import NotFound from './NotFound' +import Playground from './Playground' export interface IApplicationProps extends RouteComponentProps<{}> { title: string @@ -20,7 +21,7 @@ const Application: React.SFC = props => {
- + diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx new file mode 100644 index 0000000000..36bbd344d8 --- /dev/null +++ b/src/components/Editor.tsx @@ -0,0 +1,29 @@ +import * as React from 'react' +import AceEditor from 'react-ace' + +import 'brace/mode/javascript' +import 'brace/theme/github' + +/** + * @property editorValue - The string content of the react-ace editor + * @property updateCode - A callback function for the react-ace editor's `onChange` + */ +export interface IEditorProps { + editorValue: string + handleEditorChange: (newCode: string) => void +} + +export class Editor extends React.Component { + public render() { + return ( + + ) + } +} diff --git a/src/components/Playground.tsx b/src/components/Playground.tsx index 61cdffb39e..834b0eed58 100644 --- a/src/components/Playground.tsx +++ b/src/components/Playground.tsx @@ -1,20 +1,7 @@ import * as React from 'react' -import AceEditor from 'react-ace' -import { EditorContainer } from '../containers/PlaygroundContainer' +import EditorContainer from '../containers/EditorContainer' -import 'brace/mode/javascript' -import 'brace/theme/github' - -/** - * @property editorValue - The string content of the react-ace editor - * @property updateCode - A callback function for the react-ace editor's `onChange` - */ -export interface IEditorProps { - editorValue: string - handleEditorChange: (newCode: string) => void -} - -export const Playground: React.SFC<{}> = ()=> { +const Playground: React.SFC<{}> = ()=> { return (

Playground

@@ -23,17 +10,4 @@ export const Playground: React.SFC<{}> = ()=> { ) } -export class Editor extends React.Component { - public render() { - return ( - - ) - } -} +export default Playground diff --git a/src/components/__tests__/Playground.tsx b/src/components/__tests__/Playground.tsx index 0468cda30f..2950cca958 100644 --- a/src/components/__tests__/Playground.tsx +++ b/src/components/__tests__/Playground.tsx @@ -2,7 +2,7 @@ import * as React from 'react' import { shallow } from 'enzyme' -import { Playground } from '../Playground' +import Playground from '../Playground' test('Playground renders correctly', () => { const app = diff --git a/src/containers/PlaygroundContainer.ts b/src/containers/EditorContainer.ts similarity index 66% rename from src/containers/PlaygroundContainer.ts rename to src/containers/EditorContainer.ts index 390ff9041a..0771f4d928 100644 --- a/src/containers/PlaygroundContainer.ts +++ b/src/containers/EditorContainer.ts @@ -2,7 +2,7 @@ import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux' import { Dispatch } from 'redux' import { updateEditorValue } from '../actions/playground' -import { Editor, IEditorProps, Playground } from '../components/Playground' +import { Editor, IEditorProps } from '../components/Editor' import { IState } from '../reducers' type StateProps = Pick @@ -11,7 +11,7 @@ type DispatchProps = Pick /** Provides the editorValue of the `IPlaygroundState` of the `IState` as a * `StateProps` to the Playground component */ -const mapEditorStateToProps: MapStateToProps = state => { +const mapStateToProps: MapStateToProps = state => { return { editorValue: state.playground.editorValue } @@ -21,7 +21,7 @@ const mapEditorStateToProps: MapStateToProps = state => * `updateEditorValue` with `newCode`, the updated contents of the react-ace * editor. */ -const mapEditorDispatchToProps: MapDispatchToProps = (dispatch: Dispatch) => { +const mapDispatchToProps: MapDispatchToProps = (dispatch: Dispatch) => { return { handleEditorChange: (newCode: string) => { dispatch(updateEditorValue(newCode)) @@ -29,5 +29,4 @@ const mapEditorDispatchToProps: MapDispatchToProps = (dispatc } } -export default connect()(Playground) -export const EditorContainer = connect(mapEditorStateToProps, mapEditorDispatchToProps)(Editor) +export default connect(mapStateToProps, mapDispatchToProps)(Editor) From 9758bb7c952b6bedb4d835513e4cab8f2693a525 Mon Sep 17 00:00:00 2001 From: remo5000 Date: Thu, 17 May 2018 11:53:44 +0800 Subject: [PATCH 050/129] Add test and fix export type Export of `Editor` changed to a default export, like the other components. A test has been added for the `Editor` component. Snapshot changes pending. --- src/components/Editor.tsx | 4 +++- src/components/__tests__/Editor.tsx | 15 +++++++++++++++ .../__tests__/__snapshots__/Editor.tsx.snap | 3 +++ src/containers/EditorContainer.ts | 2 +- 4 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 src/components/__tests__/Editor.tsx create mode 100644 src/components/__tests__/__snapshots__/Editor.tsx.snap diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx index 36bbd344d8..ee55bf0365 100644 --- a/src/components/Editor.tsx +++ b/src/components/Editor.tsx @@ -13,7 +13,7 @@ export interface IEditorProps { handleEditorChange: (newCode: string) => void } -export class Editor extends React.Component { +class Editor extends React.Component { public render() { return ( { ) } } + +export default Editor diff --git a/src/components/__tests__/Editor.tsx b/src/components/__tests__/Editor.tsx new file mode 100644 index 0000000000..ab5f918122 --- /dev/null +++ b/src/components/__tests__/Editor.tsx @@ -0,0 +1,15 @@ +import * as React from 'react' + +import { shallow } from 'enzyme' + +import Editor, { IEditorProps } from '../Editor' + +test('Editor renders correctly', () => { + const props: IEditorProps = { + editorValue: '' + handleEditorChange: (newCode) => + } + const app = + const tree = shallow(app) + expect(tree.debug()).toMatchSnapshot() +}) diff --git a/src/components/__tests__/__snapshots__/Editor.tsx.snap b/src/components/__tests__/__snapshots__/Editor.tsx.snap new file mode 100644 index 0000000000..931defd35f --- /dev/null +++ b/src/components/__tests__/__snapshots__/Editor.tsx.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Editor renders correctly 1`] = `""`; diff --git a/src/containers/EditorContainer.ts b/src/containers/EditorContainer.ts index 0771f4d928..9fd9c75d4b 100644 --- a/src/containers/EditorContainer.ts +++ b/src/containers/EditorContainer.ts @@ -2,7 +2,7 @@ import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux' import { Dispatch } from 'redux' import { updateEditorValue } from '../actions/playground' -import { Editor, IEditorProps } from '../components/Editor' +import Editor, { IEditorProps } from '../components/Editor' import { IState } from '../reducers' type StateProps = Pick From 04385492dc21d9d1ff8479d4b192bc7e6edbc30d Mon Sep 17 00:00:00 2001 From: ning Date: Thu, 17 May 2018 11:58:10 +0800 Subject: [PATCH 051/129] Fix type errors according to tslint --- src/createStore.ts | 3 +- src/mocks/context.ts | 2 +- src/notification.ts | 2 +- src/sagas/index.ts | 4 +- src/slang/cfg.ts | 10 ++--- src/slang/createContext.ts | 8 ++-- src/slang/index.ts | 12 +++--- src/slang/interop.ts | 6 +-- src/slang/interpreter-errors.ts | 75 ++++++++++++++++----------------- src/slang/interpreter.ts | 8 ++-- src/slang/stdlib/misc.ts | 7 ++- src/slang/typings/estree.d.ts | 2 +- src/slang/utils/node.ts | 2 +- 13 files changed, 72 insertions(+), 69 deletions(-) diff --git a/src/createStore.ts b/src/createStore.ts index 0eb787c5ae..828929e726 100644 --- a/src/createStore.ts +++ b/src/createStore.ts @@ -8,9 +8,10 @@ import { Store, StoreEnhancer } from 'redux' +import reducers, { IState } from './reducers' + import createSagaMiddleware from 'redux-saga' import mainSaga from './sagas' -import reducers, { IState } from './reducers' declare var __REDUX_DEVTOOLS_EXTENSION_COMPOSE__: () => StoreEnhancer diff --git a/src/mocks/context.ts b/src/mocks/context.ts index b0983f4c62..2b8914c2e6 100644 --- a/src/mocks/context.ts +++ b/src/mocks/context.ts @@ -1,5 +1,5 @@ -import { Context } from '../slang/types' import createContext from '../slang/createContext' +import { Context } from '../slang/types' export function mockContext(): Context { return createContext() diff --git a/src/notification.ts b/src/notification.ts index 58e3a7afa4..74fa1170fe 100644 --- a/src/notification.ts +++ b/src/notification.ts @@ -1,4 +1,4 @@ -import { Toaster, Position, Intent } from '@blueprintjs/core' +import { Intent, Position, Toaster } from '@blueprintjs/core' const notification = Toaster.create({ position: Position.TOP diff --git a/src/sagas/index.ts b/src/sagas/index.ts index 700ecc078e..cdaf9a6eda 100644 --- a/src/sagas/index.ts +++ b/src/sagas/index.ts @@ -1,10 +1,10 @@ import { SagaIterator } from 'redux-saga' -import { takeEvery, select, call, put, take, race } from 'redux-saga/effects' +import { call, put, race, select, take, takeEvery } from 'redux-saga/effects' import { showWarningMessage } from '../notification' import { IState } from '../reducers' // import { Shape } from '../shape' -import { Context, createContext, runInContext, interrupt } from '../slang' +import { Context, createContext, interrupt, runInContext } from '../slang' import * as actions from '../actions' import * as actionTypes from '../actions/actionTypes' diff --git a/src/slang/cfg.ts b/src/slang/cfg.ts index 8741451b2e..9e30e8ce40 100644 --- a/src/slang/cfg.ts +++ b/src/slang/cfg.ts @@ -1,10 +1,10 @@ -import * as invariant from 'invariant' +import { base, recursive, Walker, Walkers } from 'acorn/dist/walk' import * as es from 'estree' -import { recursive, Walkers, Walker, base } from 'acorn/dist/walk' +import * as invariant from 'invariant' -import { composeWalker } from './utils/node' -import { Context, CFG } from './types' import { Types } from './constants' +import { CFG, Context } from './types' +import { composeWalker } from './utils/node' const freshLambda = (() => { let id = 0 @@ -16,7 +16,7 @@ const freshLambda = (() => { const walkers: Walkers<{}> = {} -let nodeStack: Array = [] +let nodeStack: es.Node[] = [] let scopeQueue: CFG.Scope[] = [] let edgeLabel: CFG.EdgeLabel = 'next' diff --git a/src/slang/createContext.ts b/src/slang/createContext.ts index 7953fa3968..45921567b0 100644 --- a/src/slang/createContext.ts +++ b/src/slang/createContext.ts @@ -1,6 +1,6 @@ -import { Context, Value } from './types' import * as list from './stdlib/list' import * as misc from './stdlib/misc' +import { Context, Value } from './types' const GLOBAL = typeof window === 'undefined' ? global : window @@ -75,8 +75,8 @@ export const importBuiltins = (context: Context) => { defineSymbol(context, 'timed', misc.timed) // Define all Math libraries - let objs = Object.getOwnPropertyNames(Math) - for (let i in objs) { + const objs = Object.getOwnPropertyNames(Math) + for (const i in objs) { if (objs.hasOwnProperty(i)) { const val = objs[i] if (typeof Math[val] === 'function') { @@ -115,7 +115,7 @@ export const importBuiltins = (context: Context) => { if (window.hasOwnProperty('ListVisualizer')) { defineSymbol(context, 'draw', (window as any).ListVisualizer.draw) } else { - defineSymbol(context, 'draw', function() { + defineSymbol(context, 'draw', () => { throw new Error('List visualizer is not enabled') }) } diff --git a/src/slang/index.ts b/src/slang/index.ts index 9ad263ea4f..6becac765d 100644 --- a/src/slang/index.ts +++ b/src/slang/index.ts @@ -1,17 +1,17 @@ -import { Context, Scheduler, SourceError, Result } from './types' import createContext from './createContext' +import { toString } from './interop' import { evaluate } from './interpreter' import { InterruptedError } from './interpreter-errors' import { parse } from './parser' import { AsyncScheduler, PreemptiveScheduler } from './schedulers' -import { toString } from './interop' +import { Context, Result, Scheduler, SourceError } from './types' -export type Options = { +export interface IOptions { scheduler: 'preemptive' | 'async' steps: number } -const DEFAULT_OPTIONS: Options = { +const DEFAULT_OPTIONS: IOptions = { scheduler: 'async', steps: 1000 } @@ -23,9 +23,9 @@ export class ParseError { export function runInContext( code: string, context: Context, - options: Partial = {} + options: Partial = {} ): Promise { - const theOptions: Options = { ...options, ...DEFAULT_OPTIONS } + const theOptions: IOptions = { ...options, ...DEFAULT_OPTIONS } context.errors = [] const program = parse(code, context) if (program) { diff --git a/src/slang/interop.ts b/src/slang/interop.ts index 8f754e8324..082b8a6985 100644 --- a/src/slang/interop.ts +++ b/src/slang/interop.ts @@ -1,7 +1,7 @@ import { generate } from 'astring' -import { Closure, Context, Value } from './types' -import { apply } from './interpreter' import { MAX_LIST_DISPLAY_LENGTH } from './constants' +import { apply } from './interpreter' +import { Closure, Context, Value } from './types' export const closureToJS = (value: Value, context: Context, klass: string) => { function DummyClass(this: Value) { @@ -23,7 +23,7 @@ export const closureToJS = (value: Value, context: Context, klass: string) => { DummyClass.prototype.constructor = DummyClass } }) - DummyClass.call = function(thisArg: Value, ...args: Value[]) { + DummyClass.call = (thisArg: Value, ...args: Value[]) => { return DummyClass.apply(thisArg, args) } return DummyClass diff --git a/src/slang/interpreter-errors.ts b/src/slang/interpreter-errors.ts index ab98438a0b..9a76f1684c 100644 --- a/src/slang/interpreter-errors.ts +++ b/src/slang/interpreter-errors.ts @@ -1,68 +1,67 @@ -import * as es from 'estree' import { generate } from 'astring' -import { Value, SourceError, ErrorType, ErrorSeverity } from './types' -import { toString } from './interop' +import * as es from 'estree' import { UNKNOWN_LOCATION } from './constants' +import { toString } from './interop' +import { ErrorSeverity, ErrorType, SourceError, Value } from './types' export class InterruptedError implements SourceError { - type = ErrorType.RUNTIME - severity = ErrorSeverity.ERROR - location: es.SourceLocation + public type = ErrorType.RUNTIME + public severity = ErrorSeverity.ERROR + public location: es.SourceLocation constructor(node: es.Node) { this.location = node.loc! } - explain() { + public explain() { return 'Execution aborted by user.' } - elaborate() { + public elaborate() { return 'TODO' } } export class ExceptionError implements SourceError { - type = ErrorType.RUNTIME - severity = ErrorSeverity.ERROR + public type = ErrorType.RUNTIME + public severity = ErrorSeverity.ERROR constructor(public error: Error, public location: es.SourceLocation) {} - explain() { + public explain() { return this.error.toString() } - elaborate() { + public elaborate() { return 'TODO' } } export class MaximumStackLimitExceeded implements SourceError { - type = ErrorType.RUNTIME - severity = ErrorSeverity.ERROR - - location: es.SourceLocation + public type = ErrorType.RUNTIME + public severity = ErrorSeverity.ERROR + public location: es.SourceLocation constructor(node: es.Node, private calls: es.CallExpression[]) { this.location = node ? node.loc! : UNKNOWN_LOCATION } - explain() { + public explain() { return ` Infinite recursion ${generate(this.calls[0])}..${generate(this.calls[1])}..${generate(this.calls[2])}.. ` } - elaborate() { + public elaborate() { return 'TODO' } } export class CallingNonFunctionValue implements SourceError { - type = ErrorType.RUNTIME - severity = ErrorSeverity.ERROR - location: es.SourceLocation + public type = ErrorType.RUNTIME + public severity = ErrorSeverity.ERROR + public location: es.SourceLocation constructor(private callee: Value, node?: es.Node) { if (node) { @@ -72,65 +71,65 @@ export class CallingNonFunctionValue implements SourceError { } } - explain() { + public explain() { return `Calling non-function value ${toString(this.callee)}` } - elaborate() { + public elaborate() { return 'TODO' } } export class UndefinedVariable implements SourceError { - type = ErrorType.RUNTIME - severity = ErrorSeverity.ERROR - location: es.SourceLocation + public type = ErrorType.RUNTIME + public severity = ErrorSeverity.ERROR + public location: es.SourceLocation constructor(public name: string, node: es.Node) { this.location = node.loc! } - explain() { + public explain() { return `Undefined Variable ${this.name}` } - elaborate() { + public elaborate() { return 'TODO' } } export class InvalidNumberOfArguments implements SourceError { - type = ErrorType.RUNTIME - severity = ErrorSeverity.ERROR - location: es.SourceLocation + public type = ErrorType.RUNTIME + public severity = ErrorSeverity.ERROR + public location: es.SourceLocation constructor(node: es.Node, private expected: number, private got: number) { this.location = node.loc! } - explain() { + public explain() { return `Expected ${this.expected} arguments, but got ${this.got}` } - elaborate() { + public elaborate() { return 'TODO' } } export class VariableRedeclaration implements SourceError { - type = ErrorType.RUNTIME - severity = ErrorSeverity.ERROR - location: es.SourceLocation + public type = ErrorType.RUNTIME + public severity = ErrorSeverity.ERROR + public location: es.SourceLocation constructor(node: es.Node, private name: string) { this.location = node.loc! } - explain() { + public explain() { return `Redeclaring variable ${this.name}` } - elaborate() { + public elaborate() { return 'TODO' } } diff --git a/src/slang/interpreter.ts b/src/slang/interpreter.ts index fd2465155f..6b67abf0c8 100644 --- a/src/slang/interpreter.ts +++ b/src/slang/interpreter.ts @@ -1,10 +1,10 @@ import * as es from 'estree' -import { ArrowClosure, Closure, Frame, Value, Context, SourceError, ErrorSeverity } from './types' +import * as constants from './constants' import { toJS } from './interop' +import * as errors from './interpreter-errors' +import { ArrowClosure, Closure, Context, ErrorSeverity, Frame, SourceError, Value } from './types' import { createNode } from './utils/node' -import * as constants from './constants' import * as rttc from './utils/rttc' -import * as errors from './interpreter-errors' class ReturnValue { constructor(public value: Value) {} @@ -125,7 +125,7 @@ export type Evaluator = (node: T, context: Context) => Iterab export const evaluators: { [nodeType: string]: Evaluator } = { /** Simple Values */ - Literal: function*(node: es.Literal, context: Context) { + Literal*(node: es.Literal, context: Context) { return node.value }, ThisExpression: function*(node: es.ThisExpression, context: Context) { diff --git a/src/slang/stdlib/misc.ts b/src/slang/stdlib/misc.ts index fa4593d826..5aad1e0068 100644 --- a/src/slang/stdlib/misc.ts +++ b/src/slang/stdlib/misc.ts @@ -3,6 +3,7 @@ import { toString } from '../interop' export function display(value: Value) { const output = toString(value) + /* TODO: implement display for the playground if (typeof window.__REDUX_STORE__ !== 'undefined') { window.__REDUX_STORE__.dispatch({ type: 'CREATE_INTERPRETER_OUTPUT', @@ -11,9 +12,11 @@ export function display(value: Value) { } else { // tslint:disable-next-line:no-console console.log(output) - } + } */ + // tslint:disable-next-line:no-console + console.log(output) } -window.display = display +// window.display = display display.__SOURCE__ = 'display(a)' export function error_message(value: Value) { diff --git a/src/slang/typings/estree.d.ts b/src/slang/typings/estree.d.ts index 33c844f71f..55ca251ea8 100644 --- a/src/slang/typings/estree.d.ts +++ b/src/slang/typings/estree.d.ts @@ -1,4 +1,4 @@ -import estree from 'estree' +import * as estree from 'estree' declare module 'estree' { interface BaseNode { diff --git a/src/slang/utils/node.ts b/src/slang/utils/node.ts index 72ab2ad20a..7305054dc4 100644 --- a/src/slang/utils/node.ts +++ b/src/slang/utils/node.ts @@ -84,7 +84,7 @@ const createLiteralNode = (value: {}): es.Node => { value, raw: value, __id: freshId() - } + } as es.SimpleLiteral } } From 6c4769574e622e5710cb5f574465f8927cd416f7 Mon Sep 17 00:00:00 2001 From: ning Date: Thu, 17 May 2018 12:03:45 +0800 Subject: [PATCH 052/129] Change object methods to use es6 shorthand --- src/slang/interpreter.ts | 52 ++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/slang/interpreter.ts b/src/slang/interpreter.ts index 6b67abf0c8..95882fa2ec 100644 --- a/src/slang/interpreter.ts +++ b/src/slang/interpreter.ts @@ -125,29 +125,29 @@ export type Evaluator = (node: T, context: Context) => Iterab export const evaluators: { [nodeType: string]: Evaluator } = { /** Simple Values */ - Literal*(node: es.Literal, context: Context) { + *Literal(node: es.Literal, context: Context) { return node.value }, - ThisExpression: function*(node: es.ThisExpression, context: Context) { + *ThisExpression(node: es.ThisExpression, context: Context) { return context.runtime.frames[0].thisContext }, - ArrayExpression: function*(node: es.ArrayExpression, context: Context) { + *ArrayExpression(node: es.ArrayExpression, context: Context) { const res = [] for (const n of node.elements) { res.push(yield* evaluate(n, context)) } return res }, - FunctionExpression: function*(node: es.FunctionExpression, context: Context) { + *FunctionExpression(node: es.FunctionExpression, context: Context) { return new Closure(node, currentFrame(context), context) }, - ArrowFunctionExpression: function*(node: es.Function, context: Context) { + *ArrowFunctionExpression(node: es.Function, context: Context) { return new ArrowClosure(node, currentFrame(context), context) }, - Identifier: function*(node: es.Identifier, context: Context) { + *Identifier(node: es.Identifier, context: Context) { return getVariable(context, node.name) }, - CallExpression: function*(node: es.CallExpression, context: Context) { + *CallExpression(node: es.CallExpression, context: Context) { const callee = yield* evaluate(node.callee, context) const args = yield* getArgs(context, node) let thisContext = undefined @@ -157,7 +157,7 @@ export const evaluators: { [nodeType: string]: Evaluator } = { const result = yield* apply(context, callee, args, node, thisContext) return result }, - NewExpression: function*(node: es.NewExpression, context: Context) { + *NewExpression(node: es.NewExpression, context: Context) { const callee = yield* evaluate(node.callee, context) const args = [] for (const arg of node.arguments) { @@ -173,7 +173,7 @@ export const evaluators: { [nodeType: string]: Evaluator } = { } return obj }, - UnaryExpression: function*(node: es.UnaryExpression, context: Context) { + *UnaryExpression(node: es.UnaryExpression, context: Context) { const value = yield* evaluate(node.argument, context) if (node.operator === '!') { return !value @@ -183,7 +183,7 @@ export const evaluators: { [nodeType: string]: Evaluator } = { return +value } }, - BinaryExpression: function*(node: es.BinaryExpression, context: Context) { + *BinaryExpression(node: es.BinaryExpression, context: Context) { const left = yield* evaluate(node.left, context) const right = yield* evaluate(node.right, context) @@ -229,10 +229,10 @@ export const evaluators: { [nodeType: string]: Evaluator } = { } return result }, - ConditionalExpression: function*(node: es.ConditionalExpression, context: Context) { + *ConditionalExpression(node: es.ConditionalExpression, context: Context) { return yield* this.IfStatement(node, context) }, - LogicalExpression: function*(node: es.LogicalExpression, context: Context) { + *LogicalExpression(node: es.LogicalExpression, context: Context) { const left = yield* evaluate(node.left, context) if ((node.operator === '&&' && left) || (node.operator === '||' && !left)) { return yield* evaluate(node.right, context) @@ -240,20 +240,20 @@ export const evaluators: { [nodeType: string]: Evaluator } = { return left } }, - VariableDeclaration: function*(node: es.VariableDeclaration, context: Context) { + *VariableDeclaration(node: es.VariableDeclaration, context: Context) { const declaration = node.declarations[0] const id = declaration.id as es.Identifier const value = yield* evaluate(declaration.init!, context) defineVariable(context, id.name, value) return undefined }, - ContinueStatement: function*(node: es.ContinueStatement, context: Context) { + *ContinueStatement(node: es.ContinueStatement, context: Context) { return new ContinueValue() }, - BreakStatement: function*(node: es.BreakStatement, context: Context) { + *BreakStatement(node: es.BreakStatement, context: Context) { return new BreakValue() }, - ForStatement: function*(node: es.ForStatement, context: Context) { + *ForStatement(node: es.ForStatement, context: Context) { if (node.init) { yield* evaluate(node.init, context) } @@ -281,7 +281,7 @@ export const evaluators: { [nodeType: string]: Evaluator } = { } return value }, - MemberExpression: function*(node: es.MemberExpression, context: Context) { + *MemberExpression(node: es.MemberExpression, context: Context) { let obj = yield* evaluate(node.object, context) if (obj instanceof Closure) { obj = obj.fun @@ -298,7 +298,7 @@ export const evaluators: { [nodeType: string]: Evaluator } = { } } }, - AssignmentExpression: function*(node: es.AssignmentExpression, context: Context) { + *AssignmentExpression(node: es.AssignmentExpression, context: Context) { if (node.left.type === 'MemberExpression') { const left = node.left let obj = yield* evaluate(left.object, context) @@ -318,14 +318,14 @@ export const evaluators: { [nodeType: string]: Evaluator } = { setVariable(context, id.name, value) return value }, - FunctionDeclaration: function*(node: es.FunctionDeclaration, context: Context) { + *FunctionDeclaration(node: es.FunctionDeclaration, context: Context) { const id = node.id as es.Identifier // tslint:disable-next-line:no-any const closure = new Closure(node as any, currentFrame(context), context) defineVariable(context, id.name, closure) return undefined }, - IfStatement: function*(node: es.IfStatement, context: Context) { + *IfStatement(node: es.IfStatement, context: Context) { const test = yield* evaluate(node.test, context) if (test) { return yield* evaluate(node.consequent, context) @@ -335,10 +335,10 @@ export const evaluators: { [nodeType: string]: Evaluator } = { return undefined } }, - ExpressionStatement: function*(node: es.ExpressionStatement, context: Context) { + *ExpressionStatement(node: es.ExpressionStatement, context: Context) { return yield* evaluate(node.expression, context) }, - ReturnStatement: function*(node: es.ReturnStatement, context: Context) { + *ReturnStatement(node: es.ReturnStatement, context: Context) { if (node.argument) { if (node.argument.type === 'CallExpression') { const callee = yield* evaluate(node.argument.callee, context) @@ -351,7 +351,7 @@ export const evaluators: { [nodeType: string]: Evaluator } = { return new ReturnValue(undefined) } }, - WhileStatement: function*(node: es.WhileStatement, context: Context) { + *WhileStatement(node: es.WhileStatement, context: Context) { let value: any // tslint:disable-line let test while ( @@ -367,7 +367,7 @@ export const evaluators: { [nodeType: string]: Evaluator } = { } return value }, - ObjectExpression: function*(node: es.ObjectExpression, context: Context) { + *ObjectExpression(node: es.ObjectExpression, context: Context) { const obj = {} for (let prop of node.properties) { let key @@ -380,7 +380,7 @@ export const evaluators: { [nodeType: string]: Evaluator } = { } return obj }, - BlockStatement: function*(node: es.BlockStatement, context: Context) { + *BlockStatement(node: es.BlockStatement, context: Context) { let result: Value for (const statement of node.body) { result = yield* evaluate(statement, context) @@ -394,7 +394,7 @@ export const evaluators: { [nodeType: string]: Evaluator } = { } return result }, - Program: function*(node: es.BlockStatement, context: Context) { + *Program(node: es.BlockStatement, context: Context) { let result: Value for (const statement of node.body) { result = yield* evaluate(statement, context) From 034951520a02833b99c53a5181b407a1df24ea87 Mon Sep 17 00:00:00 2001 From: ning Date: Thu, 17 May 2018 12:14:11 +0800 Subject: [PATCH 053/129] Fix more linting errors for interpreter.ts --- src/slang/interpreter.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/slang/interpreter.ts b/src/slang/interpreter.ts index 95882fa2ec..71c9bce858 100644 --- a/src/slang/interpreter.ts +++ b/src/slang/interpreter.ts @@ -150,7 +150,7 @@ export const evaluators: { [nodeType: string]: Evaluator } = { *CallExpression(node: es.CallExpression, context: Context) { const callee = yield* evaluate(node.callee, context) const args = yield* getArgs(context, node) - let thisContext = undefined + let thisContext; if (node.callee.type === 'MemberExpression') { thisContext = yield* evaluate(node.callee.object, context) } @@ -301,7 +301,7 @@ export const evaluators: { [nodeType: string]: Evaluator } = { *AssignmentExpression(node: es.AssignmentExpression, context: Context) { if (node.left.type === 'MemberExpression') { const left = node.left - let obj = yield* evaluate(left.object, context) + const obj = yield* evaluate(left.object, context) let prop if (left.computed) { prop = yield* evaluate(left.property, context) @@ -355,6 +355,7 @@ export const evaluators: { [nodeType: string]: Evaluator } = { let value: any // tslint:disable-line let test while ( + // tslint:disable-next-line (test = yield* evaluate(node.test, context)) && !(value instanceof ReturnValue) && !(value instanceof BreakValue) && @@ -369,7 +370,7 @@ export const evaluators: { [nodeType: string]: Evaluator } = { }, *ObjectExpression(node: es.ObjectExpression, context: Context) { const obj = {} - for (let prop of node.properties) { + for (const prop of node.properties) { let key if (prop.key.type === 'Identifier') { key = prop.key.name From 99ad59eae123c8b1defb32ddc6f6c012b0e0a2ad Mon Sep 17 00:00:00 2001 From: remo5000 Date: Thu, 17 May 2018 12:14:38 +0800 Subject: [PATCH 054/129] Disable `no-empty` rule. Rationale: A no-op function is much easier to see as `() => {}` as compared to `() => undefined` or even `() =>`. In addition, this syntax is used by parts of the interpreter as well. --- tslint.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tslint.json b/tslint.json index 38976e529b..ba3e3e3263 100644 --- a/tslint.json +++ b/tslint.json @@ -1,7 +1,8 @@ { "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"], "rules": { - "object-literal-sort-keys": false + "object-literal-sort-keys": false, + "no-empty": false }, "linterOptions": { "exclude": [ From 04bdf0f418de16b44c064cb08b14daba64d07280 Mon Sep 17 00:00:00 2001 From: remo5000 Date: Thu, 17 May 2018 12:20:53 +0800 Subject: [PATCH 055/129] Add `max-classes-per-file` exception for interpreter files Splitting the exports would cause futher refactoring in other parts of the interpreter codebase --- src/slang/interpreter-errors.ts | 1 + src/slang/interpreter.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/slang/interpreter-errors.ts b/src/slang/interpreter-errors.ts index 9a76f1684c..dd802dfd74 100644 --- a/src/slang/interpreter-errors.ts +++ b/src/slang/interpreter-errors.ts @@ -1,3 +1,4 @@ +/* tslint:disable: max-classes-per-file */ import { generate } from 'astring' import * as es from 'estree' import { UNKNOWN_LOCATION } from './constants' diff --git a/src/slang/interpreter.ts b/src/slang/interpreter.ts index 71c9bce858..d3685e2c7f 100644 --- a/src/slang/interpreter.ts +++ b/src/slang/interpreter.ts @@ -1,3 +1,4 @@ +/* tslint:disable: max-classes-per-file */ import * as es from 'estree' import * as constants from './constants' import { toJS } from './interop' From 05f32435ad6b0482a4214550791eea8fcd0d5e36 Mon Sep 17 00:00:00 2001 From: ning Date: Thu, 17 May 2018 12:25:42 +0800 Subject: [PATCH 056/129] Fix linting errors in parser, rules/braces... --- src/slang/parser.ts | 35 ++++++++++++++------------- src/slang/rules/bracesAroundIfElse.ts | 12 ++++----- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/slang/parser.ts b/src/slang/parser.ts index 2f6b733f6b..557113e261 100644 --- a/src/slang/parser.ts +++ b/src/slang/parser.ts @@ -1,19 +1,20 @@ -import * as es from 'estree' -import { parse as acornParse, Options as AcornOptions, Position } from 'acorn' -import { stripIndent } from 'common-tags' +import { Options as AcornOptions, parse as acornParse, Position } from 'acorn' import { simple } from 'acorn/dist/walk' -import { SourceError, ErrorType, ErrorSeverity, Context } from './types' -import syntaxTypes from './syntaxTypes' +import { stripIndent } from 'common-tags' +import * as es from 'estree' + import rules from './rules' +import syntaxTypes from './syntaxTypes' +import { Context, ErrorSeverity, ErrorType, SourceError } from './types' export type ParserOptions = { week: number } export class DisallowedConstructError implements SourceError { - type = ErrorType.SYNTAX - severity = ErrorSeverity.ERROR - nodeType: string + public type = ErrorType.SYNTAX + public severity = ErrorSeverity.ERROR + public nodeType: string constructor(public node: es.Node) { this.nodeType = this.splitNodeType() @@ -51,9 +52,9 @@ export class DisallowedConstructError implements SourceError { } export class FatalSyntaxError implements SourceError { - type = ErrorType.SYNTAX - severity = ErrorSeverity.ERROR - constructor(public location: es.SourceLocation, public message: string) {} + public type = ErrorType.SYNTAX + public severity = ErrorSeverity.ERROR + public constructor(public location: es.SourceLocation, public message: string) {} explain() { return this.message @@ -65,9 +66,9 @@ export class FatalSyntaxError implements SourceError { } export class MissingSemicolonError implements SourceError { - type = ErrorType.SYNTAX - severity = ErrorSeverity.ERROR - constructor(public location: es.SourceLocation) {} + public type = ErrorType.SYNTAX + public severity = ErrorSeverity.ERROR + public constructor(public location: es.SourceLocation) {} explain() { return 'Missing semicolon at the end of statement' @@ -79,9 +80,9 @@ export class MissingSemicolonError implements SourceError { } export class TrailingCommaError implements SourceError { - type: ErrorType.SYNTAX - severity: ErrorSeverity.WARNING - constructor(public location: es.SourceLocation) {} + public type: ErrorType.SYNTAX + public severity: ErrorSeverity.WARNING + public constructor(public location: es.SourceLocation) {} explain() { return 'Trailing comma' diff --git a/src/slang/rules/bracesAroundIfElse.ts b/src/slang/rules/bracesAroundIfElse.ts index 3ba74bfa6e..37c5b116f5 100644 --- a/src/slang/rules/bracesAroundIfElse.ts +++ b/src/slang/rules/bracesAroundIfElse.ts @@ -1,12 +1,12 @@ -import * as es from 'estree' import { generate } from 'astring' import { stripIndent } from 'common-tags' +import * as es from 'estree' -import { SourceError, Rule, ErrorSeverity, ErrorType } from '../types' +import { ErrorSeverity, ErrorType, Rule, SourceError } from '../types' export class BracesAroundIfElseError implements SourceError { - type = ErrorType.SYNTAX - severity = ErrorSeverity.ERROR + public type = ErrorType.SYNTAX + public severity = ErrorSeverity.ERROR constructor(public node: es.IfStatement, private branch: 'consequent' | 'alternate') {} @@ -14,7 +14,7 @@ export class BracesAroundIfElseError implements SourceError { return this.node.loc! } - explain() { + public explain() { if (this.branch === 'consequent') { return 'Missing curly braces around "if" block' } else { @@ -22,7 +22,7 @@ export class BracesAroundIfElseError implements SourceError { } } - elaborate() { + public elaborate() { let ifOrElse let header let body From 9018abde1bfd177255d82a86568994ddfd9c7d1b Mon Sep 17 00:00:00 2001 From: remo5000 Date: Thu, 17 May 2018 12:37:37 +0800 Subject: [PATCH 057/129] Fix tslint errors in `parser` Exception was added for multiple class exports. --- src/slang/parser.ts | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/slang/parser.ts b/src/slang/parser.ts index 557113e261..17bc6ff177 100644 --- a/src/slang/parser.ts +++ b/src/slang/parser.ts @@ -1,3 +1,4 @@ +/* tslint:disable: max-classes-per-file */ import { Options as AcornOptions, parse as acornParse, Position } from 'acorn' import { simple } from 'acorn/dist/walk' import { stripIndent } from 'common-tags' @@ -7,7 +8,8 @@ import rules from './rules' import syntaxTypes from './syntaxTypes' import { Context, ErrorSeverity, ErrorType, SourceError } from './types' -export type ParserOptions = { +/* tslint:disable:interface-name */ +export interface ParserOptions { week: number } @@ -24,11 +26,11 @@ export class DisallowedConstructError implements SourceError { return this.node.loc! } - explain() { + public explain() { return `${this.nodeType} is not allowed` } - elaborate() { + public elaborate() { return stripIndent` You are trying to use ${this.nodeType}, which is not yet allowed (yet). ` @@ -56,11 +58,11 @@ export class FatalSyntaxError implements SourceError { public severity = ErrorSeverity.ERROR public constructor(public location: es.SourceLocation, public message: string) {} - explain() { + public explain() { return this.message } - elaborate() { + public elaborate() { return 'There is a syntax error in your program' } } @@ -70,11 +72,11 @@ export class MissingSemicolonError implements SourceError { public severity = ErrorSeverity.ERROR public constructor(public location: es.SourceLocation) {} - explain() { + public explain() { return 'Missing semicolon at the end of statement' } - elaborate() { + public elaborate() { return 'Every statement must be terminated by a semicolon.' } } @@ -84,11 +86,11 @@ export class TrailingCommaError implements SourceError { public severity: ErrorSeverity.WARNING public constructor(public location: es.SourceLocation) {} - explain() { + public explain() { return 'Trailing comma' } - elaborate() { + public elaborate() { return 'Please remove the trailing comma' } } @@ -176,7 +178,7 @@ rules.forEach(rule => { }) export const parse = (source: string, context: Context) => { - let program: es.Program | undefined = undefined + let program: es.Program | undefined try { program = acornParse(source, createAcornParserOptions(context)) simple(program, walkers, undefined, context) From 22ec2ac7ba01e0740f144038b424f096a1922854 Mon Sep 17 00:00:00 2001 From: remo5000 Date: Thu, 17 May 2018 12:38:30 +0800 Subject: [PATCH 058/129] Format interpreter.ts --- src/slang/interpreter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slang/interpreter.ts b/src/slang/interpreter.ts index d3685e2c7f..6771b878c0 100644 --- a/src/slang/interpreter.ts +++ b/src/slang/interpreter.ts @@ -151,7 +151,7 @@ export const evaluators: { [nodeType: string]: Evaluator } = { *CallExpression(node: es.CallExpression, context: Context) { const callee = yield* evaluate(node.callee, context) const args = yield* getArgs(context, node) - let thisContext; + let thisContext if (node.callee.type === 'MemberExpression') { thisContext = yield* evaluate(node.callee.object, context) } From 3fbf3afe08a8815afebeb6611a22349fbc2bf61c Mon Sep 17 00:00:00 2001 From: ning Date: Thu, 17 May 2018 12:39:04 +0800 Subject: [PATCH 059/129] Fix tslint errors in rules/* --- src/slang/interpreter.ts | 2 +- src/slang/rules/bracesAroundWhile.ts | 10 +++++----- src/slang/rules/index.ts | 10 +++++----- src/slang/rules/noBlockArrowFunction.ts | 11 ++++++----- src/slang/rules/noDeclareMutable.ts | 14 +++++++------- src/slang/rules/noDeclareReserved.ts | 14 +++++++------- src/slang/rules/noIfWithoutElse.ts | 13 +++++++------ src/slang/rules/noImplicitDeclareUndefined.ts | 13 +++++++------ src/slang/rules/noImplicitReturnUndefined.ts | 12 ++++++------ src/slang/rules/noNonEmptyList.ts | 10 +++++----- src/slang/rules/singleVariableDeclaration.ts | 12 ++++++------ src/slang/rules/strictEquality.ts | 10 +++++----- 12 files changed, 67 insertions(+), 64 deletions(-) diff --git a/src/slang/interpreter.ts b/src/slang/interpreter.ts index d3685e2c7f..6771b878c0 100644 --- a/src/slang/interpreter.ts +++ b/src/slang/interpreter.ts @@ -151,7 +151,7 @@ export const evaluators: { [nodeType: string]: Evaluator } = { *CallExpression(node: es.CallExpression, context: Context) { const callee = yield* evaluate(node.callee, context) const args = yield* getArgs(context, node) - let thisContext; + let thisContext if (node.callee.type === 'MemberExpression') { thisContext = yield* evaluate(node.callee.object, context) } diff --git a/src/slang/rules/bracesAroundWhile.ts b/src/slang/rules/bracesAroundWhile.ts index 97507c7764..87835839dd 100644 --- a/src/slang/rules/bracesAroundWhile.ts +++ b/src/slang/rules/bracesAroundWhile.ts @@ -1,10 +1,10 @@ import * as es from 'estree' -import { SourceError, Rule, ErrorSeverity, ErrorType } from '../types' +import { ErrorSeverity, ErrorType, Rule, SourceError } from '../types' export class BracesAroundWhileError implements SourceError { - type = ErrorType.SYNTAX - severity = ErrorSeverity.ERROR + public type = ErrorType.SYNTAX + public severity = ErrorSeverity.ERROR constructor(public node: es.WhileStatement) {} @@ -12,11 +12,11 @@ export class BracesAroundWhileError implements SourceError { return this.node.loc! } - explain() { + public explain() { return 'Missing curly braces around "while" block' } - elaborate() { + public elaborate() { return 'TODO' } } diff --git a/src/slang/rules/index.ts b/src/slang/rules/index.ts index 83488a8d0d..06dae7f47f 100644 --- a/src/slang/rules/index.ts +++ b/src/slang/rules/index.ts @@ -4,15 +4,15 @@ import { Rule } from '../types' import bracesAroundIfElse from './bracesAroundIfElse' import bracesAroundWhile from './bracesAroundWhile' +import noBlockArrowFunction from './noBlockArrowFunction' +import noDeclareMutable from './noDeclareMutable' +import noDeclareReserved from './noDeclareReserved' import noIfWithoutElse from './noIfWithoutElse' -import singleVariableDeclaration from './singleVariableDeclaration' -import strictEquality from './strictEquality' import noImplicitDeclareUndefined from './noImplicitDeclareUndefined' import noImplicitReturnUndefined from './noImplicitReturnUndefined' import noNonEmptyList from './noNonEmptyList' -import noBlockArrowFunction from './noBlockArrowFunction' -import noDeclareReserved from './noDeclareReserved' -import noDeclareMutable from './noDeclareMutable' +import singleVariableDeclaration from './singleVariableDeclaration' +import strictEquality from './strictEquality' const rules: Array> = [ bracesAroundIfElse, diff --git a/src/slang/rules/noBlockArrowFunction.ts b/src/slang/rules/noBlockArrowFunction.ts index 984b9e4e41..cccd6d68d3 100644 --- a/src/slang/rules/noBlockArrowFunction.ts +++ b/src/slang/rules/noBlockArrowFunction.ts @@ -1,21 +1,22 @@ import * as es from 'estree' -import { SourceError, Rule, ErrorSeverity, ErrorType } from '../types' +import { ErrorSeverity, ErrorType, Rule, SourceError } from '../types' export class NoBlockArrowFunction implements SourceError { - type = ErrorType.SYNTAX - severity = ErrorSeverity.ERROR + public type = ErrorType.SYNTAX + public severity = ErrorSeverity.ERROR + constructor(public node: es.ArrowFunctionExpression) {} get location() { return this.node.loc! } - explain() { + public explain() { return 'Function definition expressions may only end with an expression.' } - elaborate() { + public elaborate() { return this.explain() } } diff --git a/src/slang/rules/noDeclareMutable.ts b/src/slang/rules/noDeclareMutable.ts index 1bdd37f153..1150548477 100644 --- a/src/slang/rules/noDeclareMutable.ts +++ b/src/slang/rules/noDeclareMutable.ts @@ -1,12 +1,12 @@ import * as es from 'estree' -import { SourceError, Rule, ErrorSeverity, ErrorType } from '../types' +import { ErrorSeverity, ErrorType, Rule, SourceError } from '../types' const mutableDeclarators = ['let', 'var'] -export class noDeclareMutableError implements SourceError { - type = ErrorType.SYNTAX - severity = ErrorSeverity.ERROR +export class NoDeclareMutableError implements SourceError { + public type = ErrorType.SYNTAX + public severity = ErrorSeverity.ERROR constructor(public node: es.VariableDeclaration) {} @@ -14,13 +14,13 @@ export class noDeclareMutableError implements SourceError { return this.node.loc! } - explain() { + public explain() { return ( 'Mutable variable declaration using keyword ' + `'${this.node.kind}'` + ' is not allowed.' ) } - elaborate() { + public elaborate() { return this.explain() } } @@ -31,7 +31,7 @@ const noDeclareMutable: Rule = { checkers: { VariableDeclaration(node: es.VariableDeclaration) { if (mutableDeclarators.includes(node.kind)) { - return [new noDeclareMutableError(node)] + return [new NoDeclareMutableError(node)] } else { return [] } diff --git a/src/slang/rules/noDeclareReserved.ts b/src/slang/rules/noDeclareReserved.ts index bc51b7351e..22e2b4183a 100644 --- a/src/slang/rules/noDeclareReserved.ts +++ b/src/slang/rules/noDeclareReserved.ts @@ -1,6 +1,6 @@ import * as es from 'estree' -import { SourceError, Rule, ErrorSeverity, ErrorType } from '../types' +import { ErrorSeverity, ErrorType, Rule, SourceError } from '../types' const reservedNames = [ 'break', @@ -50,9 +50,9 @@ const reservedNames = [ 'false' ] -export class noDeclareReservedError implements SourceError { - type = ErrorType.SYNTAX - severity = ErrorSeverity.ERROR +export class NoDeclareReservedError implements SourceError { + public type = ErrorType.SYNTAX + public severity = ErrorSeverity.ERROR constructor(public node: es.VariableDeclaration) {} @@ -60,13 +60,13 @@ export class noDeclareReservedError implements SourceError { return this.node.loc! } - explain() { + public explain() { return ( `Reserved word '${(this.node.declarations[0].id as any).name}'` + ' is not allowed as a name' ) } - elaborate() { + public elaborate() { return this.explain() } } @@ -77,7 +77,7 @@ const noDeclareReserved: Rule = { checkers: { VariableDeclaration(node: es.VariableDeclaration) { if (reservedNames.includes((node.declarations[0].id as any).name)) { - return [new noDeclareReservedError(node)] + return [new NoDeclareReservedError(node)] } else { return [] } diff --git a/src/slang/rules/noIfWithoutElse.ts b/src/slang/rules/noIfWithoutElse.ts index c4d170d42c..1290f3054c 100644 --- a/src/slang/rules/noIfWithoutElse.ts +++ b/src/slang/rules/noIfWithoutElse.ts @@ -1,23 +1,24 @@ +import { generate } from 'astring' import { stripIndent } from 'common-tags' import * as es from 'estree' -import { generate } from 'astring' -import { SourceError, Rule, ErrorSeverity, ErrorType } from '../types' +import { ErrorSeverity, ErrorType, Rule, SourceError } from '../types' export class NoIfWithoutElseError implements SourceError { - type = ErrorType.SYNTAX - severity = ErrorSeverity.ERROR + public type = ErrorType.SYNTAX + public severity = ErrorSeverity.ERROR + constructor(public node: es.IfStatement) {} get location() { return this.node.loc! } - explain() { + public explain() { return 'Missing "else" in "if-else" statement' } - elaborate() { + public elaborate() { return stripIndent` This "if" block requires corresponding "else" block which will be evaluated when ${generate(this.node.test)} expression evaluates to false. diff --git a/src/slang/rules/noImplicitDeclareUndefined.ts b/src/slang/rules/noImplicitDeclareUndefined.ts index 6cb6759b6e..8c42d495d6 100644 --- a/src/slang/rules/noImplicitDeclareUndefined.ts +++ b/src/slang/rules/noImplicitDeclareUndefined.ts @@ -1,22 +1,23 @@ -import * as es from 'estree' import { stripIndent } from 'common-tags' +import * as es from 'estree' -import { SourceError, Rule, ErrorSeverity, ErrorType } from '../types' +import { ErrorSeverity, ErrorType, Rule, SourceError } from '../types' export class NoImplicitDeclareUndefinedError implements SourceError { - type = ErrorType.SYNTAX - severity = ErrorSeverity.ERROR + public type = ErrorType.SYNTAX + public severity = ErrorSeverity.ERROR + constructor(public node: es.Identifier) {} get location() { return this.node.loc! } - explain() { + public explain() { return 'Missing value in variable declaration' } - elaborate() { + public elaborate() { return stripIndent` A variable declaration assigns a value to a name. For instance, to assign 20 to ${this.node.name}, you can write: diff --git a/src/slang/rules/noImplicitReturnUndefined.ts b/src/slang/rules/noImplicitReturnUndefined.ts index 480b0ad5fc..0f3f6741a7 100644 --- a/src/slang/rules/noImplicitReturnUndefined.ts +++ b/src/slang/rules/noImplicitReturnUndefined.ts @@ -1,11 +1,11 @@ -import * as es from 'estree' import { stripIndent } from 'common-tags' +import * as es from 'estree' -import { SourceError, Rule, ErrorSeverity, ErrorType } from '../types' +import { ErrorSeverity, ErrorType, Rule, SourceError } from '../types' export class NoImplicitReturnUndefinedError implements SourceError { - type = ErrorType.SYNTAX - severity = ErrorSeverity.ERROR + public type = ErrorType.SYNTAX + public severity = ErrorSeverity.ERROR constructor(public node: es.ReturnStatement) {} @@ -13,11 +13,11 @@ export class NoImplicitReturnUndefinedError implements SourceError { return this.node.loc! } - explain() { + public explain() { return 'Missing value in return statement' } - elaborate() { + public elaborate() { return stripIndent` This return statement is missing a value. For instance, to return the value 42, you can write diff --git a/src/slang/rules/noNonEmptyList.ts b/src/slang/rules/noNonEmptyList.ts index 43cf220fe3..d5b3b2512a 100644 --- a/src/slang/rules/noNonEmptyList.ts +++ b/src/slang/rules/noNonEmptyList.ts @@ -1,10 +1,10 @@ import * as es from 'estree' -import { SourceError, Rule, ErrorSeverity, ErrorType } from '../types' +import { ErrorSeverity, ErrorType, Rule, SourceError } from '../types' export class NoNonEmptyListError implements SourceError { - type = ErrorType.SYNTAX - severity = ErrorSeverity.ERROR + public type = ErrorType.SYNTAX + public severity = ErrorSeverity.ERROR constructor(public node: es.ArrayExpression) {} @@ -12,11 +12,11 @@ export class NoNonEmptyListError implements SourceError { return this.node.loc! } - explain() { + public explain() { return 'Only empty list notation ([]) is allowed' } - elaborate() { + public elaborate() { return 'TODO' } } diff --git a/src/slang/rules/singleVariableDeclaration.ts b/src/slang/rules/singleVariableDeclaration.ts index d86d5ab4a1..bf6c79a48d 100644 --- a/src/slang/rules/singleVariableDeclaration.ts +++ b/src/slang/rules/singleVariableDeclaration.ts @@ -1,11 +1,11 @@ -import * as es from 'estree' import { generate } from 'astring' +import * as es from 'estree' -import { SourceError, Rule, ErrorSeverity, ErrorType } from '../types' +import { ErrorSeverity, ErrorType, Rule, SourceError } from '../types' export class MultipleDeclarationsError implements SourceError { - type = ErrorType.SYNTAX - severity = ErrorSeverity.ERROR + public type = ErrorType.SYNTAX + public severity = ErrorSeverity.ERROR private fixs: es.VariableDeclaration[] constructor(public node: es.VariableDeclaration) { @@ -21,11 +21,11 @@ export class MultipleDeclarationsError implements SourceError { return this.node.loc! } - explain() { + public explain() { return 'Multiple declaration in a single statement' } - elaborate() { + public elaborate() { const fixs = this.fixs.map(n => '\t' + generate(n)).join('\n') return 'Split the variable declaration into multiple lines as follows\n\n' + fixs + '\n' } diff --git a/src/slang/rules/strictEquality.ts b/src/slang/rules/strictEquality.ts index 58bd7f7a7e..b6913cdbe3 100644 --- a/src/slang/rules/strictEquality.ts +++ b/src/slang/rules/strictEquality.ts @@ -1,10 +1,10 @@ import * as es from 'estree' -import { SourceError, Rule, ErrorSeverity, ErrorType } from '../types' +import { ErrorSeverity, ErrorType, Rule, SourceError } from '../types' export class StrictEqualityError implements SourceError { - type = ErrorType.SYNTAX - severity = ErrorSeverity.ERROR + public type = ErrorType.SYNTAX + public severity = ErrorSeverity.ERROR constructor(public node: es.BinaryExpression) {} @@ -12,7 +12,7 @@ export class StrictEqualityError implements SourceError { return this.node.loc! } - explain() { + public explain() { if (this.node.operator === '==') { return 'Use === instead of ==' } else { @@ -20,7 +20,7 @@ export class StrictEqualityError implements SourceError { } } - elaborate() { + public elaborate() { return '== and != is not a valid operator' } } From a8c50a629ae7d8d17e786dd32f165cfcee1250e5 Mon Sep 17 00:00:00 2001 From: ning Date: Thu, 17 May 2018 12:50:58 +0800 Subject: [PATCH 060/129] Fix tslint errors for rttc.ts --- src/slang/utils/rttc.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/slang/utils/rttc.ts b/src/slang/utils/rttc.ts index 8d3817fcd3..7a87e6c80a 100644 --- a/src/slang/utils/rttc.ts +++ b/src/slang/utils/rttc.ts @@ -1,20 +1,20 @@ import * as es from 'estree' -import { SourceError, Value, Context, ErrorSeverity, ErrorType } from '../types' +import { Context, ErrorSeverity, ErrorType, SourceError, Value } from '../types' class TypeError implements SourceError { - type: ErrorType.RUNTIME - severity: ErrorSeverity.WARNING - location: es.SourceLocation + public type: ErrorType.RUNTIME + public severity: ErrorSeverity.WARNING + public location: es.SourceLocation constructor(node: es.Node, public context: string, public expected: string, public got: string) { this.location = node.loc! } - explain() { + public explain() { return `TypeError: Expected ${this.expected} in ${this.context}, got ${this.got}.` } - elaborate() { + public elaborate() { return 'TODO' } } From 766cb5b2661235c44a3018b67d45fbc0f878e3d3 Mon Sep 17 00:00:00 2001 From: ning Date: Thu, 17 May 2018 12:52:16 +0800 Subject: [PATCH 061/129] Fix tslint errors for node.ts --- src/slang/utils/node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slang/utils/node.ts b/src/slang/utils/node.ts index 7305054dc4..3f2c9cc874 100644 --- a/src/slang/utils/node.ts +++ b/src/slang/utils/node.ts @@ -1,8 +1,8 @@ /** * Utility functions to work with the AST (Abstract Syntax Tree) */ +import { SimpleWalker, Walker } from 'acorn/dist/walk' import * as es from 'estree' -import { Walker, SimpleWalker } from 'acorn/dist/walk' import { Closure, Value } from '../types' /** From 67aba687c36a651cebb347f5fd0c659709f7e73e Mon Sep 17 00:00:00 2001 From: ning Date: Thu, 17 May 2018 13:04:48 +0800 Subject: [PATCH 062/129] Fix tslint errors for types.ts --- src/slang/types.ts | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/slang/types.ts b/src/slang/types.ts index 6b053f0c10..098d63b3ab 100644 --- a/src/slang/types.ts +++ b/src/slang/types.ts @@ -1,5 +1,7 @@ -import * as es from 'estree' +/* tslint:disable:interface-name max-classes-per-file */ + import { SourceLocation } from 'acorn' +import * as es from 'estree' import { closureToJS } from './interop' @@ -30,9 +32,10 @@ export interface Rule { } } +// tslint:disable-next-line:no-namespace export namespace CFG { // tslint:disable-next-line:no-shadowed-variable - export type Scope = { + export interface Scope { name: string parent?: Scope entry?: Vertex @@ -45,14 +48,14 @@ export namespace CFG { } } - export type Vertex = { + export interface Vertex { id: string node: es.Node scope?: Scope usages: Sym[] } - export type Sym = { + export interface Sym { name: string defined?: boolean definedAt?: es.SourceLocation @@ -60,7 +63,7 @@ export namespace CFG { proof?: es.Node } - export type Type = { + export interface Type { name: 'number' | 'string' | 'boolean' | 'function' | 'undefined' | 'any' params?: Type[] returnType?: Type @@ -68,13 +71,13 @@ export namespace CFG { export type EdgeLabel = 'next' | 'alternate' | 'consequent' - export type Edge = { + export interface Edge { type: EdgeLabel to: Vertex } } -export type Comment = { +export interface Comment { type: 'Line' | 'Block' value: string start: number @@ -88,7 +91,7 @@ export interface TypeError extends SourceError { proof?: es.Node } -export type Context = { +export interface Context { /** The source version used */ week: number @@ -111,7 +114,7 @@ export type Context = { } // tslint:disable:no-any -export type Environment = { [name: string]: any } +export interface Environment{ [name: string]: any } export type Value = any // tslint:enable:no-any @@ -134,6 +137,7 @@ export class Closure { public name: string /** Fake closure function */ + // tslint:disable-next-line:ban-types public fun: Function constructor(public node: es.FunctionExpression, public frame: Frame, context: Context) { @@ -160,6 +164,7 @@ export class ArrowClosure { public name: string /** Fake closure function */ + // tslint:disable-next-line:ban-types public fun: Function constructor(public node: es.Function, public frame: Frame, context: Context) { @@ -168,16 +173,16 @@ export class ArrowClosure { } } -type Error = { +interface Error { status: 'error' } -export type Finished = { +export interface Finished { status: 'finished' value: Value } -type Suspended = { +interface Suspended { status: 'suspended' it: IterableIterator scheduler: Scheduler From 7a676ef4277b144c0165e649026e62e17f3f3cdb Mon Sep 17 00:00:00 2001 From: remo5000 Date: Thu, 17 May 2018 13:05:08 +0800 Subject: [PATCH 063/129] Fix stdlib/list, stdlib/misc and scheudler tslint An additional change is the use of a line disable in `parser.ts` instead of an entire file disable. --- src/slang/parser.ts | 2 +- src/slang/schedulers.ts | 7 ++++--- src/slang/stdlib/list.ts | 1 + src/slang/stdlib/misc.ts | 15 ++++++++------- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/slang/parser.ts b/src/slang/parser.ts index 17bc6ff177..e975b8c564 100644 --- a/src/slang/parser.ts +++ b/src/slang/parser.ts @@ -8,7 +8,7 @@ import rules from './rules' import syntaxTypes from './syntaxTypes' import { Context, ErrorSeverity, ErrorType, SourceError } from './types' -/* tslint:disable:interface-name */ +// tslint:disable-next-line:interface-name export interface ParserOptions { week: number } diff --git a/src/slang/schedulers.ts b/src/slang/schedulers.ts index 8573d313ef..012e16a3a0 100644 --- a/src/slang/schedulers.ts +++ b/src/slang/schedulers.ts @@ -1,9 +1,10 @@ +/* tslint:disable: max-classes-per-file */ import * as es from 'estree' -import { Scheduler, Value, Context, Result } from './types' import { MaximumStackLimitExceeded } from './interpreter-errors' +import { Context, Result, Scheduler, Value } from './types' export class AsyncScheduler implements Scheduler { - run(it: IterableIterator, context: Context): Promise { + public run(it: IterableIterator, context: Context): Promise { return new Promise((resolve, reject) => { context.runtime.isRunning = true let itValue = it.next() @@ -27,7 +28,7 @@ export class AsyncScheduler implements Scheduler { export class PreemptiveScheduler implements Scheduler { constructor(public steps: number) {} - run(it: IterableIterator, context: Context): Promise { + public run(it: IterableIterator, context: Context): Promise { return new Promise((resolve, reject) => { context.runtime.isRunning = true let itValue = it.next() diff --git a/src/slang/stdlib/list.ts b/src/slang/stdlib/list.ts index dc1e1478ab..cb483f0786 100644 --- a/src/slang/stdlib/list.ts +++ b/src/slang/stdlib/list.ts @@ -2,6 +2,7 @@ import { toString } from '../interop' import { Value } from '../types' declare global { +// tslint:disable-next-line:interface-name interface Function { __SOURCE__?: string } diff --git a/src/slang/stdlib/misc.ts b/src/slang/stdlib/misc.ts index 5aad1e0068..ac77bac7b7 100644 --- a/src/slang/stdlib/misc.ts +++ b/src/slang/stdlib/misc.ts @@ -1,5 +1,6 @@ -import { Value } from '../types' +/* tslint:disable: ban-types*/ import { toString } from '../interop' +import { Value } from '../types' export function display(value: Value) { const output = toString(value) @@ -27,13 +28,13 @@ error_message.__SOURCE__ = 'error(a)' // tslint:disable-next-line:no-any export function timed(this: any, f: Function) { - var self = this - var timerType = Date + const self = this + const timerType = Date - return function() { - var start = timerType.now() - var result = f.apply(self, arguments) - var diff = timerType.now() - start + return () => { + const start = timerType.now() + const result = f.apply(self, arguments) + const diff = timerType.now() - start display('Duration: ' + Math.round(diff) + 'ms') return result } From 0a6e87c7748c85ba87af8e7a53c24322dd398e7b Mon Sep 17 00:00:00 2001 From: remo5000 Date: Thu, 17 May 2018 13:21:42 +0800 Subject: [PATCH 064/129] Fix `saga` not being run after store creation --- src/createStore.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/createStore.ts b/src/createStore.ts index 828929e726..75b860619b 100644 --- a/src/createStore.ts +++ b/src/createStore.ts @@ -19,7 +19,6 @@ function createStore(history: History): Store { let composeEnhancers: any = compose const sagaMiddleware = createSagaMiddleware() const middleware = [sagaMiddleware, routerMiddleware(history)] - sagaMiddleware.run(mainSaga) if (typeof __REDUX_DEVTOOLS_EXTENSION_COMPOSE__ === 'function') { composeEnhancers = __REDUX_DEVTOOLS_EXTENSION_COMPOSE__ @@ -30,8 +29,10 @@ function createStore(history: History): Store { router: routerReducer }) const enchancers = composeEnhancers(applyMiddleware(...middleware)) + const createdStore = _createStore(rootReducer, enchancers) - return _createStore(rootReducer, enchancers) + sagaMiddleware.run(mainSaga) + return createdStore } export default createStore From e4f7743fe38a0791155786e2329685747de97e46 Mon Sep 17 00:00:00 2001 From: ning Date: Thu, 17 May 2018 15:06:38 +0800 Subject: [PATCH 065/129] Fix Editor test (tslint errors) 1. Missing comma in object props 2. Syntax error and missing object comprehension in creating Editor --- src/components/__tests__/Editor.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/__tests__/Editor.tsx b/src/components/__tests__/Editor.tsx index 91f287259a..b6e7841122 100644 --- a/src/components/__tests__/Editor.tsx +++ b/src/components/__tests__/Editor.tsx @@ -6,10 +6,10 @@ import Editor, { IEditorProps } from '../Editor' test('Editor renders correctly', () => { const props: IEditorProps = { - editorValue: '' - handleEditorChange: (newCode) => {} + editorValue: '', + handleEditorChange: newCode => {} } - const app = + const app = const tree = shallow(app) expect(tree.debug()).toMatchSnapshot() }) From 873a14beb35877175019b5b2e0ba05f868ada2df Mon Sep 17 00:00:00 2001 From: ning Date: Thu, 17 May 2018 15:08:30 +0800 Subject: [PATCH 066/129] Add properties context, output to IPlaygroundState 1. context is of type slang.Context. This holds the 'environment' for the slang interpreter 2. Output is a string array that will hold the stdout/stderr (display, error calls) of a program run in the editor. For repl programs, it will also hold the return value of the program. Each element of an array is a historical output. --- src/mocks/store.ts | 12 ++++-------- src/reducers/application.ts | 2 +- src/reducers/playground.ts | 9 +++++++-- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/mocks/store.ts b/src/mocks/store.ts index d2656b1a7a..d87a5d31d0 100644 --- a/src/mocks/store.ts +++ b/src/mocks/store.ts @@ -2,18 +2,14 @@ import { Store } from 'redux' import * as mockStore from 'redux-mock-store' import { IState } from '../reducers' -import { ApplicationEnvironment } from '../reducers/application' +import { defaultState as defaultApplication } from '../reducers/application' +import { defaultState as defaultPlayground } from '../reducers/playground' export function mockInitialStore

(): Store { const createStore = (mockStore as any)() const state: IState = { - application: { - title: 'Cadet', - environment: ApplicationEnvironment.Development - }, - playground: { - editorValue: '' - } + application: defaultApplication, + playground: defaultPlayground } return createStore(state) } diff --git a/src/reducers/application.ts b/src/reducers/application.ts index 6885552c2a..053931e37f 100644 --- a/src/reducers/application.ts +++ b/src/reducers/application.ts @@ -22,7 +22,7 @@ const currentEnvironment = (): ApplicationEnvironment => { } } -const defaultState: IApplicationState = { +export const defaultState: IApplicationState = { title: 'Cadet', environment: currentEnvironment() } diff --git a/src/reducers/playground.ts b/src/reducers/playground.ts index 22cd54cfeb..b1af8654c4 100644 --- a/src/reducers/playground.ts +++ b/src/reducers/playground.ts @@ -1,16 +1,21 @@ import { Action, Reducer } from 'redux' import { IUpdateEditorValue } from '../actions' import { UPDATE_EDITOR_VALUE } from '../actions/actionTypes' +import { Context, createContext } from '../slang' export interface IPlaygroundState { editorValue: string + context: Context + output: string[] } /** * The default (initial) state of the `IPlaygroundState` */ -const defaultState: IPlaygroundState = { - editorValue: '' +export const defaultState: IPlaygroundState = { + editorValue: '', + context: createContext(), + output: [] } /** From 52210e53007ebe960472bdf055fa24714248c37c Mon Sep 17 00:00:00 2001 From: remo5000 Date: Thu, 17 May 2018 15:17:05 +0800 Subject: [PATCH 067/129] Add button to call on handleEvalEditor This change allows the newly added button to call upon the handleEvalEditor callback, whose reducer is not defined (yet) --- src/components/Editor.tsx | 50 +++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx index ee55bf0365..340c44e620 100644 --- a/src/components/Editor.tsx +++ b/src/components/Editor.tsx @@ -1,29 +1,59 @@ import * as React from 'react' import AceEditor from 'react-ace' - import 'brace/mode/javascript' import 'brace/theme/github' +import { + Button, IconName, Intent +} from '@blueprintjs/core' + /** * @property editorValue - The string content of the react-ace editor - * @property updateCode - A callback function for the react-ace editor's `onChange` + * @property handleEditorChange - A callback function + * for the react-ace editor's `onChange` + * @property handleEvalEditor - A callback function for evaluation + * of the editor's content, using `slang` */ export interface IEditorProps { editorValue: string handleEditorChange: (newCode: string) => void + handleEvalEditor: () => void } + class Editor extends React.Component { public render() { + + const genericButton = ( + label: string, + icon: IconName, + handleClick = () => { }, + intent = Intent.NONE, + notMinimal = false + ) => + ( + ) + const runButton = genericButton('Run', 'play', this.props.handleEvalEditor) + return ( - +

+ + {runButton} +
) } } From 4fa0b3ccdcb04d30f3557af00b496667c96b1cd3 Mon Sep 17 00:00:00 2001 From: remo5000 Date: Thu, 17 May 2018 15:56:54 +0800 Subject: [PATCH 068/129] Add mapDispatchToProps for EditorContainer Note that the intersection type is used with `Pick` in order to define `DispatchProps`, this might not be ideal. Input on this issue would be appreciated. --- src/actions/playground.ts | 4 ++++ src/components/Editor.tsx | 3 ++- src/components/__tests__/Editor.tsx | 3 ++- src/containers/EditorContainer.ts | 10 +++++----- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/actions/playground.ts b/src/actions/playground.ts index cd0e714fa0..47257c4bd4 100644 --- a/src/actions/playground.ts +++ b/src/actions/playground.ts @@ -19,3 +19,7 @@ export const updateEditorValue: ActionCreator = (newEditorVa type: actionTypes.UPDATE_EDITOR_VALUE, payload: newEditorValue }) + +export const evalEditor = () => ({ + type: actionTypes.EVAL_EDITOR +}) \ No newline at end of file diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx index 340c44e620..9b2492b3a1 100644 --- a/src/components/Editor.tsx +++ b/src/components/Editor.tsx @@ -1,7 +1,8 @@ import * as React from 'react' -import AceEditor from 'react-ace' + import 'brace/mode/javascript' import 'brace/theme/github' +import AceEditor from 'react-ace' import { Button, IconName, Intent diff --git a/src/components/__tests__/Editor.tsx b/src/components/__tests__/Editor.tsx index b6e7841122..f37263fc91 100644 --- a/src/components/__tests__/Editor.tsx +++ b/src/components/__tests__/Editor.tsx @@ -7,7 +7,8 @@ import Editor, { IEditorProps } from '../Editor' test('Editor renders correctly', () => { const props: IEditorProps = { editorValue: '', - handleEditorChange: newCode => {} + handleEditorChange: newCode => {}, + handleEvalEditor: () => {} } const app = const tree = shallow(app) diff --git a/src/containers/EditorContainer.ts b/src/containers/EditorContainer.ts index 9fd9c75d4b..bc86aa8ef0 100644 --- a/src/containers/EditorContainer.ts +++ b/src/containers/EditorContainer.ts @@ -1,12 +1,13 @@ import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux' import { Dispatch } from 'redux' -import { updateEditorValue } from '../actions/playground' +import { evalEditor, updateEditorValue } from '../actions/playground' import Editor, { IEditorProps } from '../components/Editor' import { IState } from '../reducers' type StateProps = Pick -type DispatchProps = Pick +type DispatchProps = Pick + & Pick /** Provides the editorValue of the `IPlaygroundState` of the `IState` as a * `StateProps` to the Playground component @@ -23,9 +24,8 @@ const mapStateToProps: MapStateToProps = state => { */ const mapDispatchToProps: MapDispatchToProps = (dispatch: Dispatch) => { return { - handleEditorChange: (newCode: string) => { - dispatch(updateEditorValue(newCode)) - } + handleEditorChange: (newCode: string) => dispatch(updateEditorValue(newCode)), + handleEvalEditor: () => dispatch(evalEditor()) } } From cc4e2387435586ada7593c1adbdd80176df9003b Mon Sep 17 00:00:00 2001 From: remo5000 Date: Thu, 17 May 2018 16:17:29 +0800 Subject: [PATCH 069/129] Fix 'ace is undefined' error --- src/components/Editor.tsx | 62 ++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx index 9b2492b3a1..852692b19e 100644 --- a/src/components/Editor.tsx +++ b/src/components/Editor.tsx @@ -1,12 +1,12 @@ import * as React from 'react' +import AceEditor from 'react-ace' import 'brace/mode/javascript' import 'brace/theme/github' -import AceEditor from 'react-ace' -import { - Button, IconName, Intent -} from '@blueprintjs/core' +// import { +// Button, IconName, Intent +// } from '@blueprintjs/core' /** * @property editorValue - The string content of the react-ace editor @@ -25,36 +25,32 @@ export interface IEditorProps { class Editor extends React.Component { public render() { - const genericButton = ( - label: string, - icon: IconName, - handleClick = () => { }, - intent = Intent.NONE, - notMinimal = false - ) => - ( - ) - const runButton = genericButton('Run', 'play', this.props.handleEvalEditor) - + // const genericButton = ( + // label: string, + // icon: IconName, + // handleClick = () => { }, + // intent = Intent.NONE, + // notMinimal = false + // ) => + // ( + // ) + // const runButton = genericButton('Run', 'play', this.props.handleEvalEditor) return ( -
- - {runButton} -
+ ) } } From 7044f5238d34c8bd6075a15fc63092e45f9eeb4f Mon Sep 17 00:00:00 2001 From: ning Date: Thu, 17 May 2018 16:18:08 +0800 Subject: [PATCH 070/129] Add Output SFC --- src/components/Output.tsx | 16 ++++++++++++++++ src/components/Playground.tsx | 3 +++ src/containers/OutputContainer.tsx | 12 ++++++++++++ src/reducers/playground.ts | 2 +- 4 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 src/components/Output.tsx create mode 100644 src/containers/OutputContainer.tsx diff --git a/src/components/Output.tsx b/src/components/Output.tsx new file mode 100644 index 0000000000..be00b393f5 --- /dev/null +++ b/src/components/Output.tsx @@ -0,0 +1,16 @@ +import * as React from 'react' + +import { TextArea } from '@blueprintjs/core' + +export interface IOutputProps { + output: string[] +} + +const Output: React.SFC = (props) => ( +