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,
|
||||
} from './editor/highlightextension'
|
||||
import { Selections, useStore } from './useStore'
|
||||
import { Logs } from './components/Logs'
|
||||
import { Logs, KCLErrors } from './components/Logs'
|
||||
import { PanelHeader } from './components/PanelHeader'
|
||||
import { MemoryPanel } from './components/MemoryPanel'
|
||||
import { useHotKeyListener } from './hooks/useHotKeyListener'
|
||||
@ -22,6 +22,7 @@ import { EngineCommandManager } from './lang/std/engineConnection'
|
||||
import { isOverlap } from './lib/utils'
|
||||
import { SetToken } from './components/TokenInput'
|
||||
import { AppHeader } from './components/AppHeader'
|
||||
import { KCLError } from './lang/errors'
|
||||
|
||||
export function App() {
|
||||
const cam = useRef()
|
||||
@ -32,6 +33,7 @@ export function App() {
|
||||
setSelectionRanges,
|
||||
selectionRanges,
|
||||
addLog,
|
||||
addKCLError,
|
||||
code,
|
||||
setCode,
|
||||
setAst,
|
||||
@ -79,6 +81,7 @@ export function App() {
|
||||
token: s.token,
|
||||
formatCode: s.formatCode,
|
||||
debugPanel: s.debugPanel,
|
||||
addKCLError: s.addKCLError,
|
||||
}))
|
||||
// const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => {
|
||||
const onChange = (value: string, viewUpdate: ViewUpdate) => {
|
||||
@ -249,9 +252,13 @@ export function App() {
|
||||
setError()
|
||||
})
|
||||
} catch (e: any) {
|
||||
setError('problem')
|
||||
console.log(e)
|
||||
addLog(e)
|
||||
if (e instanceof KCLError) {
|
||||
addKCLError(e)
|
||||
} else {
|
||||
setError('problem')
|
||||
console.log(e)
|
||||
addLog(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
asyncWrap()
|
||||
@ -261,7 +268,7 @@ export function App() {
|
||||
<AppHeader />
|
||||
<ModalContainer />
|
||||
<Allotment snap={true}>
|
||||
<Allotment vertical defaultSizes={[5, 400, 1, 1]} minSize={20}>
|
||||
<Allotment vertical defaultSizes={[5, 400, 1, 1, 200]} minSize={20}>
|
||||
<SetToken />
|
||||
<div className="h-full flex flex-col items-start">
|
||||
<PanelHeader title="Editor" />
|
||||
@ -288,6 +295,7 @@ export function App() {
|
||||
</div>
|
||||
<MemoryPanel />
|
||||
<Logs />
|
||||
<KCLErrors />
|
||||
</Allotment>
|
||||
<Allotment vertical defaultSizes={[40, 400]} minSize={20}>
|
||||
<Stream />
|
||||
|
@ -38,3 +38,36 @@ export const Logs = () => {
|
||||
</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 { parseExpression } from './astMathExpressions'
|
||||
import { KCLSyntaxError, KCLUnimplementedError } from './errors'
|
||||
import {
|
||||
BinaryPart,
|
||||
BodyItem,
|
||||
@ -128,6 +129,11 @@ function makeArguments(
|
||||
}
|
||||
}
|
||||
const nextBraceOrCommaToken = nextMeaningfulToken(tokens, argumentToken.index)
|
||||
if (nextBraceOrCommaToken.token == undefined) {
|
||||
throw new KCLSyntaxError('Expected argument', [
|
||||
[argumentToken.token.start, argumentToken.token.end],
|
||||
])
|
||||
}
|
||||
const isIdentifierOrLiteral =
|
||||
nextBraceOrCommaToken.token.type === 'comma' ||
|
||||
nextBraceOrCommaToken.token.type === 'brace'
|
||||
@ -282,7 +288,10 @@ function makeArguments(
|
||||
) {
|
||||
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(
|
||||
@ -406,14 +415,18 @@ function makeValue(
|
||||
lastIndex: arrowFunctionLastIndex,
|
||||
}
|
||||
} 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 === '-') {
|
||||
const { expression, lastIndex } = makeUnaryExpression(tokens, index)
|
||||
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(
|
||||
@ -505,7 +518,9 @@ function makeArrayElements(
|
||||
nextToken.token.type === 'brace' && nextToken.token.value === ']'
|
||||
const isComma = nextToken.token.type === 'comma'
|
||||
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
|
||||
? nextToken.index
|
||||
@ -617,7 +632,10 @@ function makeMemberExpression(
|
||||
const keysInfo = collectObjectKeys(tokens, index)
|
||||
const lastKey = keysInfo[keysInfo.length - 1]
|
||||
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)
|
||||
let memberExpression: MemberExpression = {
|
||||
type: 'MemberExpression',
|
||||
@ -807,7 +825,10 @@ function makePipeBody(
|
||||
value = val.value
|
||||
lastIndex = val.lastIndex
|
||||
} 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)
|
||||
@ -1073,7 +1094,7 @@ function makeBody(
|
||||
lastIndex,
|
||||
}
|
||||
}
|
||||
throw new Error('Unexpected token')
|
||||
throw new KCLSyntaxError('Unexpected token', [[token.start, token.end]])
|
||||
}
|
||||
export const abstractSyntaxTree = (tokens: Token[]): Program => {
|
||||
const { body, nonCodeMeta } = makeBody({ tokens })
|
||||
@ -1227,15 +1248,26 @@ export function findClosingBrace(
|
||||
if (isFirstCall) {
|
||||
searchOpeningBrace = currentToken.value
|
||||
if (!['(', '{', '['].includes(searchOpeningBrace)) {
|
||||
throw new Error(
|
||||
`expected to be started on a opening brace ( { [, instead found '${searchOpeningBrace}'`
|
||||
throw new KCLSyntaxError(
|
||||
`expected to be started on a opening brace ( { [, instead found '${searchOpeningBrace}'`,
|
||||
[[currentToken.start, currentToken.end]]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const foundClosingBrace =
|
||||
_braceCount === 1 &&
|
||||
currentToken.value === closingBraceMap[searchOpeningBrace]
|
||||
const foundClosingBrace = (() => {
|
||||
try {
|
||||
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 foundAnotherClosingBrace =
|
||||
currentToken.value === closingBraceMap[searchOpeningBrace]
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
isNotCodeToken,
|
||||
} from './abstractSyntaxTree'
|
||||
import { Token } from './tokeniser'
|
||||
import { KCLSyntaxError } from './errors'
|
||||
|
||||
export function reversePolishNotation(
|
||||
tokens: Token[],
|
||||
@ -82,7 +83,9 @@ export function reversePolishNotation(
|
||||
if (isNotCodeToken(currentToken)) {
|
||||
return reversePolishNotation(tokens.slice(1), previousPostfix, operators)
|
||||
}
|
||||
throw new Error('Unknown token')
|
||||
throw new KCLSyntaxError('Unknown token', [
|
||||
[currentToken.start, currentToken.end],
|
||||
])
|
||||
}
|
||||
|
||||
interface ParenthesisToken {
|
||||
@ -204,21 +207,27 @@ const buildTree = (
|
||||
}
|
||||
|
||||
export function parseExpression(tokens: Token[]): BinaryExpression {
|
||||
const treeWithMabyeBadTopLevelStartEnd = buildTree(
|
||||
const treeWithMaybeBadTopLevelStartEnd = buildTree(
|
||||
reversePolishNotation(tokens)
|
||||
)
|
||||
const left = treeWithMabyeBadTopLevelStartEnd?.left as any
|
||||
const start = left?.startExtended || treeWithMabyeBadTopLevelStartEnd?.start
|
||||
const left = treeWithMaybeBadTopLevelStartEnd?.left as any
|
||||
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.endExtended
|
||||
|
||||
const right = treeWithMabyeBadTopLevelStartEnd?.right as any
|
||||
const end = right?.endExtended || treeWithMabyeBadTopLevelStartEnd?.end
|
||||
const right = treeWithMaybeBadTopLevelStartEnd?.right as any
|
||||
const end = right?.endExtended || treeWithMaybeBadTopLevelStartEnd?.end
|
||||
delete right.startExtended
|
||||
delete right.endExtended
|
||||
|
||||
const tree: BinaryExpression = {
|
||||
...treeWithMabyeBadTopLevelStartEnd,
|
||||
...treeWithMaybeBadTopLevelStartEnd,
|
||||
start,
|
||||
end,
|
||||
left,
|
||||
@ -232,7 +241,7 @@ function _precedence(operator: Token): 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
|
||||
if (['+', '-'].includes(operator)) {
|
||||
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'
|
||||
import { InternalFnNames } from './std/stdTypes'
|
||||
import { internalFns } from './std/std'
|
||||
import {
|
||||
KCLUndefinedValueError,
|
||||
KCLValueAlreadyDefined,
|
||||
KCLSyntaxError,
|
||||
KCLSemanticError,
|
||||
KCLTypeError,
|
||||
} from './errors'
|
||||
import {
|
||||
EngineCommandManager,
|
||||
ArtifactMap,
|
||||
@ -124,11 +131,12 @@ export interface ProgramMemory {
|
||||
const addItemToMemory = (
|
||||
programMemory: ProgramMemory,
|
||||
key: string,
|
||||
sourceRange: [[number, number]],
|
||||
value: MemoryItem | Promise<MemoryItem>
|
||||
) => {
|
||||
const _programMemory = programMemory
|
||||
if (_programMemory.root[key] || _programMemory.pendingMemory[key]) {
|
||||
throw new Error(`Memory item ${key} already exists`)
|
||||
throw new KCLValueAlreadyDefined(key, sourceRange)
|
||||
}
|
||||
if (value instanceof Promise) {
|
||||
_programMemory.pendingMemory[key] = value
|
||||
@ -155,7 +163,8 @@ const promisifyMemoryItem = async (obj: MemoryItem) => {
|
||||
|
||||
const getMemoryItem = async (
|
||||
programMemory: ProgramMemory,
|
||||
key: string
|
||||
key: string,
|
||||
sourceRanges: [number, number][]
|
||||
): Promise<MemoryItem> => {
|
||||
if (programMemory.root[key]) {
|
||||
return programMemory.root[key]
|
||||
@ -163,7 +172,7 @@ const getMemoryItem = async (
|
||||
if (programMemory.pendingMemory[key]) {
|
||||
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 (
|
||||
@ -249,27 +258,43 @@ export const _executor = async (
|
||||
_programMemory = addItemToMemory(
|
||||
_programMemory,
|
||||
variableName,
|
||||
[sourceRange],
|
||||
value
|
||||
)
|
||||
} else {
|
||||
_programMemory = addItemToMemory(_programMemory, variableName, {
|
||||
type: 'userVal',
|
||||
value,
|
||||
__meta,
|
||||
})
|
||||
_programMemory = addItemToMemory(
|
||||
_programMemory,
|
||||
variableName,
|
||||
[sourceRange],
|
||||
{
|
||||
type: 'userVal',
|
||||
value,
|
||||
__meta,
|
||||
}
|
||||
)
|
||||
}
|
||||
} else if (declaration.init.type === 'Identifier') {
|
||||
_programMemory = addItemToMemory(_programMemory, variableName, {
|
||||
type: 'userVal',
|
||||
value: _programMemory.root[declaration.init.name].value,
|
||||
__meta,
|
||||
})
|
||||
_programMemory = addItemToMemory(
|
||||
_programMemory,
|
||||
variableName,
|
||||
[sourceRange],
|
||||
{
|
||||
type: 'userVal',
|
||||
value: _programMemory.root[declaration.init.name].value,
|
||||
__meta,
|
||||
}
|
||||
)
|
||||
} else if (declaration.init.type === 'Literal') {
|
||||
_programMemory = addItemToMemory(_programMemory, variableName, {
|
||||
type: 'userVal',
|
||||
value: declaration.init.value,
|
||||
__meta,
|
||||
})
|
||||
_programMemory = addItemToMemory(
|
||||
_programMemory,
|
||||
variableName,
|
||||
[sourceRange],
|
||||
{
|
||||
type: 'userVal',
|
||||
value: declaration.init.value,
|
||||
__meta,
|
||||
}
|
||||
)
|
||||
} else if (declaration.init.type === 'BinaryExpression') {
|
||||
const prom = getBinaryExpressionResult(
|
||||
declaration.init,
|
||||
@ -280,6 +305,7 @@ export const _executor = async (
|
||||
_programMemory = addItemToMemory(
|
||||
_programMemory,
|
||||
variableName,
|
||||
[sourceRange],
|
||||
promisifyMemoryItem({
|
||||
type: 'userVal',
|
||||
value: prom,
|
||||
@ -296,6 +322,7 @@ export const _executor = async (
|
||||
_programMemory = addItemToMemory(
|
||||
_programMemory,
|
||||
variableName,
|
||||
[sourceRange],
|
||||
promisifyMemoryItem({
|
||||
type: 'userVal',
|
||||
value: prom,
|
||||
@ -332,7 +359,11 @@ export const _executor = async (
|
||||
value: await prom,
|
||||
}
|
||||
} else if (element.type === 'Identifier') {
|
||||
const node = await getMemoryItem(_programMemory, element.name)
|
||||
const node = await getMemoryItem(
|
||||
_programMemory,
|
||||
element.name,
|
||||
[[element.start, element.end]]
|
||||
)
|
||||
return {
|
||||
value: node.value,
|
||||
__meta: node.__meta[node.__meta.length - 1],
|
||||
@ -348,8 +379,11 @@ export const _executor = async (
|
||||
value: await prom,
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
`Unexpected element type ${element.type} in array expression`
|
||||
throw new KCLSyntaxError(
|
||||
`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
|
||||
.filter(({ __meta }) => __meta)
|
||||
.map(({ __meta }) => __meta) as Metadata[]
|
||||
_programMemory = addItemToMemory(_programMemory, variableName, {
|
||||
type: 'userVal',
|
||||
value: awaitedValueInfo.map(({ value }) => value),
|
||||
__meta: [...__meta, ...meta],
|
||||
})
|
||||
_programMemory = addItemToMemory(
|
||||
_programMemory,
|
||||
variableName,
|
||||
[sourceRange],
|
||||
{
|
||||
type: 'userVal',
|
||||
value: awaitedValueInfo.map(({ value }) => value),
|
||||
__meta: [...__meta, ...meta],
|
||||
}
|
||||
)
|
||||
} else if (declaration.init.type === 'ObjectExpression') {
|
||||
const prom = executeObjectExpression(
|
||||
_programMemory,
|
||||
@ -373,6 +412,7 @@ export const _executor = async (
|
||||
_programMemory = addItemToMemory(
|
||||
_programMemory,
|
||||
variableName,
|
||||
[sourceRange],
|
||||
promisifyMemoryItem({
|
||||
type: 'userVal',
|
||||
value: prom,
|
||||
@ -385,6 +425,7 @@ export const _executor = async (
|
||||
_programMemory = addItemToMemory(
|
||||
_programMemory,
|
||||
declaration.id.name,
|
||||
[sourceRange],
|
||||
{
|
||||
type: 'userVal',
|
||||
value: async (...args: any[]) => {
|
||||
@ -397,20 +438,27 @@ export const _executor = async (
|
||||
},
|
||||
}
|
||||
if (args.length > fnInit.params.length) {
|
||||
throw new Error(
|
||||
`Too many arguments passed to function ${declaration.id.name}`
|
||||
throw new KCLSyntaxError(
|
||||
`Too many arguments passed to function ${declaration.id.name}`,
|
||||
[[declaration.start, declaration.end]]
|
||||
)
|
||||
} else if (args.length < fnInit.params.length) {
|
||||
throw new Error(
|
||||
`Too few arguments passed to function ${declaration.id.name}`
|
||||
throw new KCLSyntaxError(
|
||||
`Too few arguments passed to function ${declaration.id.name}`,
|
||||
[[declaration.start, declaration.end]]
|
||||
)
|
||||
}
|
||||
fnInit.params.forEach((param, index) => {
|
||||
fnMemory = addItemToMemory(fnMemory, param.name, {
|
||||
type: 'userVal',
|
||||
value: args[index],
|
||||
__meta,
|
||||
})
|
||||
fnMemory = addItemToMemory(
|
||||
fnMemory,
|
||||
param.name,
|
||||
[sourceRange],
|
||||
{
|
||||
type: 'userVal',
|
||||
value: args[index],
|
||||
__meta,
|
||||
}
|
||||
)
|
||||
})
|
||||
const prom = _executor(
|
||||
fnInit.body,
|
||||
@ -437,6 +485,7 @@ export const _executor = async (
|
||||
_programMemory = addItemToMemory(
|
||||
_programMemory,
|
||||
variableName,
|
||||
[sourceRange],
|
||||
promisifyMemoryItem({
|
||||
type: 'userVal',
|
||||
value: prom,
|
||||
@ -454,6 +503,7 @@ export const _executor = async (
|
||||
_programMemory = addItemToMemory(
|
||||
_programMemory,
|
||||
variableName,
|
||||
[sourceRange],
|
||||
prom.then((a) => {
|
||||
return a?.type === 'sketchGroup' || a?.type === 'extrudeGroup'
|
||||
? a
|
||||
@ -465,8 +515,9 @@ export const _executor = async (
|
||||
})
|
||||
)
|
||||
} else {
|
||||
throw new Error(
|
||||
'Unsupported declaration type: ' + declaration.init.type
|
||||
throw new KCLSyntaxError(
|
||||
'Unsupported declaration type: ' + declaration.init.type,
|
||||
[[declaration.start, declaration.end]]
|
||||
)
|
||||
}
|
||||
})
|
||||
@ -483,10 +534,18 @@ export const _executor = async (
|
||||
})
|
||||
if ('show' === functionName) {
|
||||
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
|
||||
} else {
|
||||
if (_programMemory.root[functionName] == undefined) {
|
||||
throw new KCLSemanticError(`No such name ${functionName} defined`, [
|
||||
[statement.start, statement.end],
|
||||
])
|
||||
}
|
||||
_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(
|
||||
@ -742,7 +803,9 @@ async function executeObjectExpression(
|
||||
obj[property.key.name] = await prom
|
||||
} else if (property.value.type === 'Identifier') {
|
||||
obj[property.key.name] = (
|
||||
await getMemoryItem(_programMemory, property.value.name)
|
||||
await getMemoryItem(_programMemory, property.value.name, [
|
||||
[property.value.start, property.value.end],
|
||||
])
|
||||
).value
|
||||
} else if (property.value.type === 'ObjectExpression') {
|
||||
const prom = executeObjectExpression(
|
||||
@ -780,13 +843,15 @@ async function executeObjectExpression(
|
||||
proms.push(prom)
|
||||
obj[property.key.name] = await prom
|
||||
} else {
|
||||
throw new Error(
|
||||
`Unexpected property type ${property.value.type} in object expression`
|
||||
throw new KCLSyntaxError(
|
||||
`Unexpected property type ${property.value.type} in object expression`,
|
||||
[[property.value.start, property.value.end]]
|
||||
)
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
`Unexpected property type ${property.type} in object expression`
|
||||
throw new KCLSyntaxError(
|
||||
`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
|
||||
} else if (arg.type === 'Identifier') {
|
||||
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
|
||||
} else if (arg.type === 'PipeSubstitution') {
|
||||
return previousResults[expressionIndex - 1]
|
||||
@ -933,7 +1000,9 @@ async function executeCallExpression(
|
||||
_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) {
|
||||
|
@ -20,6 +20,7 @@ import {
|
||||
SourceRangeMap,
|
||||
EngineCommandManager,
|
||||
} from './lang/std/engineConnection'
|
||||
import { KCLError } from './lang/errors'
|
||||
|
||||
export type Selection = {
|
||||
type: 'default' | 'line-end' | 'line-mid'
|
||||
@ -122,6 +123,8 @@ export interface StoreState {
|
||||
setGuiMode: (guiMode: GuiModes) => void
|
||||
logs: string[]
|
||||
addLog: (log: string) => void
|
||||
kclErrors: KCLError[]
|
||||
addKCLError: (err: KCLError) => void
|
||||
resetLogs: () => void
|
||||
ast: Program | null
|
||||
setAst: (ast: Program | null) => void
|
||||
@ -251,6 +254,10 @@ export const useStore = create<StoreState>()(
|
||||
set((state) => ({ logs: [...state.logs, log] }))
|
||||
}
|
||||
},
|
||||
kclErrors: [],
|
||||
addKCLError: (e) => {
|
||||
set((state) => ({ kclErrors: [...state.kclErrors, e] }))
|
||||
},
|
||||
resetLogs: () => {
|
||||
set({ logs: [] })
|
||||
},
|
||||
|
Reference in New Issue
Block a user