diff --git a/src/actions/actionTypes.ts b/src/actions/actionTypes.ts index 536d1fdee9..735b1a65c0 100644 --- a/src/actions/actionTypes.ts +++ b/src/actions/actionTypes.ts @@ -13,6 +13,8 @@ export const CLEAR_REPL_INPUT = 'CLEAR_REPL_INPUT' export const CLEAR_REPL_OUTPUT = 'CLEAR_REPL_OUTPUT' export const CLEAR_CONTEXT = 'CLEAR_CONTEXT' export const SEND_REPL_INPUT_TO_OUTPUT = 'SEND_REPL_INPUT_TO_OUTPUT' +export const CHAPTER_SELECT = 'CHAPTER_SELECT' +export const CHANGE_CHAPTER = 'CHANGE_CHAPTER' /** Interpreter */ export const HANDLE_CONSOLE_LOG = 'HANDLE_CONSOLE_LOG' diff --git a/src/actions/playground.ts b/src/actions/playground.ts index c900fe6437..39b772f22c 100644 --- a/src/actions/playground.ts +++ b/src/actions/playground.ts @@ -1,3 +1,4 @@ +import { ChangeEvent } from 'react' import { ActionCreator } from 'redux' import * as actionTypes from './actionTypes' @@ -19,6 +20,18 @@ export const sendReplInputToOutput: ActionCreator = (newOut } }) +export const chapterSelect: ActionCreator = ( + e: ChangeEvent +) => ({ + type: actionTypes.CHAPTER_SELECT, + payload: e.currentTarget.value +}) + +export const changeChapter: ActionCreator = (newChapter: number) => ({ + type: actionTypes.CHANGE_CHAPTER, + payload: newChapter +}) + export const evalEditor = () => ({ type: actionTypes.EVAL_EDITOR }) diff --git a/src/components/IDE/Control.tsx b/src/components/IDE/Control.tsx index ee54aa5014..543773cb8b 100644 --- a/src/components/IDE/Control.tsx +++ b/src/components/IDE/Control.tsx @@ -1,6 +1,7 @@ +import { Button, IconName, Intent } from '@blueprintjs/core' import * as React from 'react' -import { Button, IconName, Intent } from '@blueprintjs/core' +import { sourceChapters } from '../../reducers/states' /** * @property handleEvalEditor - A callback function for evaluation @@ -11,27 +12,41 @@ export interface IControlProps { handleEvalEditor: () => void handleEvalRepl: () => void handleClearReplOutput: () => void + handleChapterSelect: (e: React.ChangeEvent) => void handleInterruptEval: () => void } +const genericButton = ( + label: string, + icon: IconName, + handleClick = () => {}, + intent = Intent.NONE, + notMinimal = false +) => ( + +) + +const chapterSelect = (handleSelect = (e: React.ChangeEvent) => {}) => ( +
+ +
+) + class Control extends React.Component { public render() { - const genericButton = ( - label: string, - icon: IconName, - handleClick = () => {}, - intent = Intent.NONE, - notMinimal = false - ) => ( - - ) const runButton = this.props.isRunning ? null : genericButton('Run', 'play', this.props.handleEvalEditor) @@ -48,8 +63,9 @@ class Control extends React.Component {
-
{evalButton}
-
{clearButton}
+ {chapterSelect(this.props.handleChapterSelect)} +
{evalButton}
+
{clearButton}
diff --git a/src/components/IDE/__tests__/Control.tsx b/src/components/IDE/__tests__/Control.tsx index 941a3e1239..d74ee3a909 100644 --- a/src/components/IDE/__tests__/Control.tsx +++ b/src/components/IDE/__tests__/Control.tsx @@ -10,6 +10,7 @@ test('Control renders correctly', () => { handleEvalEditor: () => {}, handleEvalRepl: () => {}, handleClearReplOutput: () => {}, + handleChapterSelect: (e: React.ChangeEvent) => {}, handleInterruptEval: () => {} } const app = diff --git a/src/components/IDE/__tests__/__snapshots__/Control.tsx.snap b/src/components/IDE/__tests__/__snapshots__/Control.tsx.snap index 162ba7418a..1dc888996b 100644 --- a/src/components/IDE/__tests__/__snapshots__/Control.tsx.snap +++ b/src/components/IDE/__tests__/__snapshots__/Control.tsx.snap @@ -9,12 +9,22 @@ exports[`Control renders correctly 1`] = `
-
+
+ +
+
Eval
-
+
Clear diff --git a/src/containers/IDE/ControlContainer.ts b/src/containers/IDE/ControlContainer.ts index 0c52a2baa6..5c683699ed 100644 --- a/src/containers/IDE/ControlContainer.ts +++ b/src/containers/IDE/ControlContainer.ts @@ -2,7 +2,7 @@ import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux' import { bindActionCreators, Dispatch } from 'redux' import { handleInterruptExecution } from '../../actions/interpreter' -import { clearReplOutput, evalEditor, evalRepl } from '../../actions/playground' +import { chapterSelect, clearReplOutput, evalEditor, evalRepl } from '../../actions/playground' import Control, { IControlProps } from '../../components/IDE/Control' import { IState } from '../../reducers/states' @@ -11,6 +11,7 @@ type StateProps = Pick type DispatchProps = Pick & Pick & Pick & + Pick & Pick /** No-op mapStateToProps */ @@ -27,6 +28,7 @@ const mapDispatchToProps: MapDispatchToProps = (dispatch: Dis handleEvalEditor: evalEditor, handleEvalRepl: evalRepl, handleClearReplOutput: clearReplOutput, + handleChapterSelect: chapterSelect, handleInterruptEval: handleInterruptExecution }, dispatch diff --git a/src/mocks/context.ts b/src/mocks/context.ts index 12ecd4c6f7..ded2dfc7df 100644 --- a/src/mocks/context.ts +++ b/src/mocks/context.ts @@ -1,6 +1,6 @@ import { createContext } from '../slang' import { Context } from '../slang/types' -export function mockContext(): Context { - return createContext() +export function mockContext(chapter = 1): Context { + return createContext(chapter) } diff --git a/src/reducers/playground.ts b/src/reducers/playground.ts index 2a00539a20..f7f449af63 100644 --- a/src/reducers/playground.ts +++ b/src/reducers/playground.ts @@ -1,5 +1,6 @@ import { Reducer } from 'redux' import { + CHANGE_CHAPTER, CLEAR_CONTEXT, CLEAR_REPL_INPUT, CLEAR_REPL_OUTPUT, @@ -44,7 +45,12 @@ export const reducer: Reducer = (state = defaultPlayground, ac case CLEAR_CONTEXT: return { ...state, - context: createContext() + context: createContext(state.sourceChapter) + } + case CHANGE_CHAPTER: + return { + ...state, + sourceChapter: action.payload } case HANDLE_CONSOLE_LOG: /* Possible cases: diff --git a/src/reducers/states.ts b/src/reducers/states.ts index 696621780f..e548185339 100644 --- a/src/reducers/states.ts +++ b/src/reducers/states.ts @@ -11,7 +11,11 @@ export interface IApplicationState { readonly environment: ApplicationEnvironment } +export const sourceChapters = [1, 2] +const latestSourceChapter = sourceChapters.slice(-1)[0] + export interface IPlaygroundState { + readonly sourceChapter: number readonly editorValue: string readonly replValue: string readonly context: Context @@ -88,9 +92,10 @@ export const defaultApplication: IApplicationState = { } export const defaultPlayground: IPlaygroundState = { + sourceChapter: latestSourceChapter, editorValue: '', replValue: '', - context: createContext(), + context: createContext(latestSourceChapter), output: [], isRunning: false } diff --git a/src/sagas/index.ts b/src/sagas/index.ts index 6b5fe39e69..10c4727f61 100644 --- a/src/sagas/index.ts +++ b/src/sagas/index.ts @@ -6,7 +6,7 @@ import { Context, interrupt, runInContext } from '../slang' import * as actions from '../actions' import * as actionTypes from '../actions/actionTypes' -import { showWarningMessage } from '../notification' +import { showSuccessMessage, showWarningMessage } from '../notification' function* evalCode(code: string, context: Context) { const { result, interrupted } = yield race({ @@ -44,6 +44,20 @@ function* interpreterSaga(): SagaIterator { yield put(actions.sendReplInputToOutput(code)) yield* evalCode(code, context) }) + + yield takeEvery(actionTypes.CHAPTER_SELECT, function*(action) { + const newChapter = parseInt((action as actionTypes.IAction).payload, 10) + const oldChapter = yield select((state: IState) => state.playground.sourceChapter) + if (newChapter !== oldChapter) { + yield put(actions.changeChapter(newChapter)) + yield put(actions.handleInterruptExecution()) + yield put(actions.clearContext()) + yield put(actions.clearReplOutput()) + yield call(showSuccessMessage, `Switched to Source \xa7${newChapter}`) + } else { + yield undefined + } + }) } function* mainSaga() { diff --git a/src/slang/__tests__/__snapshots__/index.ts.snap b/src/slang/__tests__/__snapshots__/index.ts.snap index c18adc1898..85933c03d2 100644 --- a/src/slang/__tests__/__snapshots__/index.ts.snap +++ b/src/slang/__tests__/__snapshots__/index.ts.snap @@ -6,10 +6,53 @@ Object { "value": ArrowClosure { "frame": Object { "environment": Object { + "Infinity": Infinity, + "NaN": NaN, "display": [Function], "error": [Function], + "math_E": 2.718281828459045, + "math_LN10": 2.302585092994046, + "math_LN2": 0.6931471805599453, + "math_LOG10E": 0.4342944819032518, + "math_LOG2E": 1.4426950408889634, "math_PI": 3.141592653589793, + "math_SQRT1_2": 0.7071067811865476, + "math_SQRT2": 1.4142135623730951, + "math_abs": [Function], + "math_acos": [Function], + "math_acosh": [Function], + "math_asin": [Function], + "math_asinh": [Function], + "math_atan": [Function], + "math_atan2": [Function], + "math_atanh": [Function], + "math_cbrt": [Function], + "math_ceil": [Function], + "math_clz32": [Function], + "math_cos": [Function], + "math_cosh": [Function], + "math_exp": [Function], + "math_expm1": [Function], + "math_floor": [Function], + "math_fround": [Function], + "math_hypot": [Function], + "math_imul": [Function], + "math_log": [Function], + "math_log10": [Function], + "math_log1p": [Function], + "math_log2": [Function], + "math_max": [Function], + "math_min": [Function], + "math_pow": [Function], + "math_random": [Function], + "math_round": [Function], + "math_sign": [Function], + "math_sin": [Function], + "math_sinh": [Function], "math_sqrt": [Function], + "math_tan": [Function], + "math_tanh": [Function], + "math_trunc": [Function], "parse_int": [Function], "prompt": [Function], "runtime": [Function], @@ -66,10 +109,53 @@ exports[`Arrow function definition returns itself 2`] = ` ArrowClosure { "frame": Object { "environment": Object { + "Infinity": Infinity, + "NaN": NaN, "display": [Function], "error": [Function], + "math_E": 2.718281828459045, + "math_LN10": 2.302585092994046, + "math_LN2": 0.6931471805599453, + "math_LOG10E": 0.4342944819032518, + "math_LOG2E": 1.4426950408889634, "math_PI": 3.141592653589793, + "math_SQRT1_2": 0.7071067811865476, + "math_SQRT2": 1.4142135623730951, + "math_abs": [Function], + "math_acos": [Function], + "math_acosh": [Function], + "math_asin": [Function], + "math_asinh": [Function], + "math_atan": [Function], + "math_atan2": [Function], + "math_atanh": [Function], + "math_cbrt": [Function], + "math_ceil": [Function], + "math_clz32": [Function], + "math_cos": [Function], + "math_cosh": [Function], + "math_exp": [Function], + "math_expm1": [Function], + "math_floor": [Function], + "math_fround": [Function], + "math_hypot": [Function], + "math_imul": [Function], + "math_log": [Function], + "math_log10": [Function], + "math_log1p": [Function], + "math_log2": [Function], + "math_max": [Function], + "math_min": [Function], + "math_pow": [Function], + "math_random": [Function], + "math_round": [Function], + "math_sign": [Function], + "math_sin": [Function], + "math_sinh": [Function], "math_sqrt": [Function], + "math_tan": [Function], + "math_tanh": [Function], + "math_trunc": [Function], "parse_int": [Function], "prompt": [Function], "runtime": [Function], diff --git a/src/slang/createContext.ts b/src/slang/createContext.ts index 45921567b0..d958b515a9 100644 --- a/src/slang/createContext.ts +++ b/src/slang/createContext.ts @@ -17,8 +17,8 @@ const createEmptyRuntime = () => ({ nodes: [] }) -export const createEmptyContext = (week: number): Context => ({ - week, +export const createEmptyContext = (chapter: number): Context => ({ + chapter, errors: [], cfg: createEmptyCFG(), runtime: createEmptyRuntime() @@ -56,24 +56,15 @@ export const importExternals = (context: Context, externals: string[]) => { export const importBuiltins = (context: Context) => { ensureGlobalEnvironmentExist(context) - if (context.week >= 3) { - defineSymbol(context, 'math_PI', Math.PI) - defineSymbol(context, 'math_sqrt', Math.sqrt) + if (context.chapter >= 1) { 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) - + defineSymbol(context, 'NaN', NaN) + defineSymbol(context, 'Infinity', Infinity) // Define all Math libraries const objs = Object.getOwnPropertyNames(Math) for (const i in objs) { @@ -88,14 +79,15 @@ export const importBuiltins = (context: Context) => { } } - if (context.week >= 5) { - defineSymbol(context, 'list', list.list) + if (context.chapter >= 2) { + // List library 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, 'is_empty_list', list.is_empty_list) + defineSymbol(context, 'is_list', list.is_list) + defineSymbol(context, 'list', list.list) defineSymbol(context, 'length', list.length) defineSymbol(context, 'map', list.map) defineSymbol(context, 'build_list', list.build_list) @@ -106,12 +98,20 @@ export const importBuiltins = (context: Context) => { 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) + defineSymbol(context, 'equal', list.equal) + } + + if (context.chapter >= Infinity) { + // previously week 4 + defineSymbol(context, 'alert', alert) + defineSymbol(context, 'math_floor', Math.floor) + defineSymbol(context, 'timed', misc.timed) + // previously week 5 + defineSymbol(context, 'assoc', list.assoc) if (window.hasOwnProperty('ListVisualizer')) { defineSymbol(context, 'draw', (window as any).ListVisualizer.draw) } else { @@ -119,22 +119,19 @@ export const importBuiltins = (context: Context) => { throw new Error('List visualizer is not enabled') }) } - } - if (context.week >= 6) { + // previously week 6 defineSymbol(context, 'is_number', misc.is_number) - } - if (context.week >= 8) { + // previously week 8 defineSymbol(context, 'undefined', undefined) defineSymbol(context, 'set_head', list.set_head) defineSymbol(context, 'set_tail', list.set_tail) - } - if (context.week >= 9) { + // previously week 9 defineSymbol(context, 'array_length', misc.array_length) } } -const createContext = (week = 3, externals = []) => { - const context = createEmptyContext(week) +const createContext = (chapter = 1, externals = []) => { + const context = createEmptyContext(chapter) importBuiltins(context) importExternals(context, externals) diff --git a/src/slang/interop.ts b/src/slang/interop.ts index 082b8a6985..45030dbd24 100644 --- a/src/slang/interop.ts +++ b/src/slang/interop.ts @@ -1,7 +1,7 @@ import { generate } from 'astring' import { MAX_LIST_DISPLAY_LENGTH } from './constants' import { apply } from './interpreter' -import { Closure, Context, Value } from './types' +import { ArrowClosure, Closure, Context, Value } from './types' export const closureToJS = (value: Value, context: Context, klass: string) => { function DummyClass(this: Value) { @@ -30,7 +30,7 @@ export const closureToJS = (value: Value, context: Context, klass: string) => { } export const toJS = (value: Value, context: Context, klass?: string) => { - if (value instanceof Closure) { + if (value instanceof Closure || value instanceof ArrowClosure) { return value.fun } else { return value diff --git a/src/slang/parser.ts b/src/slang/parser.ts index e975b8c564..dcfa02e1e0 100644 --- a/src/slang/parser.ts +++ b/src/slang/parser.ts @@ -10,7 +10,7 @@ import { Context, ErrorSeverity, ErrorType, SourceError } from './types' // tslint:disable-next-line:interface-name export interface ParserOptions { - week: number + chapter: number } export class DisallowedConstructError implements SourceError { @@ -133,7 +133,7 @@ for (const type of Object.keys(syntaxTypes)) { usages: [] } context.cfg.edges[id] = [] - if (syntaxTypes[node.type] > context.week) { + if (syntaxTypes[node.type] > context.chapter) { context.errors.push(new DisallowedConstructError(node)) } } @@ -167,7 +167,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.chapter >= rule.disableOn) { return } const checker = rule.checkers[key] diff --git a/src/slang/syntaxTypes.ts b/src/slang/syntaxTypes.ts index 0efe756b42..2ebbfb2c54 100644 --- a/src/slang/syntaxTypes.ts +++ b/src/slang/syntaxTypes.ts @@ -1,42 +1,40 @@ 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, + // Chapter 1 + Program: 1, + ExpressionStatement: 1, + IfStatement: 1, + FunctionDeclaration: 1, + VariableDeclaration: 1, + ReturnStatement: 1, + CallExpression: 1, + UnaryExpression: 1, + BinaryExpression: 1, + LogicalExpression: 1, + ConditionalExpression: 1, + FunctionExpression: 1, + ArrowFunctionExpression: 1, + Identifier: 1, + Literal: 1, + + // Chapter 2 + ArrayExpression: 2, // Week 5 EmptyStatement: 5, - ArrayExpression: 5, - - // Week 8 + // preivously Week 8 AssignmentExpression: 8, WhileStatement: 8, - - // Week 9 + // previously Week 9 ForStatement: 9, BreakStatement: 9, ContinueStatement: 9, MemberExpression: 9, - - // Week 10 + // previously Week 10 ThisExpression: 10, ObjectExpression: 10, Property: 10, UpdateExpression: 10, NewExpression: 10, - // Disallowed Forever SwitchStatement: Infinity, DebuggerStatement: Infinity, diff --git a/src/slang/types.ts b/src/slang/types.ts index 6a9c186597..338f44f985 100644 --- a/src/slang/types.ts +++ b/src/slang/types.ts @@ -93,7 +93,7 @@ export interface TypeError extends SourceError { export interface Context { /** The source version used */ - week: number + chapter: number /** All the errors gathered */ errors: SourceError[]