Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions editor/decoration.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import {outputLinesField} from "./outputLines";
import {RangeSetBuilder} from "@codemirror/state";

const highlight = Decoration.line({attributes: {class: "cm-output-line"}});
const errorHighlight = Decoration.line({attributes: {class: "cm-output-line cm-error-line"}});
// const linePrefix = Decoration.mark({attributes: {class: "cm-output-line-prefix"}});
// const lineContent = Decoration.mark({attributes: {class: "cm-output-line-content"}});

function createWidgets(lines) {
const builder = new RangeSetBuilder();
for (const {from, to} of lines) {
builder.add(from, from, highlight);
for (const {from, type} of lines) {
if (type === "output") builder.add(from, from, highlight);
else if (type === "error") builder.add(from, from, errorHighlight);
// builder.add(from, from + 3, linePrefix);
// builder.add(from + 4, to, lineContent);
}
Expand Down
11 changes: 11 additions & 0 deletions editor/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,17 @@
/*background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3E%3Cpath fill='%237d7d7d' fill-opacity='0.4' d='M1 3h1v1H1V3zm2-2h1v1H3V1z'%3E%3C/path%3E%3C/svg%3E");*/
}

/* We can't see background color, which is conflict with selection background color.
* So we use svg pattern to simulate the background color.
*/
.cm-output-line.cm-error-line {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4' %3E%3Cpath fill='%23ff0000' fill-opacity='0.4' d='M1 3h1v1H1V3zm2-2h1v1H3V1z'%3E%3C/path%3E%3C/svg%3E");
}

.cm-output-line.cm-error-line span {
color: #c83f30 !important;
}

.cm-doc-tag span {
color: #cf222e;
}
Expand Down
16 changes: 14 additions & 2 deletions editor/outputLines.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import {syntaxTree} from "@codemirror/language";
import {StateField} from "@codemirror/state";
import {OUTPUT_MARK} from "../runtime/constant.js";
import {OUTPUT_MARK, ERROR_MARK} from "../runtime/constant.js";

const OUTPUT_MARK_CODE_POINT = OUTPUT_MARK.codePointAt(0);

const ERROR_MARK_CODE_POINT = ERROR_MARK.codePointAt(0);

/** @type {StateField<{number: number, from: number, to: number}[]>} */
export const outputLinesField = StateField.define({
create(state) {
Expand All @@ -25,11 +27,21 @@ function computeLineNumbers(state) {
if (node.name === "LineComment" && node.node.parent.name === "Script") {
// Check if the line comment covers the entire line.
const line = state.doc.lineAt(node.from);
if (line.from === node.from && line.to === node.to && line.text.codePointAt(2) === OUTPUT_MARK_CODE_POINT) {
if (line.from !== node.from || line.to !== node.to) return;
if (line.text.codePointAt(2) === OUTPUT_MARK_CODE_POINT) {
lineNumbers.push({
number: line.number,
from: line.from,
to: line.to,
type: "output",
});
}
if (line.text.codePointAt(2) === ERROR_MARK_CODE_POINT) {
lineNumbers.push({
number: line.number,
from: line.from,
to: line.to,
type: "error",
});
}
}
Expand Down
2 changes: 2 additions & 0 deletions runtime/constant.js
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export const OUTPUT_MARK = "➜";

export const ERROR_MARK = "✗";
31 changes: 23 additions & 8 deletions runtime/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import {parse} from "acorn";
import {group} from "d3-array";
import {dispatch as d3Dispatch} from "d3-dispatch";
import * as stdlib from "./stdlib.js";
import {OUTPUT_MARK} from "./constant.js";
import {OUTPUT_MARK, ERROR_MARK} from "./constant.js";

const PREFIX = `//${OUTPUT_MARK}`;
const OUTPUT_PREFIX = `//${OUTPUT_MARK}`;

const ERROR_PREFIX = `//${ERROR_MARK}`;

const BUILTINS = {
recho: () => stdlib,
Expand All @@ -17,6 +19,10 @@ function uid() {
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
}

function isError(value) {
return value instanceof Error;
}

function safeEval(code, inputs) {
const body = `const foo = ${code}; return foo(${inputs.join(",")})`;
const fn = new Function(...inputs, body);
Expand Down Expand Up @@ -46,10 +52,18 @@ function inspect(value, {limit = 200, quote = "double", indent = null} = {}) {
return string;
}

function format(value, options) {
function format(value, options, prefix) {
const string = inspect(value, options);
const lines = string.split("\n");
return lines.map((line) => `${PREFIX} ${line}`).join("\n");
return lines.map((line) => `${prefix} ${line}`).join("\n");
}

function formatOutput(value, options) {
return format(value, options, OUTPUT_PREFIX);
}

function formatError(value, options) {
return format(value, options, ERROR_PREFIX);
}

export function createRuntime(initialCode) {
Expand All @@ -72,7 +86,8 @@ export function createRuntime(initialCode) {
const start = node.start;
const {values} = node.state;
if (values.length) {
const output = values.map(({value, options}) => format(value, options)).join("\n") + "\n";
const f = (v, o) => (isError(v) ? formatError(v, o) : formatOutput(v, o));
const output = values.map(({value, options}) => f(value, options)).join("\n") + "\n";
changes.push({from: start, insert: output});
}
}
Expand Down Expand Up @@ -127,7 +142,7 @@ export function createRuntime(initialCode) {
} catch (error) {
console.error(error);
const changes = removeChanges(code);
const errorMsg = format(error) + "\n";
const errorMsg = formatError(error) + "\n";
changes.push({from: 0, insert: errorMsg});
dispatch(changes);
return null;
Expand All @@ -140,7 +155,7 @@ export function createRuntime(initialCode) {
} catch (error) {
console.error(error);
const changes = removeChanges(code);
const errorMsg = format(error) + "\n";
const errorMsg = formatError(error) + "\n";
changes.push({from: 0, insert: errorMsg});
dispatch(changes);
return null;
Expand All @@ -153,7 +168,7 @@ export function createRuntime(initialCode) {
const oldOutputs = code
.split("\n")
.map((l, i) => [l, i])
.filter(([l]) => l.startsWith(PREFIX))
.filter(([l]) => l.startsWith(OUTPUT_PREFIX) || l.startsWith(ERROR_PREFIX))
.map(([_, i]) => i);

const lineOf = (i) => {
Expand Down
6 changes: 5 additions & 1 deletion test/js/runtime-error.js
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
export const runtimeError = `add(1, 2);`;
export const runtimeError = `const add = (a, b) => a + b;

ad(1, 2);

echo(add(1, 2));`;
6 changes: 5 additions & 1 deletion test/js/syntax-error.js
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
export const syntaxError = `function add();`;
export const syntaxError = `const a = 1;

function add();

echo(1 + 2);`;
9 changes: 7 additions & 2 deletions test/output/runtimeError.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
//➜ { [RuntimeError: add is not defined] input: "add" }
add(1, 2);
const add = (a, b) => a + b;

//✗ { [RuntimeError: ad is not defined] input: "ad" }
ad(1, 2);

//➜ 3
echo(add(1, 2));
8 changes: 6 additions & 2 deletions test/output/syntaxError.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
//➜ { [SyntaxError: Unexpected token (1:14)] pos: 14, loc: Position { line: 1, column: 14 }, raisedAt: 15 }
function add();
//✗ { [SyntaxError: Unexpected token (3:14)] pos: 28, loc: Position { line: 3, column: 14 }, raisedAt: 29 }
const a = 1;

function add();

echo(1 + 2);
2 changes: 1 addition & 1 deletion test/output/syntaxError2.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
// [SyntaxError: Assignment to external variable 'a' (1:0)]
// [SyntaxError: Assignment to external variable 'a' (1:0)]
let a = 1; a = 2;