remove semi-colons

This commit is contained in:
Kurt Hutten IrevDev
2022-11-26 08:34:23 +11:00
parent ab9fb05e30
commit 48e59ac710
19 changed files with 1159 additions and 1122 deletions

View File

@ -27,18 +27,31 @@
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"eject": "react-scripts eject",
"fmt": "prettier --write ./src/**.{ts,tsx} && prettier --write ./src/**/*.{ts,tsx}"
},
"jest": {
"transformIgnorePatterns": [
"node_modules/(?!(three|allotment)/)"
]
},
"prettier": {
"trailingComma": "es5",
"tabWidth": 2,
"semi": false,
"singleQuote": true
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
],
"rules": {
"semi": [
"error",
"never"
]
}
},
"browserslist": {
"production": [
@ -56,6 +69,7 @@
"@types/three": "^0.146.0",
"autoprefixer": "^10.4.13",
"postcss": "^8.4.19",
"prettier": "^2.8.0",
"tailwindcss": "^3.2.4"
}
}

View File

@ -1,19 +1,19 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import App from "./App";
import React from 'react'
import { render, screen } from '@testing-library/react'
import App from './App'
let listener: ((rect: any) => void) | undefined = undefined;
(global as any).ResizeObserver = class ResizeObserver {
let listener: ((rect: any) => void) | undefined = undefined
;(global as any).ResizeObserver = class ResizeObserver {
constructor(ls: ((rect: any) => void) | undefined) {
listener = ls;
listener = ls
}
observe() {}
unobserve() {}
disconnect() {}
};
}
test("renders learn react link", () => {
render(<App />);
const linkElement = screen.getByText(/viewer/i);
expect(linkElement).toBeInTheDocument();
});
test('renders learn react link', () => {
render(<App />)
const linkElement = screen.getByText(/viewer/i)
expect(linkElement).toBeInTheDocument()
})

View File

@ -1,20 +1,20 @@
import { useRef, useState, useEffect } from "react";
import { Canvas } from "@react-three/fiber";
import { Allotment } from "allotment";
import { OrbitControls, OrthographicCamera } from "@react-three/drei";
import { lexer } from "./lang/tokeniser";
import { abstractSyntaxTree } from "./lang/abstractSyntaxTree";
import { executor } from "./lang/executor";
import { BufferGeometry } from "three";
import CodeMirror from "@uiw/react-codemirror";
import { javascript } from "@codemirror/lang-javascript";
import { ViewUpdate } from "@codemirror/view";
import { useRef, useState, useEffect } from 'react'
import { Canvas } from '@react-three/fiber'
import { Allotment } from 'allotment'
import { OrbitControls, OrthographicCamera } from '@react-three/drei'
import { lexer } from './lang/tokeniser'
import { abstractSyntaxTree } from './lang/abstractSyntaxTree'
import { executor } from './lang/executor'
import { BufferGeometry } from 'three'
import CodeMirror from '@uiw/react-codemirror'
import { javascript } from '@codemirror/lang-javascript'
import { ViewUpdate } from '@codemirror/view'
import {
lineHighlightField,
addLineHighlight,
} from "./editor/highlightextension";
import { useStore } from "./useStore";
import { isOverlapping } from "./lib/utils";
} from './editor/highlightextension'
import { useStore } from './useStore'
import { isOverlapping } from './lib/utils'
const _code = `sketch mySketch {
path myPath = lineTo(0,1)
@ -22,13 +22,13 @@ const _code = `sketch mySketch {
path rightPath = lineTo(1,0)
close()
}
show(mySketch)`;
show(mySketch)`
const OrrthographicCamera = OrthographicCamera as any;
const OrrthographicCamera = OrthographicCamera as any
function App() {
const cam = useRef();
const [code, setCode] = useState(_code);
const cam = useRef()
const [code, setCode] = useState(_code)
const { editorView, setEditorView, setSelectionRange, selectionRange } =
useStore(
({ editorView, setEditorView, setSelectionRange, selectionRange }) => ({
@ -37,31 +37,32 @@ function App() {
setSelectionRange,
selectionRange,
})
);
)
// const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => {
const onChange = (value: string, viewUpdate: ViewUpdate) => {
setCode(value);
setCode(value)
if (editorView) {
editorView?.dispatch({ effects: addLineHighlight.of([0, 0]) });
editorView?.dispatch({ effects: addLineHighlight.of([0, 0]) })
}
}; //, []);
} //, []);
const onUpdate = (viewUpdate: ViewUpdate) => {
if (!editorView) {
setEditorView(viewUpdate.view);
setEditorView(viewUpdate.view)
}
const range = viewUpdate.state.selection.ranges[0];
const isNoChange = range.from === selectionRange[0] && range.to === selectionRange[1]
const range = viewUpdate.state.selection.ranges[0]
const isNoChange =
range.from === selectionRange[0] && range.to === selectionRange[1]
if (isNoChange) return
setSelectionRange([range.from, range.to]);
};
setSelectionRange([range.from, range.to])
}
const [geoArray, setGeoArray] = useState<
{ geo: BufferGeometry; sourceRange: [number, number] }[]
>([]);
>([])
useEffect(() => {
try {
const tokens = lexer(code);
const ast = abstractSyntaxTree(tokens);
const programMemory = executor(ast);
const tokens = lexer(code)
const ast = abstractSyntaxTree(tokens)
const programMemory = executor(ast)
const geos: { geo: BufferGeometry; sourceRange: [number, number] }[] =
programMemory.root.mySketch
.map(
@ -69,17 +70,17 @@ function App() {
geo,
sourceRange,
}: {
geo: BufferGeometry;
sourceRange: [number, number];
geo: BufferGeometry
sourceRange: [number, number]
}) => ({ geo, sourceRange })
)
.filter((a: any) => !!a.geo);
setGeoArray(geos);
console.log(programMemory);
.filter((a: any) => !!a.geo)
setGeoArray(geos)
console.log(programMemory)
} catch (e) {
console.log(e);
console.log(e)
}
}, [code]);
}, [code])
return (
<div className="h-screen">
<Allotment>
@ -129,49 +130,49 @@ function App() {
</div>
</Allotment>
</div>
);
)
}
export default App;
export default App
function Line({
geo,
sourceRange,
}: {
geo: BufferGeometry;
sourceRange: [number, number];
geo: BufferGeometry
sourceRange: [number, number]
}) {
const { setHighlightRange, selectionRange } = useStore(
({ setHighlightRange, selectionRange }) => ({
setHighlightRange,
selectionRange,
})
);
)
// This reference will give us direct access to the mesh
const ref = useRef<BufferGeometry | undefined>() as any;
const [hovered, setHover] = useState(false);
const [editorCursor, setEditorCursor] = useState(false);
const ref = useRef<BufferGeometry | undefined>() as any
const [hovered, setHover] = useState(false)
const [editorCursor, setEditorCursor] = useState(false)
useEffect(() => {
const shouldHighlight = isOverlapping(sourceRange, selectionRange);
setEditorCursor(shouldHighlight);
}, [selectionRange, sourceRange]);
const shouldHighlight = isOverlapping(sourceRange, selectionRange)
setEditorCursor(shouldHighlight)
}, [selectionRange, sourceRange])
return (
<mesh
ref={ref}
onPointerOver={(event) => {
setHover(true);
setHighlightRange(sourceRange);
setHover(true)
setHighlightRange(sourceRange)
}}
onPointerOut={(event) => {
setHover(false);
setHighlightRange([0, 0]);
setHover(false)
setHighlightRange([0, 0])
}}
>
<primitive object={geo} />
<meshStandardMaterial
color={hovered ? "hotpink" : editorCursor ? "skyblue" : "orange"}
color={hovered ? 'hotpink' : editorCursor ? 'skyblue' : 'orange'}
/>
</mesh>
);
)
}

View File

@ -1,30 +1,30 @@
import { StateField, StateEffect } from "@codemirror/state";
import { EditorView, Decoration } from "@codemirror/view";
import { StateField, StateEffect } from '@codemirror/state'
import { EditorView, Decoration } from '@codemirror/view'
export { EditorView }
export const addLineHighlight = StateEffect.define<[number, number]>();
export const addLineHighlight = StateEffect.define<[number, number]>()
export const lineHighlightField = StateField.define({
create() {
return Decoration.none;
return Decoration.none
},
update(lines, tr) {
lines = lines.map(tr.changes);
lines = lines.map(tr.changes)
const deco = []
for (let e of tr.effects) {
if (e.is(addLineHighlight)) {
lines = Decoration.none;
lines = Decoration.none
const [from, to] = e.value
if (!(from === to && from === 0)) {
lines = lines.update({ add: [matchDeco.range(from, to)] });
lines = lines.update({ add: [matchDeco.range(from, to)] })
deco.push(matchDeco.range(from, to))
}
}
}
return lines;
return lines
},
provide: (f) => EditorView.decorations.from(f),
});
})
const matchDeco = Decoration.mark({class: "bg-yellow-200"})
const matchDeco = Decoration.mark({ class: 'bg-yellow-200' })

View File

