Collect structured errors from parsing/executing KCL (#187)
Currently, syntax/semantic errors in the user's source code result in vanilla JS exceptions being thrown, so they show up in the console. Instead, this PR: - Adds a new type KCLError - KCL syntax/semantic errors when parsing/executing the source code now throw KCLErrors instead of vanilla JS exceptions. - KCL errors are caught and logged to a new "Errors" panel, instead of the browser console.
This commit is contained in:
18
src/App.tsx
18
src/App.tsx
@ -12,7 +12,7 @@ import {
|
|||||||
addLineHighlight,
|
addLineHighlight,
|
||||||
} from './editor/highlightextension'
|
} from './editor/highlightextension'
|
||||||
import { Selections, useStore } from './useStore'
|
import { Selections, useStore } from './useStore'
|
||||||
import { Logs } from './components/Logs'
|
import { Logs, KCLErrors } from './components/Logs'
|
||||||
import { PanelHeader } from './components/PanelHeader'
|
import { PanelHeader } from './components/PanelHeader'
|
||||||
import { MemoryPanel } from './components/MemoryPanel'
|
import { MemoryPanel } from './components/MemoryPanel'
|
||||||
import { useHotKeyListener } from './hooks/useHotKeyListener'
|
import { useHotKeyListener } from './hooks/useHotKeyListener'
|
||||||
@ -22,6 +22,7 @@ import { EngineCommandManager } from './lang/std/engineConnection'
|
|||||||
import { isOverlap } from './lib/utils'
|
import { isOverlap } from './lib/utils'
|
||||||
import { SetToken } from './components/TokenInput'
|
import { SetToken } from './components/TokenInput'
|
||||||
import { AppHeader } from './components/AppHeader'
|
import { AppHeader } from './components/AppHeader'
|
||||||
|
import { KCLError } from './lang/errors'
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
const cam = useRef()
|
const cam = useRef()
|
||||||
@ -32,6 +33,7 @@ export function App() {
|
|||||||
setSelectionRanges,
|
setSelectionRanges,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
addLog,
|
addLog,
|
||||||
|
addKCLError,
|
||||||
code,
|
code,
|
||||||
setCode,
|
setCode,
|
||||||
setAst,
|
setAst,
|
||||||
@ -79,6 +81,7 @@ export function App() {
|
|||||||
token: s.token,
|
token: s.token,
|
||||||
formatCode: s.formatCode,
|
formatCode: s.formatCode,
|
||||||
debugPanel: s.debugPanel,
|
debugPanel: s.debugPanel,
|
||||||
|
addKCLError: s.addKCLError,
|
||||||
}))
|
}))
|
||||||
// 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) => {
|
||||||
@ -249,9 +252,13 @@ export function App() {
|
|||||||
setError()
|
setError()
|
||||||
})
|
})
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
setError('problem')
|
if (e instanceof KCLError) {
|
||||||
console.log(e)
|
addKCLError(e)
|
||||||
addLog(e)
|
} else {
|
||||||
|
setError('problem')
|
||||||
|
console.log(e)
|
||||||
|
addLog(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
asyncWrap()
|
asyncWrap()
|
||||||
@ -261,7 +268,7 @@ export function App() {
|
|||||||
<AppHeader />
|
<AppHeader />
|
||||||
<ModalContainer />
|
<ModalContainer />
|
||||||
<Allotment snap={true}>
|
<Allotment snap={true}>
|
||||||
<Allotment vertical defaultSizes={[5, 400, 1, 1]} minSize={20}>
|
<Allotment vertical defaultSizes={[5, 400, 1, 1, 200]} minSize={20}>
|
||||||
<SetToken />
|
<SetToken />
|
||||||
<div className="h-full flex flex-col items-start">
|
<div className="h-full flex flex-col items-start">
|
||||||
<PanelHeader title="Editor" />
|
<PanelHeader title="Editor" />
|
||||||
@ -288,6 +295,7 @@ export function App() {
|
|||||||
</div>
|
</div>
|
||||||
<MemoryPanel />
|
<MemoryPanel />
|
||||||
<Logs />
|
<Logs />
|
||||||
|
<KCLErrors />
|
||||||
</Allotment>
|
</Allotment>
|
||||||
<Allotment vertical defaultSizes={[40, 400]} minSize={20}>
|
<Allotment vertical defaultSizes={[40, 400]} minSize={20}>
|
||||||
<Stream />
|
<Stream />
|
||||||
|
@ -38,3 +38,36 @@ export const Logs = () => {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const KCLErrors = () => {
|
||||||
|
const { kclErrors } = useStore(({ kclErrors }) => ({
|
||||||
|
kclErrors,
|
||||||
|
}))
|
||||||
|
useEffect(() => {
|
||||||
|
const element = document.querySelector('.console-tile')
|
||||||
|
if (element) {
|
||||||
|
element.scrollTop = element.scrollHeight - element.clientHeight
|
||||||
|
}
|
||||||
|
}, [kclErrors])
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<PanelHeader title="KCL Errors" />
|
||||||
|
<div className="h-full relative">
|
||||||
|
<div className="absolute inset-0 flex flex-col items-start">
|
||||||
|
<ReactJsonTypeHack
|
||||||
|
src={kclErrors}
|
||||||
|
collapsed={1}
|
||||||
|
collapseStringsAfterLength={60}
|
||||||
|
enableClipboard={false}
|
||||||
|
displayArrayKey={false}
|
||||||
|
displayDataTypes={false}
|
||||||
|
displayObjectSize={true}
|
||||||
|
indentWidth={2}
|
||||||
|
quotesOnKeys={false}
|
||||||
|
name={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Token } from './tokeniser'
|
import { Token } from './tokeniser'
|
||||||
import { parseExpression } from './astMathExpressions'
|
import { parseExpression } from './astMathExpressions'
|
||||||
|
import { KCLSyntaxError, KCLUnimplementedError } from './errors'
|
||||||
import {
|
import {
|
||||||
BinaryPart,
|
BinaryPart,
|
||||||
BodyItem,
|
BodyItem,
|
||||||
@ -128,6 +129,11 @@ function makeArguments(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const nextBraceOrCommaToken = nextMeaningfulToken(tokens, argumentToken.index)
|
const nextBraceOrCommaToken = nextMeaningfulToken(tokens, argumentToken.index)
|
||||||
|
if (nextBraceOrCommaToken.token == undefined) {
|
||||||
|
throw new KCLSyntaxError('Expected argument', [
|
||||||
|
[argumentToken.token.start, argumentToken.token.end],
|
||||||
|
])
|
||||||
|
}
|
||||||
const isIdentifierOrLiteral =
|
const isIdentifierOrLiteral =
|
||||||
nextBraceOrCommaToken.token.type === 'comma' ||
|
nextBraceOrCommaToken.token.type === 'comma' ||
|
||||||
nextBraceOrCommaToken.token.type === 'brace'
|
nextBraceOrCommaToken.token.type === 'brace'
|
||||||
@ -282,7 +288,10 @@ function makeArguments(
|
|||||||
) {
|
) {
|
||||||
return makeArguments(tokens, argumentToken.index, previousArgs)
|
return makeArguments(tokens, argumentToken.index, previousArgs)
|
||||||
}
|
}
|
||||||
throw new Error('Expected a previous Argument if statement to match')
|
throw new KCLSyntaxError(
|
||||||
|
'Expected a previous Argument if statement to match',
|
||||||
|
[[argumentToken.token.start, argumentToken.token.end]]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeVariableDeclaration(
|
function makeVariableDeclaration(
|
||||||
@ -406,14 +415,18 @@ function makeValue(
|
|||||||
lastIndex: arrowFunctionLastIndex,
|
lastIndex: arrowFunctionLastIndex,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error('TODO - handle expression with braces')
|
throw new KCLUnimplementedError('expression with braces', [
|
||||||
|
[currentToken.start, currentToken.end],
|
||||||
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (currentToken.type === 'operator' && currentToken.value === '-') {
|
if (currentToken.type === 'operator' && currentToken.value === '-') {
|
||||||
const { expression, lastIndex } = makeUnaryExpression(tokens, index)
|
const { expression, lastIndex } = makeUnaryExpression(tokens, index)
|
||||||
return { value: expression, lastIndex }
|
return { value: expression, lastIndex }
|
||||||
}
|
}
|
||||||
throw new Error('Expected a previous Value if statement to match')
|
throw new KCLSyntaxError('Expected a previous Value if statement to match', [
|
||||||
|
[currentToken.start, currentToken.end],
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeVariableDeclarators(
|
function makeVariableDeclarators(
|
||||||
@ -505,7 +518,9 @@ function makeArrayElements(
|
|||||||
nextToken.token.type === 'brace' && nextToken.token.value === ']'
|
nextToken.token.type === 'brace' && nextToken.token.value === ']'
|
||||||
const isComma = nextToken.token.type === 'comma'
|
const isComma = nextToken.token.type === 'comma'
|
||||||
if (!isClosingBrace && !isComma) {
|
if (!isClosingBrace && !isComma) {
|
||||||
throw new Error('Expected a comma or closing brace')
|
throw new KCLSyntaxError('Expected a comma or closing brace', [
|
||||||
|
[nextToken.token.start, nextToken.token.end],
|
||||||
|
])
|
||||||
}
|
}
|
||||||
const nextCallIndex = isClosingBrace
|
const nextCallIndex = isClosingBrace
|
||||||
? nextToken.index
|
? nextToken.index
|
||||||
@ -617,7 +632,10 @@ function makeMemberExpression(
|
|||||||
const keysInfo = collectObjectKeys(tokens, index)
|
const keysInfo = collectObjectKeys(tokens, index)
|
||||||
const lastKey = keysInfo[keysInfo.length - 1]
|
const lastKey = keysInfo[keysInfo.length - 1]
|
||||||
const firstKey = keysInfo.shift()
|
const firstKey = keysInfo.shift()
|
||||||
if (!firstKey) throw new Error('Expected a key')
|
if (!firstKey)
|
||||||
|
throw new KCLSyntaxError('Expected a key', [
|
||||||
|
[currentToken.start, currentToken.end],
|
||||||
|
])
|
||||||
const root = makeIdentifier(tokens, index)
|
const root = makeIdentifier(tokens, index)
|
||||||
let memberExpression: MemberExpression = {
|
let memberExpression: MemberExpression = {
|
||||||
type: 'MemberExpression',
|
type: 'MemberExpression',
|
||||||
@ -807,7 +825,10 @@ function makePipeBody(
|
|||||||
value = val.value
|
value = val.value
|
||||||
lastIndex = val.lastIndex
|
lastIndex = val.lastIndex
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Expected a previous PipeValue if statement to match')
|
throw new KCLSyntaxError(
|
||||||
|
'Expected a previous PipeValue if statement to match',
|
||||||
|
[[currentToken.start, currentToken.end]]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextPipeToken = hasPipeOperator(tokens, index)
|
const nextPipeToken = hasPipeOperator(tokens, index)
|
||||||
@ -1073,7 +1094,7 @@ function makeBody(
|
|||||||
lastIndex,
|
lastIndex,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new Error('Unexpected token')
|
throw new KCLSyntaxError('Unexpected token', [[token.start, token.end]])
|
||||||
}
|
}
|
||||||
export const abstractSyntaxTree = (tokens: Token[]): Program => {
|
export const abstractSyntaxTree = (tokens: Token[]): Program => {
|
||||||
const { body, nonCodeMeta } = makeBody({ tokens })
|
const { body, nonCodeMeta } = makeBody({ tokens })
|
||||||
@ -1227,15 +1248,26 @@ export function findClosingBrace(
|
|||||||
if (isFirstCall) {
|
if (isFirstCall) {
|
||||||
searchOpeningBrace = currentToken.value
|
searchOpeningBrace = currentToken.value
|
||||||
if (!['(', '{', '['].includes(searchOpeningBrace)) {
|
if (!['(', '{', '['].includes(searchOpeningBrace)) {
|
||||||
throw new Error(
|
throw new KCLSyntaxError(
|
||||||
`expected to be started on a opening brace ( { [, instead found '${searchOpeningBrace}'`
|
`expected to be started on a opening brace ( { [, instead found '${searchOpeningBrace}'`,
|
||||||
|
[[currentToken.start, currentToken.end]]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const foundClosingBrace =
|
const foundClosingBrace = (() => {
|
||||||
_braceCount === 1 &&
|
try {
|
||||||
currentToken.value === closingBraceMap[searchOpeningBrace]
|
return (
|
||||||
|
_braceCount === 1 &&
|
||||||
|
currentToken.value === closingBraceMap[searchOpeningBrace]
|
||||||
|
)
|
||||||
|
} catch (e: any) {
|
||||||
|
throw new KCLSyntaxError('Missing a closing brace', [
|
||||||
|
[currentToken.start, currentToken.end],
|
||||||
|
])
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
const foundAnotherOpeningBrace = currentToken.value === searchOpeningBrace
|
const foundAnotherOpeningBrace = currentToken.value === searchOpeningBrace
|
||||||
const foundAnotherClosingBrace =
|
const foundAnotherClosingBrace =
|
||||||
currentToken.value === closingBraceMap[searchOpeningBrace]
|
currentToken.value === closingBraceMap[searchOpeningBrace]
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
isNotCodeToken,
|
isNotCodeToken,
|
||||||
} from './abstractSyntaxTree'
|
} from './abstractSyntaxTree'
|
||||||
import { Token } from './tokeniser'
|
import { Token } from './tokeniser'
|
||||||
|
import { KCLSyntaxError } from './errors'
|
||||||
|
|
||||||
export function reversePolishNotation(
|
export function reversePolishNotation(
|
||||||
tokens: Token[],
|
tokens: Token[],
|
||||||
@ -82,7 +83,9 @@ export function reversePolishNotation(
|
|||||||
if (isNotCodeToken(currentToken)) {
|
if (isNotCodeToken(currentToken)) {
|
||||||
return reversePolishNotation(tokens.slice(1), previousPostfix, operators)
|
return reversePolishNotation(tokens.slice(1), previousPostfix, operators)
|
||||||
}
|
}
|
||||||
throw new Error('Unknown token')
|
throw new KCLSyntaxError('Unknown token', [
|
||||||
|
[currentToken.start, currentToken.end],
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ParenthesisToken {
|
interface ParenthesisToken {
|
||||||
@ -204,21 +207,27 @@ const buildTree = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function parseExpression(tokens: Token[]): BinaryExpression {
|
export function parseExpression(tokens: Token[]): BinaryExpression {
|
||||||
const treeWithMabyeBadTopLevelStartEnd = buildTree(
|
const treeWithMaybeBadTopLevelStartEnd = buildTree(
|
||||||
reversePolishNotation(tokens)
|
reversePolishNotation(tokens)
|
||||||
)
|
)
|
||||||
const left = treeWithMabyeBadTopLevelStartEnd?.left as any
|
const left = treeWithMaybeBadTopLevelStartEnd?.left as any
|
||||||
const start = left?.startExtended || treeWithMabyeBadTopLevelStartEnd?.start
|
const start = left?.startExtended || treeWithMaybeBadTopLevelStartEnd?.start
|
||||||
|
if (left == undefined || left == null) {
|
||||||
|
throw new KCLSyntaxError(
|
||||||
|
'syntax',
|
||||||
|
tokens.map((token) => [token.start, token.end])
|
||||||
|
) // Add text
|
||||||
|
}
|
||||||
delete left.startExtended
|
delete left.startExtended
|
||||||
delete left.endExtended
|
delete left.endExtended
|
||||||
|
|
||||||
const right = treeWithMabyeBadTopLevelStartEnd?.right as any
|
const right = treeWithMaybeBadTopLevelStartEnd?.right as any
|
||||||
const end = right?.endExtended || treeWithMabyeBadTopLevelStartEnd?.end
|
const end = right?.endExtended || treeWithMaybeBadTopLevelStartEnd?.end
|
||||||
delete right.startExtended
|
delete right.startExtended
|
||||||
delete right.endExtended
|
delete right.endExtended
|
||||||
|
|
||||||
const tree: BinaryExpression = {
|
const tree: BinaryExpression = {
|
||||||
...treeWithMabyeBadTopLevelStartEnd,
|
...treeWithMaybeBadTopLevelStartEnd,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
left,
|
left,
|
||||||
@ -232,7 +241,7 @@ function _precedence(operator: Token): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function precedence(operator: string): number {
|
export function precedence(operator: string): number {
|
||||||
// might be useful for refenecne to make it match
|
// might be useful for reference to make it match
|
||||||
// another commonly used lang https://www.w3schools.com/js/js_precedence.asp
|
// another commonly used lang https://www.w3schools.com/js/js_precedence.asp
|
||||||
if (['+', '-'].includes(operator)) {
|
if (['+', '-'].includes(operator)) {
|
||||||
return 11
|
return 11
|
||||||
|
57
src/lang/errors.ts
Normal file
57
src/lang/errors.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
export class KCLError {
|
||||||
|
kind: string | undefined
|
||||||
|
sourceRanges: [number, number][]
|
||||||
|
msg: string
|
||||||
|
constructor(
|
||||||
|
kind: string | undefined,
|
||||||
|
msg: string,
|
||||||
|
sourceRanges: [number, number][]
|
||||||
|
) {
|
||||||
|
this.kind = kind
|
||||||
|
this.msg = msg
|
||||||
|
this.sourceRanges = sourceRanges
|
||||||
|
Object.setPrototypeOf(this, KCLError.prototype)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class KCLSyntaxError extends KCLError {
|
||||||
|
constructor(msg: string, sourceRanges: [number, number][]) {
|
||||||
|
super('syntax', msg, sourceRanges)
|
||||||
|
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class KCLSemanticError extends KCLError {
|
||||||
|
constructor(msg: string, sourceRanges: [number, number][]) {
|
||||||
|
super('semantic', msg, sourceRanges)
|
||||||
|
Object.setPrototypeOf(this, KCLSemanticError.prototype)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class KCLTypeError extends KCLError {
|
||||||
|
constructor(msg: string, sourceRanges: [number, number][]) {
|
||||||
|
super('type', msg, sourceRanges)
|
||||||
|
Object.setPrototypeOf(this, KCLTypeError.prototype)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class KCLUnimplementedError extends KCLError {
|
||||||
|
constructor(msg: string, sourceRanges: [number, number][]) {
|
||||||
|
super('unimplemented feature', msg, sourceRanges)
|
||||||
|
Object.setPrototypeOf(this, KCLUnimplementedError.prototype)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class KCLValueAlreadyDefined extends KCLError {
|
||||||
|
constructor(key: string, sourceRanges: [number, number][]) {
|
||||||
|
super('name', `Key ${key} was already defined elsewhere`, sourceRanges)
|
||||||
|
Object.setPrototypeOf(this, KCLValueAlreadyDefined.prototype)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class KCLUndefinedValueError extends KCLError {
|
||||||
|
constructor(key: string, sourceRanges: [number, number][]) {
|
||||||
|
super('name', `Key ${key} has not been defined`, sourceRanges)
|
||||||
|
Object.setPrototypeOf(this, KCLUndefinedValueError.prototype)
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,13 @@ import {
|
|||||||
} from './abstractSyntaxTreeTypes'
|
} from './abstractSyntaxTreeTypes'
|
||||||
import { InternalFnNames } from './std/stdTypes'
|
import { InternalFnNames } from './std/stdTypes'
|
||||||
import { internalFns } from './std/std'
|
import { internalFns } from './std/std'
|
||||||
|
import {
|
||||||
|
KCLUndefinedValueError,
|
||||||
|
KCLValueAlreadyDefined,
|
||||||
|
KCLSyntaxError,
|
||||||
|
KCLSemanticError,
|
||||||
|
KCLTypeError,
|
||||||
|
} from './errors'
|
||||||
import {
|
import {
|
||||||
EngineCommandManager,
|
EngineCommandManager,
|
||||||
ArtifactMap,
|
ArtifactMap,
|
||||||
@ -124,11 +131,12 @@ export interface ProgramMemory {
|
|||||||
const addItemToMemory = (
|
const addItemToMemory = (
|
||||||
programMemory: ProgramMemory,
|
programMemory: ProgramMemory,
|
||||||
key: string,
|
key: string,
|
||||||
|
sourceRange: [[number, number]],
|
||||||
value: MemoryItem | Promise<MemoryItem>
|
value: MemoryItem | Promise<MemoryItem>
|
||||||
) => {
|
) => {
|
||||||
const _programMemory = programMemory
|
const _programMemory = programMemory
|
||||||
if (_programMemory.root[key] || _programMemory.pendingMemory[key]) {
|
if (_programMemory.root[key] || _programMemory.pendingMemory[key]) {
|
||||||
throw new Error(`Memory item ${key} already exists`)
|
throw new KCLValueAlreadyDefined(key, sourceRange)
|
||||||
}
|
}
|
||||||
if (value instanceof Promise) {
|
if (value instanceof Promise) {
|
||||||
_programMemory.pendingMemory[key] = value
|
_programMemory.pendingMemory[key] = value
|
||||||
@ -155,7 +163,8 @@ const promisifyMemoryItem = async (obj: MemoryItem) => {
|
|||||||
|
|
||||||
const getMemoryItem = async (
|
const getMemoryItem = async (
|
||||||
programMemory: ProgramMemory,
|
programMemory: ProgramMemory,
|
||||||
key: string
|
key: string,
|
||||||
|
sourceRanges: [number, number][]
|
||||||
): Promise<MemoryItem> => {
|
): Promise<MemoryItem> => {
|
||||||
if (programMemory.root[key]) {
|
if (programMemory.root[key]) {
|
||||||
return programMemory.root[key]
|
return programMemory.root[key]
|
||||||
@ -163,7 +172,7 @@ const getMemoryItem = async (
|
|||||||
if (programMemory.pendingMemory[key]) {
|
if (programMemory.pendingMemory[key]) {
|
||||||
return programMemory.pendingMemory[key] as Promise<MemoryItem>
|
return programMemory.pendingMemory[key] as Promise<MemoryItem>
|
||||||
}
|
}
|
||||||
throw new Error(`Memory item ${key} not found`)
|
throw new KCLUndefinedValueError(`Memory item ${key} not found`, sourceRanges)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const executor = async (
|
export const executor = async (
|
||||||
@ -249,27 +258,43 @@ export const _executor = async (
|
|||||||
_programMemory = addItemToMemory(
|
_programMemory = addItemToMemory(
|
||||||
_programMemory,
|
_programMemory,
|
||||||
variableName,
|
variableName,
|
||||||
|
[sourceRange],
|
||||||
value
|
value
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
_programMemory = addItemToMemory(_programMemory, variableName, {
|
_programMemory = addItemToMemory(
|
||||||
type: 'userVal',
|
_programMemory,
|
||||||
value,
|
variableName,
|
||||||
__meta,
|
[sourceRange],
|
||||||
})
|
{
|
||||||
|
type: 'userVal',
|
||||||
|
value,
|
||||||
|
__meta,
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else if (declaration.init.type === 'Identifier') {
|
} else if (declaration.init.type === 'Identifier') {
|
||||||
_programMemory = addItemToMemory(_programMemory, variableName, {
|
_programMemory = addItemToMemory(
|
||||||
type: 'userVal',
|
_programMemory,
|
||||||
value: _programMemory.root[declaration.init.name].value,
|
variableName,
|
||||||
__meta,
|
[sourceRange],
|
||||||
})
|
{
|
||||||
|
type: 'userVal',
|
||||||
|
value: _programMemory.root[declaration.init.name].value,
|
||||||
|
__meta,
|
||||||
|
}
|
||||||
|
)
|
||||||
} else if (declaration.init.type === 'Literal') {
|
} else if (declaration.init.type === 'Literal') {
|
||||||
_programMemory = addItemToMemory(_programMemory, variableName, {
|
_programMemory = addItemToMemory(
|
||||||
type: 'userVal',
|
_programMemory,
|
||||||
value: declaration.init.value,
|
variableName,
|
||||||
__meta,
|
[sourceRange],
|
||||||
})
|
{
|
||||||
|
type: 'userVal',
|
||||||
|
value: declaration.init.value,
|
||||||
|
__meta,
|
||||||
|
}
|
||||||
|
)
|
||||||
} else if (declaration.init.type === 'BinaryExpression') {
|
} else if (declaration.init.type === 'BinaryExpression') {
|
||||||
const prom = getBinaryExpressionResult(
|
const prom = getBinaryExpressionResult(
|
||||||
declaration.init,
|
declaration.init,
|
||||||
@ -280,6 +305,7 @@ export const _executor = async (
|
|||||||
_programMemory = addItemToMemory(
|
_programMemory = addItemToMemory(
|
||||||
_programMemory,
|
_programMemory,
|
||||||
variableName,
|
variableName,
|
||||||
|
[sourceRange],
|
||||||
promisifyMemoryItem({
|
promisifyMemoryItem({
|
||||||
type: 'userVal',
|
type: 'userVal',
|
||||||
value: prom,
|
value: prom,
|
||||||
@ -296,6 +322,7 @@ export const _executor = async (
|
|||||||
_programMemory = addItemToMemory(
|
_programMemory = addItemToMemory(
|
||||||
_programMemory,
|
_programMemory,
|
||||||
variableName,
|
variableName,
|
||||||
|
[sourceRange],
|
||||||
promisifyMemoryItem({
|
promisifyMemoryItem({
|
||||||
type: 'userVal',
|
type: 'userVal',
|
||||||
value: prom,
|
value: prom,
|
||||||
@ -332,7 +359,11 @@ export const _executor = async (
|
|||||||
value: await prom,
|
value: await prom,
|
||||||
}
|
}
|
||||||
} else if (element.type === 'Identifier') {
|
} else if (element.type === 'Identifier') {
|
||||||
const node = await getMemoryItem(_programMemory, element.name)
|
const node = await getMemoryItem(
|
||||||
|
_programMemory,
|
||||||
|
element.name,
|
||||||
|
[[element.start, element.end]]
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
value: node.value,
|
value: node.value,
|
||||||
__meta: node.__meta[node.__meta.length - 1],
|
__meta: node.__meta[node.__meta.length - 1],
|
||||||
@ -348,8 +379,11 @@ export const _executor = async (
|
|||||||
value: await prom,
|
value: await prom,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new KCLSyntaxError(
|
||||||
`Unexpected element type ${element.type} in array expression`
|
`Unexpected element type ${element.type} in array expression`,
|
||||||
|
// TODO: Refactor this whole block into a `switch` so that we have a specific
|
||||||
|
// type here and can put a sourceRange.
|
||||||
|
[]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -358,11 +392,16 @@ export const _executor = async (
|
|||||||
const meta = awaitedValueInfo
|
const meta = awaitedValueInfo
|
||||||
.filter(({ __meta }) => __meta)
|
.filter(({ __meta }) => __meta)
|
||||||
.map(({ __meta }) => __meta) as Metadata[]
|
.map(({ __meta }) => __meta) as Metadata[]
|
||||||
_programMemory = addItemToMemory(_programMemory, variableName, {
|
_programMemory = addItemToMemory(
|
||||||
type: 'userVal',
|
_programMemory,
|
||||||
value: awaitedValueInfo.map(({ value }) => value),
|
variableName,
|
||||||
__meta: [...__meta, ...meta],
|
[sourceRange],
|
||||||
})
|
{
|
||||||
|
type: 'userVal',
|
||||||
|
value: awaitedValueInfo.map(({ value }) => value),
|
||||||
|
__meta: [...__meta, ...meta],
|
||||||
|
}
|
||||||
|
)
|
||||||
} else if (declaration.init.type === 'ObjectExpression') {
|
} else if (declaration.init.type === 'ObjectExpression') {
|
||||||
const prom = executeObjectExpression(
|
const prom = executeObjectExpression(
|
||||||
_programMemory,
|
_programMemory,
|
||||||
@ -373,6 +412,7 @@ export const _executor = async (
|
|||||||
_programMemory = addItemToMemory(
|
_programMemory = addItemToMemory(
|
||||||
_programMemory,
|
_programMemory,
|
||||||
variableName,
|
variableName,
|
||||||
|
[sourceRange],
|
||||||
promisifyMemoryItem({
|
promisifyMemoryItem({
|
||||||
type: 'userVal',
|
type: 'userVal',
|
||||||
value: prom,
|
value: prom,
|
||||||
@ -385,6 +425,7 @@ export const _executor = async (
|
|||||||
_programMemory = addItemToMemory(
|
_programMemory = addItemToMemory(
|
||||||
_programMemory,
|
_programMemory,
|
||||||
declaration.id.name,
|
declaration.id.name,
|
||||||
|
[sourceRange],
|
||||||
{
|
{
|
||||||
type: 'userVal',
|
type: 'userVal',
|
||||||
value: async (...args: any[]) => {
|
value: async (...args: any[]) => {
|
||||||
@ -397,20 +438,27 @@ export const _executor = async (
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
if (args.length > fnInit.params.length) {
|
if (args.length > fnInit.params.length) {
|
||||||
throw new Error(
|
throw new KCLSyntaxError(
|
||||||
`Too many arguments passed to function ${declaration.id.name}`
|
`Too many arguments passed to function ${declaration.id.name}`,
|
||||||
|
[[declaration.start, declaration.end]]
|
||||||
)
|
)
|
||||||
} else if (args.length < fnInit.params.length) {
|
} else if (args.length < fnInit.params.length) {
|
||||||
throw new Error(
|
throw new KCLSyntaxError(
|
||||||
`Too few arguments passed to function ${declaration.id.name}`
|
`Too few arguments passed to function ${declaration.id.name}`,
|
||||||
|
[[declaration.start, declaration.end]]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
fnInit.params.forEach((param, index) => {
|
fnInit.params.forEach((param, index) => {
|
||||||
fnMemory = addItemToMemory(fnMemory, param.name, {
|
fnMemory = addItemToMemory(
|
||||||
type: 'userVal',
|
fnMemory,
|
||||||
value: args[index],
|
param.name,
|
||||||
__meta,
|
[sourceRange],
|
||||||
})
|
{
|
||||||
|
type: 'userVal',
|
||||||
|
value: args[index],
|
||||||
|
__meta,
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
const prom = _executor(
|
const prom = _executor(
|
||||||
fnInit.body,
|
fnInit.body,
|
||||||
@ -437,6 +485,7 @@ export const _executor = async (
|
|||||||
_programMemory = addItemToMemory(
|
_programMemory = addItemToMemory(
|
||||||
_programMemory,
|
_programMemory,
|
||||||
variableName,
|
variableName,
|
||||||
|
[sourceRange],
|
||||||
promisifyMemoryItem({
|
promisifyMemoryItem({
|
||||||
type: 'userVal',
|
type: 'userVal',
|
||||||
value: prom,
|
value: prom,
|
||||||
@ -454,6 +503,7 @@ export const _executor = async (
|
|||||||
_programMemory = addItemToMemory(
|
_programMemory = addItemToMemory(
|
||||||
_programMemory,
|
_programMemory,
|
||||||
variableName,
|
variableName,
|
||||||
|
[sourceRange],
|
||||||
prom.then((a) => {
|
prom.then((a) => {
|
||||||
return a?.type === 'sketchGroup' || a?.type === 'extrudeGroup'
|
return a?.type === 'sketchGroup' || a?.type === 'extrudeGroup'
|
||||||
? a
|
? a
|
||||||
@ -465,8 +515,9 @@ export const _executor = async (
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new KCLSyntaxError(
|
||||||
'Unsupported declaration type: ' + declaration.init.type
|
'Unsupported declaration type: ' + declaration.init.type,
|
||||||
|
[[declaration.start, declaration.end]]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -483,10 +534,18 @@ export const _executor = async (
|
|||||||
})
|
})
|
||||||
if ('show' === functionName) {
|
if ('show' === functionName) {
|
||||||
if (options.bodyType !== 'root') {
|
if (options.bodyType !== 'root') {
|
||||||
throw new Error(`Cannot call ${functionName} outside of a root`)
|
throw new KCLSemanticError(
|
||||||
|
`Cannot call ${functionName} outside of a root`,
|
||||||
|
[[statement.start, statement.end]]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
_programMemory.return = expression.arguments as any // todo memory redo
|
_programMemory.return = expression.arguments as any // todo memory redo
|
||||||
} else {
|
} else {
|
||||||
|
if (_programMemory.root[functionName] == undefined) {
|
||||||
|
throw new KCLSemanticError(`No such name ${functionName} defined`, [
|
||||||
|
[statement.start, statement.end],
|
||||||
|
])
|
||||||
|
}
|
||||||
_programMemory.root[functionName].value(...args)
|
_programMemory.root[functionName].value(...args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -693,7 +752,9 @@ async function executePipeBody(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error('Invalid pipe expression')
|
throw new KCLSyntaxError('Invalid pipe expression', [
|
||||||
|
[expression.start, expression.end],
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
async function executeObjectExpression(
|
async function executeObjectExpression(
|
||||||
@ -742,7 +803,9 @@ async function executeObjectExpression(
|
|||||||
obj[property.key.name] = await prom
|
obj[property.key.name] = await prom
|
||||||
} else if (property.value.type === 'Identifier') {
|
} else if (property.value.type === 'Identifier') {
|
||||||
obj[property.key.name] = (
|
obj[property.key.name] = (
|
||||||
await getMemoryItem(_programMemory, property.value.name)
|
await getMemoryItem(_programMemory, property.value.name, [
|
||||||
|
[property.value.start, property.value.end],
|
||||||
|
])
|
||||||
).value
|
).value
|
||||||
} else if (property.value.type === 'ObjectExpression') {
|
} else if (property.value.type === 'ObjectExpression') {
|
||||||
const prom = executeObjectExpression(
|
const prom = executeObjectExpression(
|
||||||
@ -780,13 +843,15 @@ async function executeObjectExpression(
|
|||||||
proms.push(prom)
|
proms.push(prom)
|
||||||
obj[property.key.name] = await prom
|
obj[property.key.name] = await prom
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new KCLSyntaxError(
|
||||||
`Unexpected property type ${property.value.type} in object expression`
|
`Unexpected property type ${property.value.type} in object expression`,
|
||||||
|
[[property.value.start, property.value.end]]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new KCLSyntaxError(
|
||||||
`Unexpected property type ${property.type} in object expression`
|
`Unexpected property type ${property.type} in object expression`,
|
||||||
|
[[property.value.start, property.value.end]]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -850,7 +915,7 @@ async function executeArrayExpression(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
throw new Error('Invalid argument type')
|
throw new KCLTypeError('Invalid argument type', [[el.start, el.end]])
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -891,7 +956,9 @@ async function executeCallExpression(
|
|||||||
return arg.value
|
return arg.value
|
||||||
} else if (arg.type === 'Identifier') {
|
} else if (arg.type === 'Identifier') {
|
||||||
await new Promise((r) => setTimeout(r)) // push into next even loop, but also probably should fix this
|
await new Promise((r) => setTimeout(r)) // push into next even loop, but also probably should fix this
|
||||||
const temp = await getMemoryItem(programMemory, arg.name)
|
const temp = await getMemoryItem(programMemory, arg.name, [
|
||||||
|
[arg.start, arg.end],
|
||||||
|
])
|
||||||
return temp?.type === 'userVal' ? temp.value : temp
|
return temp?.type === 'userVal' ? temp.value : temp
|
||||||
} else if (arg.type === 'PipeSubstitution') {
|
} else if (arg.type === 'PipeSubstitution') {
|
||||||
return previousResults[expressionIndex - 1]
|
return previousResults[expressionIndex - 1]
|
||||||
@ -933,7 +1000,9 @@ async function executeCallExpression(
|
|||||||
_pipeInfo
|
_pipeInfo
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
throw new Error('Invalid argument type in function call')
|
throw new KCLSyntaxError('Invalid argument type in function call', [
|
||||||
|
[arg.start, arg.end],
|
||||||
|
])
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
if (functionName in internalFns) {
|
if (functionName in internalFns) {
|
||||||
|
@ -20,6 +20,7 @@ import {
|
|||||||
SourceRangeMap,
|
SourceRangeMap,
|
||||||
EngineCommandManager,
|
EngineCommandManager,
|
||||||
} from './lang/std/engineConnection'
|
} from './lang/std/engineConnection'
|
||||||
|
import { KCLError } from './lang/errors'
|
||||||
|
|
||||||
export type Selection = {
|
export type Selection = {
|
||||||
type: 'default' | 'line-end' | 'line-mid'
|
type: 'default' | 'line-end' | 'line-mid'
|
||||||
@ -122,6 +123,8 @@ export interface StoreState {
|
|||||||
setGuiMode: (guiMode: GuiModes) => void
|
setGuiMode: (guiMode: GuiModes) => void
|
||||||
logs: string[]
|
logs: string[]
|
||||||
addLog: (log: string) => void
|
addLog: (log: string) => void
|
||||||
|
kclErrors: KCLError[]
|
||||||
|
addKCLError: (err: KCLError) => void
|
||||||
resetLogs: () => void
|
resetLogs: () => void
|
||||||
ast: Program | null
|
ast: Program | null
|
||||||
setAst: (ast: Program | null) => void
|
setAst: (ast: Program | null) => void
|
||||||
@ -251,6 +254,10 @@ export const useStore = create<StoreState>()(
|
|||||||
set((state) => ({ logs: [...state.logs, log] }))
|
set((state) => ({ logs: [...state.logs, log] }))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
kclErrors: [],
|
||||||
|
addKCLError: (e) => {
|
||||||
|
set((state) => ({ kclErrors: [...state.kclErrors, e] }))
|
||||||
|
},
|
||||||
resetLogs: () => {
|
resetLogs: () => {
|
||||||
set({ logs: [] })
|
set({ logs: [] })
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user