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

View File

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

View File

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

View File

@ -1,30 +1,30 @@
import { StateField, StateEffect } from "@codemirror/state"; import { StateField, StateEffect } from '@codemirror/state'
import { EditorView, Decoration } from "@codemirror/view"; import { EditorView, Decoration } from '@codemirror/view'
export { EditorView } export { EditorView }
export const addLineHighlight = StateEffect.define<[number, number]>(); export const addLineHighlight = StateEffect.define<[number, number]>()
export const lineHighlightField = StateField.define({ export const lineHighlightField = StateField.define({
create() { create() {
return Decoration.none; return Decoration.none
}, },
update(lines, tr) { update(lines, tr) {
lines = lines.map(tr.changes); lines = lines.map(tr.changes)
const deco = [] const deco = []
for (let e of tr.effects) { for (let e of tr.effects) {
if (e.is(addLineHighlight)) { if (e.is(addLineHighlight)) {
lines = Decoration.none; lines = Decoration.none
const [from, to] = e.value const [from, to] = e.value
if (!(from === to && from === 0)) { 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)) deco.push(matchDeco.range(from, to))
} }
} }
} }
return lines; return lines
}, },
provide: (f) => EditorView.decorations.from(f), 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 React from 'react'
import ReactDOM from 'react-dom/client'; import ReactDOM from 'react-dom/client'
import './index.css'; import './index.css'
import App from './App'; import App from './App'
import reportWebVitals from './reportWebVitals'; import reportWebVitals from './reportWebVitals'
const root = ReactDOM.createRoot( const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
document.getElementById('root') as HTMLElement
);
root.render( root.render(
<React.StrictMode> <React.StrictMode>
<App /> <App />
</React.StrictMode> </React.StrictMode>
); )
// If you want to start measuring performance in your app, pass a function // If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log)) // to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals // 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 { abstractSyntaxTree, findClosingBrace } from './abstractSyntaxTree'
import { lexer } from "./tokeniser"; import { lexer } from './tokeniser'
describe("findClosingBrace", () => { describe('findClosingBrace', () => {
test("finds the closing brace", () => { test('finds the closing brace', () => {
const basic = "( hey )"; const basic = '( hey )'
expect(findClosingBrace(lexer(basic), 0)).toBe(4); expect(findClosingBrace(lexer(basic), 0)).toBe(4)
const handlesNonZeroIndex = const handlesNonZeroIndex =
"(indexForBracketToRightOfThisIsTwo(shouldBeFour)AndNotThisSix)"; '(indexForBracketToRightOfThisIsTwo(shouldBeFour)AndNotThisSix)'
expect(findClosingBrace(lexer(handlesNonZeroIndex), 2)).toBe(4); expect(findClosingBrace(lexer(handlesNonZeroIndex), 2)).toBe(4)
expect(findClosingBrace(lexer(handlesNonZeroIndex), 0)).toBe(6); expect(findClosingBrace(lexer(handlesNonZeroIndex), 0)).toBe(6)
const handlesNested = const handlesNested =
"{a{b{c(}d]}eathou athoeu tah u} thatOneToTheLeftIsLast }"; '{a{b{c(}d]}eathou athoeu tah u} thatOneToTheLeftIsLast }'
expect(findClosingBrace(lexer(handlesNested), 0)).toBe(18); expect(findClosingBrace(lexer(handlesNested), 0)).toBe(18)
// throws when not started on a brace // throws when not started on a brace
expect(() => findClosingBrace(lexer(handlesNested), 1)).toThrow(); expect(() => findClosingBrace(lexer(handlesNested), 1)).toThrow()
}); })
}); })
describe("testing AST", () => { describe('testing AST', () => {
test("test 5 + 6", () => { test('test 5 + 6', () => {
const tokens = lexer("5 +6"); const tokens = lexer('5 +6')
const result = abstractSyntaxTree(tokens); const result = abstractSyntaxTree(tokens)
expect(result).toEqual({ expect(result).toEqual({
type: "Program", type: 'Program',
start: 0, start: 0,
end: 4, end: 4,
body: [ body: [
{ {
type: "ExpressionStatement", type: 'ExpressionStatement',
start: 0, start: 0,
end: 4, end: 4,
expression: { expression: {
type: "BinaryExpression", type: 'BinaryExpression',
start: 0, start: 0,
end: 4, end: 4,
left: { left: {
type: "Literal", type: 'Literal',
start: 0, start: 0,
end: 1, end: 1,
value: 5, value: 5,
raw: "5", raw: '5',
}, },
operator: "+", operator: '+',
right: { right: {
type: "Literal", type: 'Literal',
start: 3, start: 3,
end: 4, end: 4,
value: 6, value: 6,
raw: "6", raw: '6',
}, },
}, },
}, },
], ],
}); })
}); })
test("test const myVar = 5", () => { test('test const myVar = 5', () => {
const tokens = lexer("const myVar = 5"); const tokens = lexer('const myVar = 5')
const { body } = abstractSyntaxTree(tokens); const { body } = abstractSyntaxTree(tokens)
expect(body).toEqual([ expect(body).toEqual([
{ {
type: "VariableDeclaration", type: 'VariableDeclaration',
start: 0, start: 0,
end: 15, end: 15,
kind: "const", kind: 'const',
declarations: [ declarations: [
{ {
type: "VariableDeclarator", type: 'VariableDeclarator',
start: 6, start: 6,
end: 15, end: 15,
id: { id: {
type: "Identifier", type: 'Identifier',
start: 6, start: 6,
end: 11, end: 11,
name: "myVar", name: 'myVar',
}, },
init: { init: {
type: "Literal", type: 'Literal',
start: 14, start: 14,
end: 15, end: 15,
value: 5, value: 5,
raw: "5", raw: '5',
}, },
}, },
], ],
}, },
]); ])
}); })
test("test multi-line", () => { test('test multi-line', () => {
const code = `const myVar = 5 const code = `const myVar = 5
const newVar = myVar + 1 const newVar = myVar + 1
`; `
const tokens = lexer(code); const tokens = lexer(code)
const { body } = abstractSyntaxTree(tokens); const { body } = abstractSyntaxTree(tokens)
expect(body).toEqual([ expect(body).toEqual([
{ {
type: "VariableDeclaration", type: 'VariableDeclaration',
start: 0, start: 0,
end: 15, end: 15,
kind: "const", kind: 'const',
declarations: [ declarations: [
{ {
type: "VariableDeclarator", type: 'VariableDeclarator',
start: 6, start: 6,
end: 15, end: 15,
id: { id: {
type: "Identifier", type: 'Identifier',
start: 6, start: 6,
end: 11, end: 11,
name: "myVar", name: 'myVar',
}, },
init: { init: {
type: "Literal", type: 'Literal',
start: 14, start: 14,
end: 15, end: 15,
value: 5, value: 5,
raw: "5", raw: '5',
}, },
}, },
], ],
}, },
{ {
type: "VariableDeclaration", type: 'VariableDeclaration',
start: 16, start: 16,
end: 40, end: 40,
kind: "const", kind: 'const',
declarations: [ declarations: [
{ {
type: "VariableDeclarator", type: 'VariableDeclarator',
start: 22, start: 22,
end: 40, end: 40,
id: { id: {
type: "Identifier", type: 'Identifier',
start: 22, start: 22,
end: 28, end: 28,
name: "newVar", name: 'newVar',
}, },
init: { init: {
type: "BinaryExpression", type: 'BinaryExpression',
start: 31, start: 31,
end: 40, end: 40,
left: { left: {
type: "Identifier", type: 'Identifier',
start: 31, start: 31,
end: 36, end: 36,
name: "myVar", name: 'myVar',
}, },
operator: "+", operator: '+',
right: { right: {
type: "Literal", type: 'Literal',
start: 39, start: 39,
end: 40, end: 40,
value: 1, value: 1,
raw: "1", raw: '1',
}, },
}, },
}, },
], ],
}, },
]); ])
}); })
test('test using std function "log"', () => { test('test using std function "log"', () => {
const code = `log(5, "hello", aIdentifier)`; const code = `log(5, "hello", aIdentifier)`
const tokens = lexer(code); const tokens = lexer(code)
const { body } = abstractSyntaxTree(tokens); const { body } = abstractSyntaxTree(tokens)
expect(body).toEqual([ expect(body).toEqual([
{ {
type: "ExpressionStatement", type: 'ExpressionStatement',
start: 0, start: 0,
end: 28, end: 28,
expression: { expression: {
type: "CallExpression", type: 'CallExpression',
start: 0, start: 0,
end: 28, end: 28,
callee: { callee: {
type: "Identifier", type: 'Identifier',
start: 0, start: 0,
end: 3, end: 3,
name: "log", name: 'log',
}, },
arguments: [ arguments: [
{ {
type: "Literal", type: 'Literal',
start: 4, start: 4,
end: 5, end: 5,
value: 5, value: 5,
raw: "5", raw: '5',
}, },
{ {
type: "Literal", type: 'Literal',
start: 7, start: 7,
end: 14, end: 14,
value: "hello", value: 'hello',
raw: '"hello"', raw: '"hello"',
}, },
{ {
type: "Identifier", type: 'Identifier',
start: 16, start: 16,
end: 27, end: 27,
name: "aIdentifier", name: 'aIdentifier',
}, },
], ],
optional: false, optional: false,
}, },
}, },
]); ])
}); })
}); })
describe("testing function declaration", () => { describe('testing function declaration', () => {
test("fn funcN = () => {}", () => { test('fn funcN = () => {}', () => {
const tokens = lexer("fn funcN = () => {}"); const tokens = lexer('fn funcN = () => {}')
const { body } = abstractSyntaxTree(tokens); const { body } = abstractSyntaxTree(tokens)
expect(body).toEqual([ expect(body).toEqual([
{ {
type: "VariableDeclaration", type: 'VariableDeclaration',
start: 0, start: 0,
end: 19, end: 19,
kind: "fn", kind: 'fn',
declarations: [ declarations: [
{ {
type: "VariableDeclarator", type: 'VariableDeclarator',
start: 3, start: 3,
end: 19, end: 19,
id: { id: {
type: "Identifier", type: 'Identifier',
start: 3, start: 3,
end: 8, end: 8,
name: "funcN", name: 'funcN',
}, },
init: { init: {
type: "FunctionExpression", type: 'FunctionExpression',
start: 11, start: 11,
end: 19, end: 19,
id: null, id: null,
params: [], params: [],
body: { body: {
type: "BlockStatement", type: 'BlockStatement',
start: 17, start: 17,
end: 19, end: 19,
body: [], 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( const tokens = lexer(
["fn funcN = (a, b) => {", " return a + b", "}"].join("\n") ['fn funcN = (a, b) => {', ' return a + b', '}'].join('\n')
); )
const { body } = abstractSyntaxTree(tokens); const { body } = abstractSyntaxTree(tokens)
expect(body).toEqual([ expect(body).toEqual([
{ {
type: "VariableDeclaration", type: 'VariableDeclaration',
start: 0, start: 0,
end: 39, end: 39,
kind: "fn", kind: 'fn',
declarations: [ declarations: [
{ {
type: "VariableDeclarator", type: 'VariableDeclarator',
start: 3, start: 3,
end: 39, end: 39,
id: { id: {
type: "Identifier", type: 'Identifier',
start: 3, start: 3,
end: 8, end: 8,
name: "funcN", name: 'funcN',
}, },
init: { init: {
type: "FunctionExpression", type: 'FunctionExpression',
start: 11, start: 11,
end: 39, end: 39,
id: null, id: null,
params: [ params: [
{ {
type: "Identifier", type: 'Identifier',
start: 12, start: 12,
end: 13, end: 13,
name: "a", name: 'a',
}, },
{ {
type: "Identifier", type: 'Identifier',
start: 15, start: 15,
end: 16, end: 16,
name: "b", name: 'b',
}, },
], ],
body: { body: {
type: "BlockStatement", type: 'BlockStatement',
start: 21, start: 21,
end: 39, end: 39,
body: [ body: [
{ {
type: "ReturnStatement", type: 'ReturnStatement',
start: 25, start: 25,
end: 37, end: 37,
argument: { argument: {
type: "BinaryExpression", type: 'BinaryExpression',
start: 32, start: 32,
end: 37, end: 37,
left: { left: {
type: "Identifier", type: 'Identifier',
start: 32, start: 32,
end: 33, end: 33,
name: "a", name: 'a',
}, },
operator: "+", operator: '+',
right: { right: {
type: "Identifier", type: 'Identifier',
start: 36, start: 36,
end: 37, 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( const tokens = lexer(
`fn funcN = (a, b) => { return a + b } `fn funcN = (a, b) => { return a + b }
const myVar = funcN(1, 2)` const myVar = funcN(1, 2)`
); )
const { body } = abstractSyntaxTree(tokens); const { body } = abstractSyntaxTree(tokens)
expect(body).toEqual([ expect(body).toEqual([
{ {
type: "VariableDeclaration", type: 'VariableDeclaration',
start: 0, start: 0,
end: 37, end: 37,
kind: "fn", kind: 'fn',
declarations: [ declarations: [
{ {
type: "VariableDeclarator", type: 'VariableDeclarator',
start: 3, start: 3,
end: 37, end: 37,
id: { id: {
type: "Identifier", type: 'Identifier',
start: 3, start: 3,
end: 8, end: 8,
name: "funcN", name: 'funcN',
}, },
init: { init: {
type: "FunctionExpression", type: 'FunctionExpression',
start: 11, start: 11,
end: 37, end: 37,
id: null, id: null,
params: [ params: [
{ {
type: "Identifier", type: 'Identifier',
start: 12, start: 12,
end: 13, end: 13,
name: "a", name: 'a',
}, },
{ {
type: "Identifier", type: 'Identifier',
start: 15, start: 15,
end: 16, end: 16,
name: "b", name: 'b',
}, },
], ],
body: { body: {
type: "BlockStatement", type: 'BlockStatement',
start: 21, start: 21,
end: 37, end: 37,
body: [ body: [
{ {
type: "ReturnStatement", type: 'ReturnStatement',
start: 23, start: 23,
end: 35, end: 35,
argument: { argument: {
type: "BinaryExpression", type: 'BinaryExpression',
start: 30, start: 30,
end: 35, end: 35,
left: { left: {
type: "Identifier", type: 'Identifier',
start: 30, start: 30,
end: 31, end: 31,
name: "a", name: 'a',
}, },
operator: "+", operator: '+',
right: { right: {
type: "Identifier", type: 'Identifier',
start: 34, start: 34,
end: 35, end: 35,
name: "b", name: 'b',
}, },
}, },
}, },
@ -403,45 +403,45 @@ const myVar = funcN(1, 2)`
], ],
}, },
{ {
type: "VariableDeclaration", type: 'VariableDeclaration',
start: 38, start: 38,
end: 63, end: 63,
kind: "const", kind: 'const',
declarations: [ declarations: [
{ {
type: "VariableDeclarator", type: 'VariableDeclarator',
start: 44, start: 44,
end: 63, end: 63,
id: { id: {
type: "Identifier", type: 'Identifier',
start: 44, start: 44,
end: 49, end: 49,
name: "myVar", name: 'myVar',
}, },
init: { init: {
type: "CallExpression", type: 'CallExpression',
start: 52, start: 52,
end: 63, end: 63,
callee: { callee: {
type: "Identifier", type: 'Identifier',
start: 52, start: 52,
end: 57, end: 57,
name: "funcN", name: 'funcN',
}, },
arguments: [ arguments: [
{ {
type: "Literal", type: 'Literal',
start: 58, start: 58,
end: 59, end: 59,
value: 1, value: 1,
raw: "1", raw: '1',
}, },
{ {
type: "Literal", type: 'Literal',
start: 61, start: 61,
end: 62, end: 62,
value: 2, value: 2,
raw: "2", raw: '2',
}, },
], ],
optional: false, optional: false,
@ -449,87 +449,87 @@ const myVar = funcN(1, 2)`
}, },
], ],
}, },
]); ])
}); })
}); })
describe("structures specific to this lang", () => { describe('structures specific to this lang', () => {
test("sketch", () => { test('sketch', () => {
let code = `sketch mySketch { let code = `sketch mySketch {
path myPath = lineTo(0,1) path myPath = lineTo(0,1)
lineTo(1,1) lineTo(1,1)
path rightPath = lineTo(1,0) path rightPath = lineTo(1,0)
close() close()
} }
`; `
const tokens = lexer(code); const tokens = lexer(code)
const { body } = abstractSyntaxTree(tokens); const { body } = abstractSyntaxTree(tokens)
expect(body).toEqual([ expect(body).toEqual([
{ {
type: "VariableDeclaration", type: 'VariableDeclaration',
start: 0, start: 0,
end: 102, end: 102,
kind: "sketch", kind: 'sketch',
declarations: [ declarations: [
{ {
type: "VariableDeclarator", type: 'VariableDeclarator',
start: 7, start: 7,
end: 102, end: 102,
id: { id: {
type: "Identifier", type: 'Identifier',
start: 7, start: 7,
end: 15, end: 15,
name: "mySketch", name: 'mySketch',
}, },
init: { init: {
type: "SketchExpression", type: 'SketchExpression',
start: 16, start: 16,
end: 102, end: 102,
body: { body: {
type: "BlockStatement", type: 'BlockStatement',
start: 16, start: 16,
end: 102, end: 102,
body: [ body: [
{ {
type: "VariableDeclaration", type: 'VariableDeclaration',
start: 20, start: 20,
end: 45, end: 45,
kind: "path", kind: 'path',
declarations: [ declarations: [
{ {
type: "VariableDeclarator", type: 'VariableDeclarator',
start: 25, start: 25,
end: 45, end: 45,
id: { id: {
type: "Identifier", type: 'Identifier',
start: 25, start: 25,
end: 31, end: 31,
name: "myPath", name: 'myPath',
}, },
init: { init: {
type: "CallExpression", type: 'CallExpression',
start: 34, start: 34,
end: 45, end: 45,
callee: { callee: {
type: "Identifier", type: 'Identifier',
start: 34, start: 34,
end: 40, end: 40,
name: "lineTo", name: 'lineTo',
}, },
arguments: [ arguments: [
{ {
type: "Literal", type: 'Literal',
start: 41, start: 41,
end: 42, end: 42,
value: 0, value: 0,
raw: "0", raw: '0',
}, },
{ {
type: "Literal", type: 'Literal',
start: 43, start: 43,
end: 44, end: 44,
value: 1, value: 1,
raw: "1", raw: '1',
}, },
], ],
optional: false, optional: false,
@ -538,78 +538,78 @@ describe("structures specific to this lang", () => {
], ],
}, },
{ {
type: "ExpressionStatement", type: 'ExpressionStatement',
start: 48, start: 48,
end: 59, end: 59,
expression: { expression: {
type: "CallExpression", type: 'CallExpression',
start: 48, start: 48,
end: 59, end: 59,
callee: { callee: {
type: "Identifier", type: 'Identifier',
start: 48, start: 48,
end: 54, end: 54,
name: "lineTo", name: 'lineTo',
}, },
arguments: [ arguments: [
{ {
type: "Literal", type: 'Literal',
start: 55, start: 55,
end: 56, end: 56,
value: 1, value: 1,
raw: "1", raw: '1',
}, },
{ {
type: "Literal", type: 'Literal',
start: 57, start: 57,
end: 58, end: 58,
value: 1, value: 1,
raw: "1", raw: '1',
}, },
], ],
optional: false, optional: false,
}, },
}, },
{ {
type: "VariableDeclaration", type: 'VariableDeclaration',
start: 62, start: 62,
end: 90, end: 90,
kind: "path", kind: 'path',
declarations: [ declarations: [
{ {
type: "VariableDeclarator", type: 'VariableDeclarator',
start: 67, start: 67,
end: 90, end: 90,
id: { id: {
type: "Identifier", type: 'Identifier',
start: 67, start: 67,
end: 76, end: 76,
name: "rightPath", name: 'rightPath',
}, },
init: { init: {
type: "CallExpression", type: 'CallExpression',
start: 79, start: 79,
end: 90, end: 90,
callee: { callee: {
type: "Identifier", type: 'Identifier',
start: 79, start: 79,
end: 85, end: 85,
name: "lineTo", name: 'lineTo',
}, },
arguments: [ arguments: [
{ {
type: "Literal", type: 'Literal',
start: 86, start: 86,
end: 87, end: 87,
value: 1, value: 1,
raw: "1", raw: '1',
}, },
{ {
type: "Literal", type: 'Literal',
start: 88, start: 88,
end: 89, end: 89,
value: 0, value: 0,
raw: "0", raw: '0',
}, },
], ],
optional: false, optional: false,
@ -618,18 +618,18 @@ describe("structures specific to this lang", () => {
], ],
}, },
{ {
type: "ExpressionStatement", type: 'ExpressionStatement',
start: 93, start: 93,
end: 100, end: 100,
expression: { expression: {
type: "CallExpression", type: 'CallExpression',
start: 93, start: 93,
end: 100, end: 100,
callee: { callee: {
type: "Identifier", type: 'Identifier',
start: 93, start: 93,
end: 98, end: 98,
name: "close", name: 'close',
}, },
arguments: [], arguments: [],
optional: false, 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 { BoxGeometry, SphereGeometry, BufferGeometry } from 'three'
import {mergeBufferGeometries} from 'three/examples/jsm/utils/BufferGeometryUtils' import { mergeBufferGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils'
export function lineGeo({ export function lineGeo({
from, from,
to, to,
}: { }: {
from: [number, number, number]; from: [number, number, number]
to: [number, number, number]; to: [number, number, number]
}): BufferGeometry { }): BufferGeometry {
const sq = (a: number): number => a * a; const sq = (a: number): number => a * a
const center = [ const center = [
(from[0] + to[0]) / 2, (from[0] + to[0]) / 2,
(from[1] + to[1]) / 2, (from[1] + to[1]) / 2,
(from[2] + to[2]) / 2, (from[2] + to[2]) / 2,
]; ]
const Hypotenuse3d = Math.sqrt( const Hypotenuse3d = Math.sqrt(
sq(from[0] - to[0]) + sq(from[1] - to[1]) + sq(from[2] - to[2]) 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 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 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 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] // 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.rotateY(ang1)
lineBody.rotateZ(ang2) 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 // create line end balls with SphereGeometry at `to` and `from` with radius of 0.15
const lineEnd1 = new SphereGeometry(0.15); const lineEnd1 = new SphereGeometry(0.15)
lineEnd1.translate(to[0], to[1], to[2]); lineEnd1.translate(to[0], to[1], to[2])
// const lineEnd2 = new SphereGeometry(0.15); // const lineEnd2 = new SphereGeometry(0.15);
// lineEnd2.translate(from[0], from[1], from[2]) // lineEnd2.translate(from[0], from[1], from[2])
// group all three geometries // group all three geometries
return mergeBufferGeometries([lineBody, lineEnd1]); return mergeBufferGeometries([lineBody, lineEnd1])
// return mergeBufferGeometries([lineBody, lineEnd1, lineEnd2]); // 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 { abstractSyntaxTree } from './abstractSyntaxTree'
import { lexer } from "./tokeniser"; import { lexer } from './tokeniser'
import { executor, ProgramMemory } from "./executor"; import { executor, ProgramMemory } from './executor'
describe("test", () => { describe('test', () => {
it("test assigning two variables, the second summing with the first", () => { it('test assigning two variables, the second summing with the first', () => {
const code = `const myVar = 5 const code = `const myVar = 5
const newVar = myVar + 1`; const newVar = myVar + 1`
const { root } = exe(code); const { root } = exe(code)
expect(root.myVar).toBe(5); expect(root.myVar).toBe(5)
expect(root.newVar).toBe(6); expect(root.newVar).toBe(6)
}); })
it("test assigning a var with a string", () => { it('test assigning a var with a string', () => {
const code = `const myVar = "a str"`; const code = `const myVar = "a str"`
const { root } = exe(code); const { root } = exe(code)
expect(root.myVar).toBe("a str"); expect(root.myVar).toBe('a str')
}); })
it("test assigning a var by cont concatenating two strings string", () => { it('test assigning a var by cont concatenating two strings string', () => {
const code = fs.readFileSync( const code = fs.readFileSync(
"./src/lang/testExamples/variableDeclaration.cado", './src/lang/testExamples/variableDeclaration.cado',
"utf-8" 'utf-8'
); )
const { root } = exe(code); const { root } = exe(code)
expect(root.myVar).toBe("a str another str"); expect(root.myVar).toBe('a str another str')
}); })
it("test with function call", () => { it('test with function call', () => {
const code = ` const code = `
const myVar = "hello" const myVar = "hello"
log(5, myVar)`; log(5, myVar)`
const programMemoryOverride = { const programMemoryOverride = {
log: jest.fn(), log: jest.fn(),
}; }
const { root } = executor(abstractSyntaxTree(lexer(code)), { const { root } = executor(abstractSyntaxTree(lexer(code)), {
root: programMemoryOverride, root: programMemoryOverride,
_sketch: [], _sketch: [],
}); })
expect(root.myVar).toBe("hello"); expect(root.myVar).toBe('hello')
expect(programMemoryOverride.log).toHaveBeenCalledWith(5, "hello"); expect(programMemoryOverride.log).toHaveBeenCalledWith(5, 'hello')
}); })
it("fn funcN = () => {}", () => { it('fn funcN = () => {}', () => {
const { root } = exe( const { root } = exe(
[ [
"fn funcN = (a, b) => {", 'fn funcN = (a, b) => {',
" return a + b", ' return a + b',
"}", '}',
"const theVar = 60", 'const theVar = 60',
"const magicNum = funcN(9, theVar)", 'const magicNum = funcN(9, theVar)',
].join("\n") ].join('\n')
); )
expect(root.theVar).toBe(60); expect(root.theVar).toBe(60)
expect(root.magicNum).toBe(69); expect(root.magicNum).toBe(69)
}); })
it("sketch declaration", () => { it('sketch declaration', () => {
let code = `sketch mySketch { let code = `sketch mySketch {
path myPath = lineTo(0,1) path myPath = lineTo(0,1)
lineTo(1,1) lineTo(1,1)
@ -60,33 +60,33 @@ log(5, myVar)`;
close() close()
} }
show(mySketch) show(mySketch)
`; `
const { root, return: _return } = exe(code); const { root, return: _return } = exe(code)
expect( expect(
root.mySketch.map(({ previousPath, geo, ...rest }: any) => rest) root.mySketch.map(({ previousPath, geo, ...rest }: any) => rest)
).toEqual([ ).toEqual([
{ type: "base", from: [0, 0] }, { type: 'base', from: [0, 0] },
{ type: "toPoint", to: [0, 1], sourceRange: [25, 45], name: "myPath" }, { type: 'toPoint', to: [0, 1], sourceRange: [25, 45], name: 'myPath' },
{ type: "toPoint", to: [1, 1], sourceRange: [48, 59] }, { type: 'toPoint', to: [1, 1], sourceRange: [48, 59] },
{ type: "toPoint", to: [1, 0], sourceRange: [67, 90], name: "rightPath" }, { type: 'toPoint', to: [1, 0], sourceRange: [67, 90], name: 'rightPath' },
{ {
type: "close", type: 'close',
firstPath: { type: "base", from: [0, 0] }, firstPath: { type: 'base', from: [0, 0] },
sourceRange: [93, 100], 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 // hmm not sure what handle the "show" function
expect(_return).toEqual([ expect(_return).toEqual([
{ {
type: "Identifier", type: 'Identifier',
start: 108, start: 108,
end: 116, end: 116,
name: "mySketch", name: 'mySketch',
}, },
]); ])
}); })
}); })
// helpers // helpers
@ -94,7 +94,7 @@ function exe(
code: string, code: string,
programMemory: ProgramMemory = { root: {}, _sketch: [] } programMemory: ProgramMemory = { root: {}, _sketch: [] }
) { ) {
const tokens = lexer(code); const tokens = lexer(code)
const ast = abstractSyntaxTree(tokens); const ast = abstractSyntaxTree(tokens)
return executor(ast, programMemory); return executor(ast, programMemory)
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,3 @@
import { Range } from '../useStore' import { Range } from '../useStore'
export const isOverlapping = (a: Range, b: Range) => { 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) => { const reportWebVitals = (onPerfEntry?: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) { if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry); getCLS(onPerfEntry)
getFID(onPerfEntry); getFID(onPerfEntry)
getFCP(onPerfEntry); getFCP(onPerfEntry)
getLCP(onPerfEntry); getLCP(onPerfEntry)
getTTFB(onPerfEntry); getTTFB(onPerfEntry)
}); })
} }
}; }
export default reportWebVitals; export default reportWebVitals

View File

@ -2,4 +2,4 @@
// allows you to do things like: // allows you to do things like:
// expect(element).toHaveTextContent(/react/i) // expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom // 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 create from 'zustand'
import {addLineHighlight, EditorView} from './editor/highlightextension' import { addLineHighlight, EditorView } from './editor/highlightextension'
export type Range = [number, number] export type Range = [number, number]
interface StoreState { interface StoreState {
editorView: EditorView | null, editorView: EditorView | null
setEditorView: (editorView: EditorView) => void, setEditorView: (editorView: EditorView) => void
highlightRange: [number, number], highlightRange: [number, number]
setHighlightRange: (range: Range) => void, setHighlightRange: (range: Range) => void
selectionRange: [number, number], selectionRange: [number, number]
setSelectionRange: (range: Range) => void, setSelectionRange: (range: Range) => void
} }
export const useStore = create<StoreState>()((set, get) => ({ export const useStore = create<StoreState>()((set, get) => ({
editorView: null, editorView: null,
setEditorView: (editorView) => { setEditorView: (editorView) => {
set({editorView}) set({ editorView })
}, },
highlightRange: [0, 0], highlightRange: [0, 0],
setHighlightRange: (highlightRange) => { setHighlightRange: (highlightRange) => {
set({ highlightRange }) set({ highlightRange })
const editorView = get().editorView const editorView = get().editorView
if (editorView) { if (editorView) {
editorView.dispatch({ effects: addLineHighlight.of(highlightRange) }); editorView.dispatch({ effects: addLineHighlight.of(highlightRange) })
} }
}, },
selectionRange: [0, 0], 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" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= 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: pretty-bytes@^5.3.0, pretty-bytes@^5.4.1:
version "5.6.0" version "5.6.0"
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"