@ -1,19 +1,17 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import React from 'react'
import ReactDOM from 'react-dom/client'
import './index.css'
import App from './App'
import reportWebVitals from './reportWebVitals'
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
)
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
reportWebVitals()

View File

@ -1,244 +1,244 @@
import { abstractSyntaxTree, findClosingBrace } from "./abstractSyntaxTree";
import { lexer } from "./tokeniser";
import { abstractSyntaxTree, findClosingBrace } from './abstractSyntaxTree'
import { lexer } from './tokeniser'
describe("findClosingBrace", () => {
test("finds the closing brace", () => {
const basic = "( hey )";
expect(findClosingBrace(lexer(basic), 0)).toBe(4);
describe('findClosingBrace', () => {
test('finds the closing brace', () => {
const basic = '( hey )'
expect(findClosingBrace(lexer(basic), 0)).toBe(4)
const handlesNonZeroIndex =
"(indexForBracketToRightOfThisIsTwo(shouldBeFour)AndNotThisSix)";
expect(findClosingBrace(lexer(handlesNonZeroIndex), 2)).toBe(4);
expect(findClosingBrace(lexer(handlesNonZeroIndex), 0)).toBe(6);
'(indexForBracketToRightOfThisIsTwo(shouldBeFour)AndNotThisSix)'
expect(findClosingBrace(lexer(handlesNonZeroIndex), 2)).toBe(4)
expect(findClosingBrace(lexer(handlesNonZeroIndex), 0)).toBe(6)
const handlesNested =
"{a{b{c(}d]}eathou athoeu tah u} thatOneToTheLeftIsLast }";
expect(findClosingBrace(lexer(handlesNested), 0)).toBe(18);
'{a{b{c(}d]}eathou athoeu tah u} thatOneToTheLeftIsLast }'
expect(findClosingBrace(lexer(handlesNested), 0)).toBe(18)
// throws when not started on a brace
expect(() => findClosingBrace(lexer(handlesNested), 1)).toThrow();
});
});
expect(() => findClosingBrace(lexer(handlesNested), 1)).toThrow()
})
})
describe("testing AST", () => {
test("test 5 + 6", () => {
const tokens = lexer("5 +6");
const result = abstractSyntaxTree(tokens);
describe('testing AST', () => {
test('test 5 + 6', () => {
const tokens = lexer('5 +6')
const result = abstractSyntaxTree(tokens)
expect(result).toEqual({
type: "Program",
type: 'Program',
start: 0,
end: 4,
body: [
{
type: "ExpressionStatement",
type: 'ExpressionStatement',
start: 0,
end: 4,
expression: {
type: "BinaryExpression",
type: 'BinaryExpression',
start: 0,
end: 4,
left: {
type: "Literal",
type: 'Literal',
start: 0,
end: 1,
value: 5,
raw: "5",
raw: '5',
},
operator: "+",
operator: '+',
right: {
type: "Literal",
type: 'Literal',
start: 3,
end: 4,
value: 6,
raw: "6",
raw: '6',
},
},
},
],
});
});
test("test const myVar = 5", () => {
const tokens = lexer("const myVar = 5");
const { body } = abstractSyntaxTree(tokens);
})
})
test('test const myVar = 5', () => {
const tokens = lexer('const myVar = 5')
const { body } = abstractSyntaxTree(tokens)
expect(body).toEqual([
{
type: "VariableDeclaration",
type: 'VariableDeclaration',
start: 0,
end: 15,
kind: "const",
kind: 'const',
declarations: [
{
type: "VariableDeclarator",
type: 'VariableDeclarator',
start: 6,
end: 15,
id: {
type: "Identifier",
type: 'Identifier',
start: 6,
end: 11,
name: "myVar",
name: 'myVar',
},
init: {
type: "Literal",
type: 'Literal',
start: 14,
end: 15,
value: 5,
raw: "5",
raw: '5',
},
},
],
},
]);
});
test("test multi-line", () => {
])
})
test('test multi-line', () => {
const code = `const myVar = 5
const newVar = myVar + 1
`;
const tokens = lexer(code);
const { body } = abstractSyntaxTree(tokens);
`
const tokens = lexer(code)
const { body } = abstractSyntaxTree(tokens)
expect(body).toEqual([
{
type: "VariableDeclaration",
type: 'VariableDeclaration',
start: 0,
end: 15,
kind: "const",
kind: 'const',
declarations: [
{
type: "VariableDeclarator",
type: 'VariableDeclarator',
start: 6,
end: 15,
id: {
type: "Identifier",
type: 'Identifier',
start: 6,
end: 11,
name: "myVar",
name: 'myVar',
},
init: {
type: "Literal",
type: 'Literal',
start: 14,
end: 15,
value: 5,
raw: "5",
raw: '5',
},
},
],
},
{
type: "VariableDeclaration",
type: 'VariableDeclaration',
start: 16,
end: 40,
kind: "const",
kind: 'const',
declarations: [
{
type: "VariableDeclarator",
type: 'VariableDeclarator',
start: 22,
end: 40,
id: {
type: "Identifier",
type: 'Identifier',
start: 22,
end: 28,
name: "newVar",
name: 'newVar',
},
init: {
type: "BinaryExpression",
type: 'BinaryExpression',
start: 31,
end: 40,
left: {
type: "Identifier",
type: 'Identifier',
start: 31,
end: 36,
name: "myVar",
name: 'myVar',
},
operator: "+",
operator: '+',
right: {
type: "Literal",
type: 'Literal',
start: 39,
end: 40,
value: 1,
raw: "1",
raw: '1',
},
},
},
],
},
]);
});
])
})
test('test using std function "log"', () => {
const code = `log(5, "hello", aIdentifier)`;
const tokens = lexer(code);
const { body } = abstractSyntaxTree(tokens);
const code = `log(5, "hello", aIdentifier)`
const tokens = lexer(code)
const { body } = abstractSyntaxTree(tokens)
expect(body).toEqual([
{
type: "ExpressionStatement",
type: 'ExpressionStatement',
start: 0,
end: 28,
expression: {
type: "CallExpression",
type: 'CallExpression',
start: 0,
end: 28,
callee: {
type: "Identifier",
type: 'Identifier',
start: 0,
end: 3,
name: "log",
name: 'log',
},
arguments: [
{
type: "Literal",
type: 'Literal',
start: 4,
end: 5,
value: 5,
raw: "5",
raw: '5',
},
{
type: "Literal",
type: 'Literal',
start: 7,
end: 14,
value: "hello",
value: 'hello',
raw: '"hello"',
},
{
type: "Identifier",
type: 'Identifier',
start: 16,
end: 27,
name: "aIdentifier",
name: 'aIdentifier',
},
],
optional: false,
},
},
]);
});
});
])
})
})
describe("testing function declaration", () => {
test("fn funcN = () => {}", () => {
const tokens = lexer("fn funcN = () => {}");
const { body } = abstractSyntaxTree(tokens);
describe('testing function declaration', () => {
test('fn funcN = () => {}', () => {
const tokens = lexer('fn funcN = () => {}')
const { body } = abstractSyntaxTree(tokens)
expect(body).toEqual([
{
type: "VariableDeclaration",
type: 'VariableDeclaration',
start: 0,
end: 19,
kind: "fn",
kind: 'fn',
declarations: [
{
type: "VariableDeclarator",
type: 'VariableDeclarator',
start: 3,
end: 19,
id: {
type: "Identifier",
type: 'Identifier',
start: 3,
end: 8,
name: "funcN",
name: 'funcN',
},
init: {
type: "FunctionExpression",
type: 'FunctionExpression',
start: 11,
end: 19,
id: null,
params: [],
body: {
type: "BlockStatement",
type: 'BlockStatement',
start: 17,
end: 19,
body: [],
@ -247,74 +247,74 @@ describe("testing function declaration", () => {
},
],
},
]);
});
test("fn funcN = (a, b) => {return a + b}", () => {
])
})
test('fn funcN = (a, b) => {return a + b}', () => {
const tokens = lexer(
["fn funcN = (a, b) => {", " return a + b", "}"].join("\n")
);
const { body } = abstractSyntaxTree(tokens);
['fn funcN = (a, b) => {', ' return a + b', '}'].join('\n')
)
const { body } = abstractSyntaxTree(tokens)
expect(body).toEqual([
{
type: "VariableDeclaration",
type: 'VariableDeclaration',
start: 0,
end: 39,
kind: "fn",
kind: 'fn',
declarations: [
{
type: "VariableDeclarator",
type: 'VariableDeclarator',
start: 3,
end: 39,
id: {
type: "Identifier",
type: 'Identifier',
start: 3,
end: 8,
name: "funcN",
name: 'funcN',
},
init: {
type: "FunctionExpression",
type: 'FunctionExpression',
start: 11,
end: 39,
id: null,
params: [
{
type: "Identifier",
type: 'Identifier',
start: 12,
end: 13,
name: "a",
name: 'a',
},
{
type: "Identifier",
type: 'Identifier',
start: 15,
end: 16,
name: "b",
name: 'b',
},
],
body: {
type: "BlockStatement",
type: 'BlockStatement',
start: 21,
end: 39,
body: [
{
type: "ReturnStatement",
type: 'ReturnStatement',
start: 25,
end: 37,
argument: {
type: "BinaryExpression",
type: 'BinaryExpression',
start: 32,
end: 37,
left: {
type: "Identifier",
type: 'Identifier',
start: 32,
end: 33,
name: "a",
name: 'a',
},
operator: "+",
operator: '+',
right: {
type: "Identifier",
type: 'Identifier',
start: 36,
end: 37,
name: "b",
name: 'b',
},
},
},
@ -324,75 +324,75 @@ describe("testing function declaration", () => {
},
],
},
]);
});
test("call expression assignment", () => {
])
})
test('call expression assignment', () => {
const tokens = lexer(
`fn funcN = (a, b) => { return a + b }
const myVar = funcN(1, 2)`
);
const { body } = abstractSyntaxTree(tokens);
)
const { body } = abstractSyntaxTree(tokens)
expect(body).toEqual([
{
type: "VariableDeclaration",
type: 'VariableDeclaration',
start: 0,
end: 37,
kind: "fn",
kind: 'fn',
declarations: [
{
type: "VariableDeclarator",
type: 'VariableDeclarator',
start: 3,
end: 37,
id: {
type: "Identifier",
type: 'Identifier',
start: 3,
end: 8,
name: "funcN",
name: 'funcN',
},
init: {
type: "FunctionExpression",
type: 'FunctionExpression',
start: 11,
end: 37,
id: null,
params: [
{
type: "Identifier",
type: 'Identifier',
start: 12,
end: 13,
name: "a",
name: 'a',
},
{
type: "Identifier",
type: 'Identifier',
start: 15,
end: 16,
name: "b",
name: 'b',
},
],
body: {
type: "BlockStatement",
type: 'BlockStatement',
start: 21,
end: 37,
body: [
{
type: "ReturnStatement",
type: 'ReturnStatement',
start: 23,
end: 35,
argument: {
type: "BinaryExpression",
type: 'BinaryExpression',
start: 30,
end: 35,
left: {
type: "Identifier",
type: 'Identifier',
start: 30,
end: 31,
name: "a",
name: 'a',
},
operator: "+",
operator: '+',
right: {
type: "Identifier",
type: 'Identifier',
start: 34,
end: 35,
name: "b",
name: 'b',
},
},
},
@ -403,45 +403,45 @@ const myVar = funcN(1, 2)`
],
},
{
type: "VariableDeclaration",
type: 'VariableDeclaration',
start: 38,
end: 63,
kind: "const",
kind: 'const',
declarations: [
{
type: "VariableDeclarator",
type: 'VariableDeclarator',
start: 44,
end: 63,
id: {
type: "Identifier",
type: 'Identifier',
start: 44,
end: 49,
name: "myVar",
name: 'myVar',
},
init: {
type: "CallExpression",
type: 'CallExpression',
start: 52,
end: 63,
callee: {
type: "Identifier",
type: 'Identifier',
start: 52,
end: 57,
name: "funcN",
name: 'funcN',
},
arguments: [
{
type: "Literal",
type: 'Literal',
start: 58,
end: 59,
value: 1,
raw: "1",
raw: '1',
},
{
type: "Literal",
type: 'Literal',
start: 61,
end: 62,
value: 2,
raw: "2",
raw: '2',
},
],
optional: false,
@ -449,87 +449,87 @@ const myVar = funcN(1, 2)`
},
],
},
]);
});
});
])
})
})
describe("structures specific to this lang", () => {
test("sketch", () => {
describe('structures specific to this lang', () => {
test('sketch', () => {
let code = `sketch mySketch {
path myPath = lineTo(0,1)
lineTo(1,1)
path rightPath = lineTo(1,0)
close()
}
`;
const tokens = lexer(code);
const { body } = abstractSyntaxTree(tokens);
`
const tokens = lexer(code)
const { body } = abstractSyntaxTree(tokens)
expect(body).toEqual([
{
type: "VariableDeclaration",
type: 'VariableDeclaration',
start: 0,
end: 102,
kind: "sketch",
kind: 'sketch',
declarations: [
{
type: "VariableDeclarator",
type: 'VariableDeclarator',
start: 7,
end: 102,
id: {
type: "Identifier",
type: 'Identifier',
start: 7,
end: 15,
name: "mySketch",
name: 'mySketch',
},
init: {
type: "SketchExpression",
type: 'SketchExpression',
start: 16,
end: 102,
body: {
type: "BlockStatement",
type: 'BlockStatement',
start: 16,
end: 102,
body: [
{
type: "VariableDeclaration",
type: 'VariableDeclaration',
start: 20,
end: 45,
kind: "path",
kind: 'path',
declarations: [
{
type: "VariableDeclarator",
type: 'VariableDeclarator',
start: 25,
end: 45,
id: {
type: "Identifier",
type: 'Identifier',
start: 25,
end: 31,
name: "myPath",
name: 'myPath',
},
init: {
type: "CallExpression",
type: 'CallExpression',
start: 34,
end: 45,
callee: {
type: "Identifier",
type: 'Identifier',
start: 34,
end: 40,
name: "lineTo",
name: 'lineTo',
},
arguments: [
{
type: "Literal",
type: 'Literal',
start: 41,
end: 42,
value: 0,
raw: "0",
raw: '0',
},
{
type: "Literal",
type: 'Literal',
start: 43,
end: 44,
value: 1,
raw: "1",
raw: '1',
},
],
optional: false,
@ -538,78 +538,78 @@ describe("structures specific to this lang", () => {
],
},
{
type: "ExpressionStatement",
type: 'ExpressionStatement',
start: 48,
end: 59,
expression: {
type: "CallExpression",
type: 'CallExpression',
start: 48,
end: 59,
callee: {
type: "Identifier",
type: 'Identifier',
start: 48,
end: 54,
name: "lineTo",
name: 'lineTo',
},
arguments: [
{
type: "Literal",
type: 'Literal',
start: 55,
end: 56,
value: 1,
raw: "1",
raw: '1',
},
{
type: "Literal",
type: 'Literal',
start: 57,
end: 58,
value: 1,
raw: "1",
raw: '1',
},
],
optional: false,
},
},
{
type: "VariableDeclaration",
type: 'VariableDeclaration',
start: 62,
end: 90,
kind: "path",
kind: 'path',
declarations: [
{
type: "VariableDeclarator",
type: 'VariableDeclarator',
start: 67,
end: 90,
id: {
type: "Identifier",
type: 'Identifier',
start: 67,
end: 76,
name: "rightPath",
name: 'rightPath',
},
init: {
type: "CallExpression",
type: 'CallExpression',
start: 79,
end: 90,
callee: {
type: "Identifier",
type: 'Identifier',
start: 79,
end: 85,
name: "lineTo",
name: 'lineTo',
},
arguments: [
{
type: "Literal",
type: 'Literal',
start: 86,
end: 87,
value: 1,
raw: "1",
raw: '1',
},
{
type: "Literal",
type: 'Literal',
start: 88,
end: 89,
value: 0,
raw: "0",
raw: '0',
},
],
optional: false,
@ -618,18 +618,18 @@ describe("structures specific to this lang", () => {
],
},
{
type: "ExpressionStatement",
type: 'ExpressionStatement',
start: 93,
end: 100,
expression: {
type: "CallExpression",
type: 'CallExpression',
start: 93,
end: 100,
callee: {
type: "Identifier",
type: 'Identifier',
start: 93,
end: 98,
name: "close",
name: 'close',
},
arguments: [],
optional: false,
@ -641,6 +641,6 @@ describe("structures specific to this lang", () => {
},
],
},
]);
});
});
])
})
})

File diff suppressed because it is too large Load Diff

View File

@ -1,40 +1,42 @@
import { BoxGeometry, SphereGeometry, BufferGeometry } from 'three'
import {mergeBufferGeometries} from 'three/examples/jsm/utils/BufferGeometryUtils'
import { mergeBufferGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils'
export function lineGeo({
from,
to,
}: {
from: [number, number, number];
to: [number, number, number];
from: [number, number, number]
to: [number, number, number]
}): BufferGeometry {
const sq = (a: number): number => a * a;
const sq = (a: number): number => a * a
const center = [
(from[0] + to[0]) / 2,
(from[1] + to[1]) / 2,
(from[2] + to[2]) / 2,
];
]
const Hypotenuse3d = Math.sqrt(
sq(from[0] - to[0]) + sq(from[1] - to[1]) + sq(from[2] - to[2])
);
const ang1 = Math.atan2(from[2] - to[2], from[0] - to[0]);
const Hypotenuse2d = Math.sqrt(sq(from[0] - to[0]) + sq(from[2] - to[2]));
const ang2 = Math.abs(Math.atan((to[1] - from[1])/ Hypotenuse2d))*Math.sign(to[1] - from[1])*(Math.sign(to[0] - from[0])||1);
)
const ang1 = Math.atan2(from[2] - to[2], from[0] - to[0])
const Hypotenuse2d = Math.sqrt(sq(from[0] - to[0]) + sq(from[2] - to[2]))
const ang2 =
Math.abs(Math.atan((to[1] - from[1]) / Hypotenuse2d)) *
Math.sign(to[1] - from[1]) *
(Math.sign(to[0] - from[0]) || 1)
// create BoxGeometry with size [Hypotenuse3d, 0.1, 0.1] centered at center, with rotation of [0, ang1, ang2]
const lineBody = new BoxGeometry(Hypotenuse3d, 0.1, 0.1);
const lineBody = new BoxGeometry(Hypotenuse3d, 0.1, 0.1)
lineBody.rotateY(ang1)
lineBody.rotateZ(ang2)
lineBody.translate(center[0], center[1], center[2]);
lineBody.translate(center[0], center[1], center[2])
// create line end balls with SphereGeometry at `to` and `from` with radius of 0.15
const lineEnd1 = new SphereGeometry(0.15);
lineEnd1.translate(to[0], to[1], to[2]);
const lineEnd1 = new SphereGeometry(0.15)
lineEnd1.translate(to[0], to[1], to[2])
// const lineEnd2 = new SphereGeometry(0.15);
// lineEnd2.translate(from[0], from[1], from[2])
// group all three geometries
return mergeBufferGeometries([lineBody, lineEnd1]);
return mergeBufferGeometries([lineBody, lineEnd1])
// return mergeBufferGeometries([lineBody, lineEnd1, lineEnd2]);
}

View File

@ -1,58 +1,58 @@
import fs from "node:fs";
import fs from 'node:fs'
import { abstractSyntaxTree } from "./abstractSyntaxTree";
import { lexer } from "./tokeniser";
import { executor, ProgramMemory } from "./executor";
import { abstractSyntaxTree } from './abstractSyntaxTree'
import { lexer } from './tokeniser'
import { executor, ProgramMemory } from './executor'
describe("test", () => {
it("test assigning two variables, the second summing with the first", () => {
describe('test', () => {
it('test assigning two variables, the second summing with the first', () => {
const code = `const myVar = 5
const newVar = myVar + 1`;
const { root } = exe(code);
expect(root.myVar).toBe(5);
expect(root.newVar).toBe(6);
});
it("test assigning a var with a string", () => {
const code = `const myVar = "a str"`;
const { root } = exe(code);
expect(root.myVar).toBe("a str");
});
it("test assigning a var by cont concatenating two strings string", () => {
const newVar = myVar + 1`
const { root } = exe(code)
expect(root.myVar).toBe(5)
expect(root.newVar).toBe(6)
})
it('test assigning a var with a string', () => {
const code = `const myVar = "a str"`
const { root } = exe(code)
expect(root.myVar).toBe('a str')
})
it('test assigning a var by cont concatenating two strings string', () => {
const code = fs.readFileSync(
"./src/lang/testExamples/variableDeclaration.cado",
"utf-8"
);
const { root } = exe(code);
expect(root.myVar).toBe("a str another str");
});
it("test with function call", () => {
'./src/lang/testExamples/variableDeclaration.cado',
'utf-8'
)
const { root } = exe(code)
expect(root.myVar).toBe('a str another str')
})
it('test with function call', () => {
const code = `
const myVar = "hello"
log(5, myVar)`;
log(5, myVar)`
const programMemoryOverride = {
log: jest.fn(),
};
}
const { root } = executor(abstractSyntaxTree(lexer(code)), {
root: programMemoryOverride,
_sketch: [],
});
expect(root.myVar).toBe("hello");
expect(programMemoryOverride.log).toHaveBeenCalledWith(5, "hello");
});
it("fn funcN = () => {}", () => {
})
expect(root.myVar).toBe('hello')
expect(programMemoryOverride.log).toHaveBeenCalledWith(5, 'hello')
})
it('fn funcN = () => {}', () => {
const { root } = exe(
[
"fn funcN = (a, b) => {",
" return a + b",
"}",
"const theVar = 60",
"const magicNum = funcN(9, theVar)",
].join("\n")
);
expect(root.theVar).toBe(60);
expect(root.magicNum).toBe(69);
});
it("sketch declaration", () => {
'fn funcN = (a, b) => {',
' return a + b',
'}',
'const theVar = 60',
'const magicNum = funcN(9, theVar)',
].join('\n')
)
expect(root.theVar).toBe(60)
expect(root.magicNum).toBe(69)
})
it('sketch declaration', () => {
let code = `sketch mySketch {
path myPath = lineTo(0,1)
lineTo(1,1)
@ -60,33 +60,33 @@ log(5, myVar)`;
close()
}
show(mySketch)
`;
const { root, return: _return } = exe(code);
`
const { root, return: _return } = exe(code)
expect(
root.mySketch.map(({ previousPath, geo, ...rest }: any) => rest)
).toEqual([
{ type: "base", from: [0, 0] },
{ type: "toPoint", to: [0, 1], sourceRange: [25, 45], name: "myPath" },
{ type: "toPoint", to: [1, 1], sourceRange: [48, 59] },
{ type: "toPoint", to: [1, 0], sourceRange: [67, 90], name: "rightPath" },
{ type: 'base', from: [0, 0] },
{ type: 'toPoint', to: [0, 1], sourceRange: [25, 45], name: 'myPath' },
{ type: 'toPoint', to: [1, 1], sourceRange: [48, 59] },
{ type: 'toPoint', to: [1, 0], sourceRange: [67, 90], name: 'rightPath' },
{
type: "close",
firstPath: { type: "base", from: [0, 0] },
type: 'close',
firstPath: { type: 'base', from: [0, 0] },
sourceRange: [93, 100],
},
]);
expect(root.mySketch[0]).toEqual(root.mySketch[4].firstPath);
])
expect(root.mySketch[0]).toEqual(root.mySketch[4].firstPath)
// hmm not sure what handle the "show" function
expect(_return).toEqual([
{
type: "Identifier",
type: 'Identifier',
start: 108,
end: 116,
name: "mySketch",
name: 'mySketch',
},
]);
});
});
])
})
})
// helpers
@ -94,7 +94,7 @@ function exe(
code: string,
programMemory: ProgramMemory = { root: {}, _sketch: [] }
) {
const tokens = lexer(code);
const ast = abstractSyntaxTree(tokens);
return executor(ast, programMemory);
const tokens = lexer(code)
const ast = abstractSyntaxTree(tokens)
return executor(ast, programMemory)
}

View File

@ -1,16 +1,16 @@
import { Program, BinaryPart, BinaryExpression } from "./abstractSyntaxTree";
import {Path, sketchFns } from "./sketch";
import { Program, BinaryPart, BinaryExpression } from './abstractSyntaxTree'
import { Path, sketchFns } from './sketch'
export interface ProgramMemory {
root: { [key: string]: any };
return?: any;
_sketch: Path[];
root: { [key: string]: any }
return?: any
_sketch: Path[]
}
export const executor = (
node: Program,
programMemory: ProgramMemory = { root: {}, _sketch: [] },
options: { bodyType: "root" | "sketch" | "block" } = { bodyType: "root" }
options: { bodyType: 'root' | 'sketch' | 'block' } = { bodyType: 'root' }
): any => {
const _programMemory: ProgramMemory = {
root: {
@ -18,33 +18,33 @@ export const executor = (
},
_sketch: [],
return: programMemory.return,
};
const { body } = node;
}
const { body } = node
body.forEach((statement) => {
if (statement.type === "VariableDeclaration") {
if (statement.type === 'VariableDeclaration') {
statement.declarations.forEach((declaration) => {
const variableName = declaration.id.name;
if (declaration.init.type === "Literal") {
_programMemory.root[variableName] = declaration.init.value;
} else if (declaration.init.type === "BinaryExpression") {
const variableName = declaration.id.name
if (declaration.init.type === 'Literal') {
_programMemory.root[variableName] = declaration.init.value
} else if (declaration.init.type === 'BinaryExpression') {
_programMemory.root[variableName] = getBinaryExpressionResult(
declaration.init,
_programMemory
);
} else if (declaration.init.type === "SketchExpression") {
const sketchInit = declaration.init;
)
} else if (declaration.init.type === 'SketchExpression') {
const sketchInit = declaration.init
const fnMemory: ProgramMemory = {
root: {
..._programMemory.root,
},
_sketch: [],
};
}
const { _sketch } = executor(sketchInit.body, fnMemory, {
bodyType: "sketch",
});
_programMemory.root[variableName] = _sketch;
} else if (declaration.init.type === "FunctionExpression") {
const fnInit = declaration.init;
bodyType: 'sketch',
})
_programMemory.root[variableName] = _sketch
} else if (declaration.init.type === 'FunctionExpression') {
const fnInit = declaration.init
_programMemory.root[declaration.id.name] = (...args: any[]) => {
const fnMemory: ProgramMemory = {
@ -52,106 +52,108 @@ export const executor = (
..._programMemory.root,
},
_sketch: [],
};
}
if (args.length > fnInit.params.length) {
throw new Error(
`Too many arguments passed to function ${declaration.id.name}`
);
)
} else if (args.length < fnInit.params.length) {
throw new Error(
`Too few arguments passed to function ${declaration.id.name}`
);
)
}
fnInit.params.forEach((param, index) => {
fnMemory.root[param.name] = args[index];
});
return executor(fnInit.body, fnMemory, {bodyType: 'block'}).return;
};
} else if (declaration.init.type === "CallExpression") {
const fnName = declaration.init.callee.name;
const fnArgs = declaration.init.arguments.map((arg) => {
if (arg.type === "Literal") {
return arg.value;
} else if (arg.type === "Identifier") {
return _programMemory.root[arg.name];
fnMemory.root[param.name] = args[index]
})
return executor(fnInit.body, fnMemory, { bodyType: 'block' }).return
}
});
if ("lineTo" === fnName || "close" === fnName) {
if (options.bodyType !== "sketch") {
} else if (declaration.init.type === 'CallExpression') {
const fnName = declaration.init.callee.name
const fnArgs = declaration.init.arguments.map((arg) => {
if (arg.type === 'Literal') {
return arg.value
} else if (arg.type === 'Identifier') {
return _programMemory.root[arg.name]
}
})
if ('lineTo' === fnName || 'close' === fnName) {
if (options.bodyType !== 'sketch') {
throw new Error(
`Cannot call ${fnName} outside of a sketch declaration`
);
)
}
const result = sketchFns[fnName](
_programMemory,
variableName,
[declaration.start, declaration.end],
...fnArgs
);
_programMemory._sketch = result.programMemory._sketch;
_programMemory.root[variableName] = result.currentPath;
)
_programMemory._sketch = result.programMemory._sketch
_programMemory.root[variableName] = result.currentPath
} else {
_programMemory.root[variableName] = _programMemory.root[fnName](
...fnArgs
);
)
}
}
});
} else if (statement.type === "ExpressionStatement") {
const expression = statement.expression;
if (expression.type === "CallExpression") {
const functionName = expression.callee.name;
})
} else if (statement.type === 'ExpressionStatement') {
const expression = statement.expression
if (expression.type === 'CallExpression') {
const functionName = expression.callee.name
const args = expression.arguments.map((arg) => {
if (arg.type === "Literal") {
return arg.value;
} else if (arg.type === "Identifier") {
return _programMemory.root[arg.name];
if (arg.type === 'Literal') {
return arg.value
} else if (arg.type === 'Identifier') {
return _programMemory.root[arg.name]
}
});
if ("lineTo" === functionName || "close" === functionName) {
if (options.bodyType !== "sketch") {
})
if ('lineTo' === functionName || 'close' === functionName) {
if (options.bodyType !== 'sketch') {
throw new Error(
`Cannot call ${functionName} outside of a sketch declaration`
);
)
}
const result = sketchFns[functionName](_programMemory, "", [statement.start, statement.end], ...args);
_programMemory._sketch = [...result.programMemory._sketch];
} else if("show" === functionName) {
if (options.bodyType !== "root") {
throw new Error(
`Cannot call ${functionName} outside of a root`
);
const result = sketchFns[functionName](
_programMemory,
'',
[statement.start, statement.end],
...args
)
_programMemory._sketch = [...result.programMemory._sketch]
} else if ('show' === functionName) {
if (options.bodyType !== 'root') {
throw new Error(`Cannot call ${functionName} outside of a root`)
}
_programMemory.return = expression.arguments;
_programMemory.return = expression.arguments
} else {
_programMemory.root[functionName](...args);
_programMemory.root[functionName](...args)
}
}
} else if (statement.type === "ReturnStatement") {
if (statement.argument.type === "BinaryExpression") {
} else if (statement.type === 'ReturnStatement') {
if (statement.argument.type === 'BinaryExpression') {
_programMemory.return = getBinaryExpressionResult(
statement.argument,
_programMemory
);
)
}
}
});
return _programMemory;
};
})
return _programMemory
}
function getBinaryExpressionResult(
expression: BinaryExpression,
programMemory: ProgramMemory
) {
const getVal = (part: BinaryPart) => {
if (part.type === "Literal") {
return part.value;
} else if (part.type === "Identifier") {
return programMemory.root[part.name];
if (part.type === 'Literal') {
return part.value
} else if (part.type === 'Identifier') {
return programMemory.root[part.name]
}
};
const left = getVal(expression.left);
const right = getVal(expression.right);
return left + right;
}
const left = getVal(expression.left)
const right = getVal(expression.right)
return left + right
}

View File

@ -1,5 +1,5 @@
import { ProgramMemory } from "./executor";
import { lineGeo } from "./engine";
import { ProgramMemory } from './executor'
import { lineGeo } from './engine'
import { BufferGeometry } from 'three'
type Coords2d = [number, number]
@ -7,75 +7,75 @@ type SourceRange = [number, number]
export type Path =
| {
type: "points";
name?: string;
from: Coords2d;
to: Coords2d;
geo: BufferGeometry;
sourceRange: SourceRange;
type: 'points'
name?: string
from: Coords2d
to: Coords2d
geo: BufferGeometry
sourceRange: SourceRange
}
| {
type: "horizontalLineTo";
name?: string;
x: number;
previousPath: Path;
geo: BufferGeometry;
sourceRange: SourceRange;
type: 'horizontalLineTo'
name?: string
x: number
previousPath: Path
geo: BufferGeometry
sourceRange: SourceRange
}
| {
type: "verticalLineTo";
name?: string;
y: number;
previousPath: Path;
geo: BufferGeometry;
sourceRange: SourceRange;
type: 'verticalLineTo'
name?: string
y: number
previousPath: Path
geo: BufferGeometry
sourceRange: SourceRange
}
| {
type: "toPoint";
name?: string;
to: Coords2d;
previousPath: Path;
geo: BufferGeometry;
sourceRange: SourceRange;
type: 'toPoint'
name?: string
to: Coords2d
previousPath: Path
geo: BufferGeometry
sourceRange: SourceRange
}
| {
type: "close";
name?: string;
firstPath: Path;
previousPath: Path;
geo: BufferGeometry;
sourceRange: SourceRange;
type: 'close'
name?: string
firstPath: Path
previousPath: Path
geo: BufferGeometry
sourceRange: SourceRange
}
| {
type: "base";
from: Coords2d;
};
type: 'base'
from: Coords2d
}
function addBasePath(programMemory: ProgramMemory) {
const base: Path = {
type: "base",
type: 'base',
from: [0, 0],
};
}
if (programMemory._sketch?.length === 0) {
return {
...programMemory,
_sketch: [base],
};
}
return programMemory;
}
return programMemory
}
interface PathReturn {
programMemory: ProgramMemory;
currentPath: Path;
programMemory: ProgramMemory
currentPath: Path
}
function getCoordsFromPaths(paths: Path[], index = 0): Coords2d {
const currentPath = paths[index]
if(!currentPath) {
if (!currentPath) {
return [0, 0]
}
if(currentPath.type === 'points' || currentPath.type === 'toPoint') {
if (currentPath.type === 'points' || currentPath.type === 'toPoint') {
return currentPath.to
} else if (currentPath.type === 'base') {
return currentPath.from
@ -86,33 +86,38 @@ function getCoordsFromPaths(paths: Path[], index = 0): Coords2d {
const pathBefore = getCoordsFromPaths(paths, index - 1)
return [pathBefore[0], currentPath.y]
}
return [0,0]
return [0, 0]
}
export const sketchFns = {
close: (programMemory: ProgramMemory, name: string = "", sourceRange: SourceRange): PathReturn => {
close: (
programMemory: ProgramMemory,
name: string = '',
sourceRange: SourceRange
): PathReturn => {
const lastPath = programMemory?._sketch?.[
programMemory?._sketch.length - 1
] as Path;
] as Path
let from = getCoordsFromPaths(programMemory?._sketch, programMemory?._sketch.length - 1)
const firstPath = programMemory?._sketch?.[0] as Path;
if (lastPath?.type === "base") {
throw new Error("Cannot close a base path");
let from = getCoordsFromPaths(
programMemory?._sketch,
programMemory?._sketch.length - 1
)
const firstPath = programMemory?._sketch?.[0] as Path
if (lastPath?.type === 'base') {
throw new Error('Cannot close a base path')
}
let to = getCoordsFromPaths(programMemory?._sketch, 0)
const newPath: Path = {
type: "close",
type: 'close',
firstPath,
previousPath: lastPath,
geo: lineGeo({from: [...from, 0], to: [...to, 0]}),
sourceRange
};
geo: lineGeo({ from: [...from, 0], to: [...to, 0] }),
sourceRange,
}
if (name) {
newPath.name = name;
newPath.name = name
}
return {
programMemory: {
@ -120,31 +125,34 @@ export const sketchFns = {
_sketch: [...(programMemory?._sketch || []), newPath],
},
currentPath: newPath,
};
}
},
lineTo: (
programMemory: ProgramMemory,
name: string = "",
name: string = '',
sourceRange: SourceRange,
...args: any[]
): PathReturn => {
const _programMemory = addBasePath(programMemory);
const [x, y] = args;
const _programMemory = addBasePath(programMemory)
const [x, y] = args
if (!_programMemory._sketch) {
throw new Error("No sketch to draw on");
throw new Error('No sketch to draw on')
}
const lastPath: Path =
_programMemory._sketch[_programMemory._sketch.length - 1];
let from = getCoordsFromPaths(programMemory?._sketch, programMemory?._sketch.length - 1)
_programMemory._sketch[_programMemory._sketch.length - 1]
let from = getCoordsFromPaths(
programMemory?._sketch,
programMemory?._sketch.length - 1
)
const currentPath: Path = {
type: "toPoint",
type: 'toPoint',
to: [x, y],
previousPath: lastPath,
geo: lineGeo({from: [...from, 0], to: [x, y, 0]}),
sourceRange
};
geo: lineGeo({ from: [...from, 0], to: [x, y, 0] }),
sourceRange,
}
if (name) {
currentPath.name = name;
currentPath.name = name
}
return {
programMemory: {
@ -152,6 +160,6 @@ export const sketchFns = {
_sketch: [...(_programMemory._sketch || []), currentPath],
},
currentPath,
};
}
},
};
}

View File

@ -10,148 +10,148 @@ import {
isWord,
isComma,
lexer,
} from "./tokeniser";
} from './tokeniser'
describe("testing helpers", () => {
it("test is number", () => {
expect(isNumber("1")).toBe(true);
expect(isNumber("5?")).toBe(true);
expect(isNumber("5 + 6")).toBe(true);
expect(isNumber("5 + a")).toBe(true);
describe('testing helpers', () => {
it('test is number', () => {
expect(isNumber('1')).toBe(true)
expect(isNumber('5?')).toBe(true)
expect(isNumber('5 + 6')).toBe(true)
expect(isNumber('5 + a')).toBe(true)
expect(isNumber("a")).toBe(false);
expect(isNumber("?")).toBe(false);
expect(isNumber("?5")).toBe(false);
});
it("test is whitespace", () => {
expect(isWhitespace(" ")).toBe(true);
expect(isWhitespace(" ")).toBe(true);
expect(isWhitespace(" a")).toBe(true);
expect(isWhitespace("a ")).toBe(true);
expect(isNumber('a')).toBe(false)
expect(isNumber('?')).toBe(false)
expect(isNumber('?5')).toBe(false)
})
it('test is whitespace', () => {
expect(isWhitespace(' ')).toBe(true)
expect(isWhitespace(' ')).toBe(true)
expect(isWhitespace(' a')).toBe(true)
expect(isWhitespace('a ')).toBe(true)
expect(isWhitespace("a")).toBe(false);
expect(isWhitespace("?")).toBe(false);
});
it("test is word", () => {
expect(isWord("a")).toBe(true);
expect(isWord("a ")).toBe(true);
expect(isWord("a5")).toBe(true);
expect(isWord("a5a")).toBe(true);
expect(isWhitespace('a')).toBe(false)
expect(isWhitespace('?')).toBe(false)
})
it('test is word', () => {
expect(isWord('a')).toBe(true)
expect(isWord('a ')).toBe(true)
expect(isWord('a5')).toBe(true)
expect(isWord('a5a')).toBe(true)
expect(isWord("5")).toBe(false);
expect(isWord("5a")).toBe(false);
expect(isWord("5a5")).toBe(false);
});
it("test is string", () => {
expect(isString('""')).toBe(true);
expect(isString('"a"')).toBe(true);
expect(isString('"a" ')).toBe(true);
expect(isString('"a"5')).toBe(true);
expect(isString("'a'5")).toBe(true);
expect(isString('"with escaped \\" backslash"')).toBe(true);
expect(isWord('5')).toBe(false)
expect(isWord('5a')).toBe(false)
expect(isWord('5a5')).toBe(false)
})
it('test is string', () => {
expect(isString('""')).toBe(true)
expect(isString('"a"')).toBe(true)
expect(isString('"a" ')).toBe(true)
expect(isString('"a"5')).toBe(true)
expect(isString("'a'5")).toBe(true)
expect(isString('"with escaped \\" backslash"')).toBe(true)
expect(isString('"')).toBe(false);
expect(isString('"a')).toBe(false);
expect(isString('a"')).toBe(false);
expect(isString(' "a"')).toBe(false);
expect(isString('5"a"')).toBe(false);
});
it("test is operator", () => {
expect(isOperator("+")).toBe(true);
expect(isOperator("+ ")).toBe(true);
expect(isOperator("-")).toBe(true);
expect(isOperator("<=")).toBe(true);
expect(isOperator("<= ")).toBe(true);
expect(isOperator(">=")).toBe(true);
expect(isOperator(">= ")).toBe(true);
expect(isOperator("> ")).toBe(true);
expect(isOperator("< ")).toBe(true);
expect(isOperator("| ")).toBe(true);
expect(isOperator("|> ")).toBe(true);
expect(isOperator("^ ")).toBe(true);
expect(isOperator("% ")).toBe(true);
expect(isOperator("+* ")).toBe(true);
expect(isString('"')).toBe(false)
expect(isString('"a')).toBe(false)
expect(isString('a"')).toBe(false)
expect(isString(' "a"')).toBe(false)
expect(isString('5"a"')).toBe(false)
})
it('test is operator', () => {
expect(isOperator('+')).toBe(true)
expect(isOperator('+ ')).toBe(true)
expect(isOperator('-')).toBe(true)
expect(isOperator('<=')).toBe(true)
expect(isOperator('<= ')).toBe(true)
expect(isOperator('>=')).toBe(true)
expect(isOperator('>= ')).toBe(true)
expect(isOperator('> ')).toBe(true)
expect(isOperator('< ')).toBe(true)
expect(isOperator('| ')).toBe(true)
expect(isOperator('|> ')).toBe(true)
expect(isOperator('^ ')).toBe(true)
expect(isOperator('% ')).toBe(true)
expect(isOperator('+* ')).toBe(true)
expect(isOperator("5 + 5")).toBe(false);
expect(isOperator("a")).toBe(false);
expect(isOperator("a+")).toBe(false);
expect(isOperator("a+5")).toBe(false);
expect(isOperator("5a+5")).toBe(false);
expect(isOperator(", newVar")).toBe(false);
expect(isOperator(",")).toBe(false);
});
it("test is paran start", () => {
expect(isParanStart("(")).toBe(true);
expect(isParanStart("( ")).toBe(true);
expect(isParanStart("(5")).toBe(true);
expect(isParanStart("(5 ")).toBe(true);
expect(isParanStart("(5 + 5")).toBe(true);
expect(isParanStart("(5 + 5)")).toBe(true);
expect(isParanStart("(5 + 5) ")).toBe(true);
expect(isOperator('5 + 5')).toBe(false)
expect(isOperator('a')).toBe(false)
expect(isOperator('a+')).toBe(false)
expect(isOperator('a+5')).toBe(false)
expect(isOperator('5a+5')).toBe(false)
expect(isOperator(', newVar')).toBe(false)
expect(isOperator(',')).toBe(false)
})
it('test is paran start', () => {
expect(isParanStart('(')).toBe(true)
expect(isParanStart('( ')).toBe(true)
expect(isParanStart('(5')).toBe(true)
expect(isParanStart('(5 ')).toBe(true)
expect(isParanStart('(5 + 5')).toBe(true)
expect(isParanStart('(5 + 5)')).toBe(true)
expect(isParanStart('(5 + 5) ')).toBe(true)
expect(isParanStart("5")).toBe(false);
expect(isParanStart("5 + 5")).toBe(false);
expect(isParanStart("5( + 5)")).toBe(false);
expect(isParanStart(" ( + 5)")).toBe(false);
});
it("test is paran end", () => {
expect(isParanEnd(")")).toBe(true);
expect(isParanEnd(") ")).toBe(true);
expect(isParanEnd(")5")).toBe(true);
expect(isParanEnd(")5 ")).toBe(true);
expect(isParanStart('5')).toBe(false)
expect(isParanStart('5 + 5')).toBe(false)
expect(isParanStart('5( + 5)')).toBe(false)
expect(isParanStart(' ( + 5)')).toBe(false)
})
it('test is paran end', () => {
expect(isParanEnd(')')).toBe(true)
expect(isParanEnd(') ')).toBe(true)
expect(isParanEnd(')5')).toBe(true)
expect(isParanEnd(')5 ')).toBe(true)
expect(isParanEnd("5")).toBe(false);
expect(isParanEnd("5 + 5")).toBe(false);
expect(isParanEnd("5) + 5")).toBe(false);
expect(isParanEnd(" ) + 5")).toBe(false);
});
it("test is block start", () => {
expect(isBlockStart("{")).toBe(true);
expect(isBlockStart("{ ")).toBe(true);
expect(isBlockStart("{5")).toBe(true);
expect(isBlockStart("{a")).toBe(true);
expect(isBlockStart("{5 ")).toBe(true);
expect(isParanEnd('5')).toBe(false)
expect(isParanEnd('5 + 5')).toBe(false)
expect(isParanEnd('5) + 5')).toBe(false)
expect(isParanEnd(' ) + 5')).toBe(false)
})
it('test is block start', () => {
expect(isBlockStart('{')).toBe(true)
expect(isBlockStart('{ ')).toBe(true)
expect(isBlockStart('{5')).toBe(true)
expect(isBlockStart('{a')).toBe(true)
expect(isBlockStart('{5 ')).toBe(true)
expect(isBlockStart("5")).toBe(false);
expect(isBlockStart("5 + 5")).toBe(false);
expect(isBlockStart("5{ + 5")).toBe(false);
expect(isBlockStart("a{ + 5")).toBe(false);
expect(isBlockStart(" { + 5")).toBe(false);
});
it("test is block end", () => {
expect(isBlockEnd("}")).toBe(true);
expect(isBlockEnd("} ")).toBe(true);
expect(isBlockEnd("}5")).toBe(true);
expect(isBlockEnd("}5 ")).toBe(true);
expect(isBlockStart('5')).toBe(false)
expect(isBlockStart('5 + 5')).toBe(false)
expect(isBlockStart('5{ + 5')).toBe(false)
expect(isBlockStart('a{ + 5')).toBe(false)
expect(isBlockStart(' { + 5')).toBe(false)
})
it('test is block end', () => {
expect(isBlockEnd('}')).toBe(true)
expect(isBlockEnd('} ')).toBe(true)
expect(isBlockEnd('}5')).toBe(true)
expect(isBlockEnd('}5 ')).toBe(true)
expect(isBlockEnd("5")).toBe(false);
expect(isBlockEnd("5 + 5")).toBe(false);
expect(isBlockEnd("5} + 5")).toBe(false);
expect(isBlockEnd(" } + 5")).toBe(false);
});
it("test is comma", () => {
expect(isComma(",")).toBe(true);
expect(isComma(", ")).toBe(true);
expect(isComma(",5")).toBe(true);
expect(isComma(",5 ")).toBe(true);
expect(isBlockEnd('5')).toBe(false)
expect(isBlockEnd('5 + 5')).toBe(false)
expect(isBlockEnd('5} + 5')).toBe(false)
expect(isBlockEnd(' } + 5')).toBe(false)
})
it('test is comma', () => {
expect(isComma(',')).toBe(true)
expect(isComma(', ')).toBe(true)
expect(isComma(',5')).toBe(true)
expect(isComma(',5 ')).toBe(true)
expect(isComma("5")).toBe(false);
expect(isComma("5 + 5")).toBe(false);
expect(isComma("5, + 5")).toBe(false);
expect(isComma(" , + 5")).toBe(false);
});
});
expect(isComma('5')).toBe(false)
expect(isComma('5 + 5')).toBe(false)
expect(isComma('5, + 5')).toBe(false)
expect(isComma(' , + 5')).toBe(false)
})
})
describe("testing lexer", () => {
it("test lexer", () => {
expect(stringSummaryLexer("1 + 2")).toEqual([
describe('testing lexer', () => {
it('test lexer', () => {
expect(stringSummaryLexer('1 + 2')).toEqual([
"number '1' from 0 to 1",
"whitespace ' ' from 1 to 3",
"operator '+' from 3 to 4",
"whitespace ' ' from 4 to 5",
"number '2' from 5 to 6",
]);
expect(stringSummaryLexer("54 + 22500 + 6")).toEqual([
])
expect(stringSummaryLexer('54 + 22500 + 6')).toEqual([
"number '54' from 0 to 2",
"whitespace ' ' from 2 to 3",
"operator '+' from 3 to 4",
@ -161,8 +161,8 @@ describe("testing lexer", () => {
"operator '+' from 11 to 12",
"whitespace ' ' from 12 to 13",
"number '6' from 13 to 14",
]);
expect(stringSummaryLexer("a + bo + t5 - 6")).toEqual([
])
expect(stringSummaryLexer('a + bo + t5 - 6')).toEqual([
"word 'a' from 0 to 1",
"whitespace ' ' from 1 to 2",
"operator '+' from 2 to 3",
@ -176,33 +176,33 @@ describe("testing lexer", () => {
"operator '-' from 12 to 13",
"whitespace ' ' from 13 to 14",
"number '6' from 14 to 15",
]);
])
expect(stringSummaryLexer('a + "a str" - 6')).toEqual([
"word 'a' from 0 to 1",
"whitespace ' ' from 1 to 2",
"operator '+' from 2 to 3",
"whitespace ' ' from 3 to 4",
"string '\"a str\"' from 4 to 11",
'string \'"a str"\' from 4 to 11',
"whitespace ' ' from 11 to 12",
"operator '-' from 12 to 13",
"whitespace ' ' from 13 to 14",
"number '6' from 14 to 15",
]);
])
expect(stringSummaryLexer("a + 'str'")).toEqual([
"word 'a' from 0 to 1",
"whitespace ' ' from 1 to 2",
"operator '+' from 2 to 3",
"whitespace ' ' from 3 to 4",
"string ''str'' from 4 to 9",
]);
])
expect(stringSummaryLexer("a +'str'")).toEqual([
"word 'a' from 0 to 1",
"whitespace ' ' from 1 to 2",
"operator '+' from 2 to 3",
"string ''str'' from 3 to 8",
]);
])
expect(stringSummaryLexer("a + (sick)")).toEqual([
expect(stringSummaryLexer('a + (sick)')).toEqual([
"word 'a' from 0 to 1",
"whitespace ' ' from 1 to 2",
"operator '+' from 2 to 3",
@ -210,9 +210,9 @@ describe("testing lexer", () => {
"brace '(' from 4 to 5",
"word 'sick' from 5 to 9",
"brace ')' from 9 to 10",
]);
])
expect(stringSummaryLexer("a + { sick}")).toEqual([
expect(stringSummaryLexer('a + { sick}')).toEqual([
"word 'a' from 0 to 1",
"whitespace ' ' from 1 to 2",
"operator '+' from 2 to 3",
@ -221,14 +221,14 @@ describe("testing lexer", () => {
"whitespace ' ' from 5 to 6",
"word 'sick' from 6 to 10",
"brace '}' from 10 to 11",
]);
])
expect(stringSummaryLexer("log('hi')")).toEqual([
"word 'log' from 0 to 3",
"brace '(' from 3 to 4",
"string ''hi'' from 4 to 8",
"brace ')' from 8 to 9",
]);
])
expect(stringSummaryLexer("log('hi', 'hello')")).toEqual([
"word 'log' from 0 to 3",
"brace '(' from 3 to 4",
@ -237,8 +237,8 @@ describe("testing lexer", () => {
"whitespace ' ' from 9 to 10",
"string ''hello'' from 10 to 17",
"brace ')' from 17 to 18",
]);
expect(stringSummaryLexer("fn funcName = (param1, param2) => {}")).toEqual([
])
expect(stringSummaryLexer('fn funcName = (param1, param2) => {}')).toEqual([
"word 'fn' from 0 to 2",
"whitespace ' ' from 2 to 3",
"word 'funcName' from 3 to 11",
@ -256,16 +256,16 @@ describe("testing lexer", () => {
"whitespace ' ' from 33 to 34",
"brace '{' from 34 to 35",
"brace '}' from 35 to 36",
]);
});
});
])
})
})
// helpers
const stringSummaryLexer = (input: string) =>
lexer(input).map(
({ type, value, start, end }) =>
`${type.padEnd(12, " ")} ${`'${value}'`.padEnd(10, " ")} from ${String(
`${type.padEnd(12, ' ')} ${`'${value}'`.padEnd(10, ' ')} from ${String(
start
).padEnd(3, " ")} to ${end}`
);
).padEnd(3, ' ')} to ${end}`
)

View File

@ -1,44 +1,55 @@
const NUMBER = /^[0-9]+/;
const WHITESPACE = /\s+/;
const WORD = /^[a-zA-Z_][a-zA-Z0-9_]*/;
const NUMBER = /^[0-9]+/
const WHITESPACE = /\s+/
const WORD = /^[a-zA-Z_][a-zA-Z0-9_]*/
// regex that captures everything between two non escaped quotes and the quotes aren't captured in the match
const STRING = /^(["'])(?:(?=(\\?))\2.)*?\1/;
const STRING = /^(["'])(?:(?=(\\?))\2.)*?\1/
// verbose regex for finding operators, multiple character operators need to be first
const OPERATOR = /^(>=|<=|==|=>|!=|\*|\+|-|\/|%|=|<|>|\||\^)/;
const OPERATOR = /^(>=|<=|==|=>|!=|\*|\+|-|\/|%|=|<|>|\||\^)/
const BLOCK_START = /^\{/;
const BLOCK_END = /^\}/;
const PARAN_START = /^\(/;
const PARAN_END = /^\)/;
const COMMA = /^,/;
const BLOCK_START = /^\{/
const BLOCK_END = /^\}/
const PARAN_START = /^\(/
const PARAN_END = /^\)/
const COMMA = /^,/
export const isNumber = (character: string) => NUMBER.test(character);
export const isWhitespace = (character: string) => WHITESPACE.test(character);
export const isWord = (character: string) => WORD.test(character);
export const isString = (character: string) => STRING.test(character);
export const isOperator = (character: string) => OPERATOR.test(character);
export const isBlockStart = (character: string) => BLOCK_START.test(character);
export const isBlockEnd = (character: string) => BLOCK_END.test(character);
export const isParanStart = (character: string) => PARAN_START.test(character);
export const isParanEnd = (character: string) => PARAN_END.test(character);
export const isComma = (character: string) => COMMA.test(character);
export const isNumber = (character: string) => NUMBER.test(character)
export const isWhitespace = (character: string) => WHITESPACE.test(character)
export const isWord = (character: string) => WORD.test(character)
export const isString = (character: string) => STRING.test(character)
export const isOperator = (character: string) => OPERATOR.test(character)
export const isBlockStart = (character: string) => BLOCK_START.test(character)
export const isBlockEnd = (character: string) => BLOCK_END.test(character)
export const isParanStart = (character: string) => PARAN_START.test(character)
export const isParanEnd = (character: string) => PARAN_END.test(character)
export const isComma = (character: string) => COMMA.test(character)
function matchFirst(str: string, regex: RegExp) {
const theMatch = str.match(regex);
const theMatch = str.match(regex)
if (!theMatch) {
throw new Error("Should always be a match:" + str);
throw new Error('Should always be a match:' + str)
}
return theMatch[0];
return theMatch[0]
}
export interface Token {
type: "number" | "word" | "operator" | "string" | "brace" | "whitespace" | "comma";
value: string;
start: number;
end: number;
type:
| 'number'
| 'word'
| 'operator'
| 'string'
| 'brace'
| 'whitespace'
| 'comma'
value: string
start: number
end: number
}
const makeToken = (type: Token["type"], value: string, start: number): Token => ({
const makeToken = (
type: Token['type'],
value: string,
start: number
): Token => ({
type,
value,
start,
@ -46,39 +57,43 @@ const makeToken = (type: Token["type"], value: string, start: number): Token =>
})
const returnTokenAtIndex = (str: string, startIndex: number): Token | null => {
const strFromIndex = str.slice(startIndex);
const strFromIndex = str.slice(startIndex)
if (isOperator(strFromIndex)) {
return makeToken("operator", matchFirst(strFromIndex, OPERATOR), startIndex);
return makeToken('operator', matchFirst(strFromIndex, OPERATOR), startIndex)
}
if (isString(strFromIndex)) {
return makeToken("string", matchFirst(strFromIndex, STRING), startIndex);
return makeToken('string', matchFirst(strFromIndex, STRING), startIndex)
}
if (isParanEnd(strFromIndex)) {
return makeToken("brace", matchFirst(strFromIndex, PARAN_END), startIndex);
return makeToken('brace', matchFirst(strFromIndex, PARAN_END), startIndex)
}
if (isParanStart(strFromIndex)) {
return makeToken("brace", matchFirst(strFromIndex, PARAN_START), startIndex);
return makeToken('brace', matchFirst(strFromIndex, PARAN_START), startIndex)
}
if (isBlockStart(strFromIndex)) {
return makeToken("brace", matchFirst(strFromIndex, BLOCK_START), startIndex);
return makeToken('brace', matchFirst(strFromIndex, BLOCK_START), startIndex)
}
if (isBlockEnd(strFromIndex)) {
return makeToken("brace", matchFirst(strFromIndex, BLOCK_END), startIndex);
return makeToken('brace', matchFirst(strFromIndex, BLOCK_END), startIndex)
}
if (isComma(strFromIndex)) {
return makeToken("comma", matchFirst(strFromIndex, COMMA), startIndex);
return makeToken('comma', matchFirst(strFromIndex, COMMA), startIndex)
}
if (isNumber(strFromIndex)) {
return makeToken("number", matchFirst(strFromIndex, NUMBER), startIndex);
return makeToken('number', matchFirst(strFromIndex, NUMBER), startIndex)
}
if (isWord(strFromIndex)) {
return makeToken("word", matchFirst(strFromIndex, WORD), startIndex);
return makeToken('word', matchFirst(strFromIndex, WORD), startIndex)
}
if (isWhitespace(strFromIndex)) {
return makeToken("whitespace", matchFirst(strFromIndex, WHITESPACE), startIndex);
return makeToken(
'whitespace',
matchFirst(strFromIndex, WHITESPACE),
startIndex
)
}
return null;
};
return null
}
export const lexer = (str: string): Token[] => {
const recursivelyTokenise = (
@ -87,14 +102,14 @@ export const lexer = (str: string): Token[] => {
previousTokens: Token[] = []
): Token[] => {
if (currentIndex >= str.length) {
return previousTokens;
return previousTokens
}
const token = returnTokenAtIndex(str, currentIndex);
const token = returnTokenAtIndex(str, currentIndex)
if (!token) {
return recursivelyTokenise(str, currentIndex + 1, previousTokens);
return recursivelyTokenise(str, currentIndex + 1, previousTokens)
}
const nextIndex = currentIndex + token.value.length;
return recursivelyTokenise(str, nextIndex, [...previousTokens, token]);
};
return recursivelyTokenise(str);
};
const nextIndex = currentIndex + token.value.length
return recursivelyTokenise(str, nextIndex, [...previousTokens, token])
}
return recursivelyTokenise(str)
}

View File

@ -1,19 +1,19 @@
import { isOverlapping } from "./utils";
import { Range } from "../useStore";
import { isOverlapping } from './utils'
import { Range } from '../useStore'
describe("testing isOverlapping", () => {
testBothOrders([0, 5], [3, 10]);
testBothOrders([0, 5], [3, 4]);
testBothOrders([0, 5], [5, 10]);
testBothOrders([0, 5], [6, 10], false);
testBothOrders([0, 5], [-1, 1]);
testBothOrders([0, 5], [-1, 0]);
testBothOrders([0, 5], [-2, -1], false);
});
describe('testing isOverlapping', () => {
testBothOrders([0, 5], [3, 10])
testBothOrders([0, 5], [3, 4])
testBothOrders([0, 5], [5, 10])
testBothOrders([0, 5], [6, 10], false)
testBothOrders([0, 5], [-1, 1])
testBothOrders([0, 5], [-1, 0])
testBothOrders([0, 5], [-2, -1], false)
})
function testBothOrders(a: Range, b: Range, result = true) {
it(`test is overlapping ${a} ${b}`, () => {
expect(isOverlapping(a, b)).toBe(result);
expect(isOverlapping(b, a)).toBe(result);
});
expect(isOverlapping(a, b)).toBe(result)
expect(isOverlapping(b, a)).toBe(result)
})
}

View File

@ -1,4 +1,3 @@
import { Range } from '../useStore'
export const isOverlapping = (a: Range, b: Range) => {

View File

@ -1,15 +1,15 @@
import { ReportHandler } from 'web-vitals';
import { ReportHandler } from 'web-vitals'
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
getCLS(onPerfEntry)
getFID(onPerfEntry)
getFCP(onPerfEntry)
getLCP(onPerfEntry)
getTTFB(onPerfEntry)
})
}
};
}
export default reportWebVitals;
export default reportWebVitals

View File

@ -2,4 +2,4 @@
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';
import '@testing-library/jest-dom'

View File

@ -1,28 +1,28 @@
import create from 'zustand'
import {addLineHighlight, EditorView} from './editor/highlightextension'
import { addLineHighlight, EditorView } from './editor/highlightextension'
export type Range = [number, number]
interface StoreState {
editorView: EditorView | null,
setEditorView: (editorView: EditorView) => void,
highlightRange: [number, number],
setHighlightRange: (range: Range) => void,
selectionRange: [number, number],
setSelectionRange: (range: Range) => void,
editorView: EditorView | null
setEditorView: (editorView: EditorView) => void
highlightRange: [number, number]
setHighlightRange: (range: Range) => void
selectionRange: [number, number]
setSelectionRange: (range: Range) => void
}
export const useStore = create<StoreState>()((set, get) => ({
editorView: null,
setEditorView: (editorView) => {
set({editorView})
set({ editorView })
},
highlightRange: [0, 0],
setHighlightRange: (highlightRange) => {
set({ highlightRange })
const editorView = get().editorView
if (editorView) {
editorView.dispatch({ effects: addLineHighlight.of(highlightRange) });
editorView.dispatch({ effects: addLineHighlight.of(highlightRange) })
}
},
selectionRange: [0, 0],

View File

@ -8239,6 +8239,11 @@ prelude-ls@~1.1.2:
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=
prettier@^2.8.0:
version "2.8.0"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.0.tgz#c7df58393c9ba77d6fba3921ae01faf994fb9dc9"
integrity sha512-9Lmg8hTFZKG0Asr/kW9Bp8tJjRVluO8EJQVfY2T7FMw9T5jy4I/Uvx0Rca/XWf50QQ1/SS48+6IJWnrb+2yemA==
pretty-bytes@^5.3.0, pretty-bytes@^5.4.1:
version "5.6.0"
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"