Compare commits

..

1 Commits

Author SHA1 Message Date
e1f1051fe0 Grackle: Add Paul's QR code as a unit test 2024-03-17 10:54:42 -05:00
31 changed files with 3237 additions and 1249 deletions

View File

@ -1,3 +1,3 @@
[codespell] [codespell]
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,absolutey ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo
skip: **/target,node_modules,build,**/Cargo.lock skip: **/target,node_modules,build,**/Cargo.lock

50
.github/workflows/cargo-build.yml vendored Normal file
View File

@ -0,0 +1,50 @@
on:
push:
branches:
- main
paths:
- '**.rs'
- '**/Cargo.toml'
- '**/Cargo.lock'
- '**/rust-toolchain.toml'
- .github/workflows/cargo-build.yml
pull_request:
paths:
- '**.rs'
- '**/Cargo.toml'
- '**/Cargo.lock'
- '**/rust-toolchain.toml'
- .github/workflows/cargo-build.yml
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
name: cargo build
jobs:
cargobuild:
name: cargo build
runs-on: ubuntu-latest
strategy:
matrix:
dir: ['src/wasm-lib']
steps:
- uses: actions/checkout@v4
- name: Install latest rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: install dependencies
if: matrix.dir == 'src-tauri'
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
- name: Rust Cache
uses: Swatinem/rust-cache@v2.6.1
- name: Run cargo build
run: |
cd "${{ matrix.dir }}"
cargo build --all
shell: bash

View File

@ -9,6 +9,12 @@ on:
- '**.rs' - '**.rs'
- .github/workflows/cargo-clippy.yml - .github/workflows/cargo-clippy.yml
pull_request: pull_request:
paths:
- '**/Cargo.toml'
- '**/Cargo.lock'
- '**/rust-toolchain.toml'
- '**.rs'
- .github/workflows/cargo-build.yml
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true cancel-in-progress: true

View File

@ -1,36 +0,0 @@
name: Check Onboarding KCL
on:
pull_request:
types: [opened, synchronize]
paths:
- 'src/lib/exampleKcl.ts'
permissions:
contents: read
issues: write
pull-requests: write
jobs:
comment:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Comment on PR
uses: actions/github-script@v6
with:
script: |
const message = '`src/lib/exampleKcl.ts` has been updated in this PR, please review and update the `src/routes/onboarding`, if needed.';
const issue_number = context.payload.pull_request.number;
const owner = context.repo.owner;
const repo = context.repo.repo;
// Post a comment on the PR
await github.rest.issues.createComment({
owner,
repo,
issue_number,
body: message,
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

View File

@ -1,6 +1,6 @@
{ {
"name": "untitled-app", "name": "untitled-app",
"version": "0.16.0", "version": "0.15.6",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.10.2", "@codemirror/autocomplete": "^6.10.2",

2
src-tauri/Cargo.lock generated
View File

@ -3876,7 +3876,7 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin-fs-extra" name = "tauri-plugin-fs-extra"
version = "0.0.0" version = "0.0.0"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#6db4320e98a4278d605dd05d4d87e1433939a7cd" source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#d95a1b382f512a0be748e7a89e0f8fc4278010e2"
dependencies = [ dependencies = [
"log", "log",
"serde", "serde",

View File

@ -7,7 +7,7 @@
}, },
"package": { "package": {
"productName": "zoo-modeling-app", "productName": "zoo-modeling-app",
"version": "0.16.0" "version": "0.15.6"
}, },
"tauri": { "tauri": {
"allowlist": { "allowlist": {

View File

@ -59,7 +59,6 @@ const router = createBrowserRouter([
{ {
loader: fileLoader, loader: fileLoader,
id: paths.FILE, id: paths.FILE,
path: paths.FILE + '/:id',
element: ( element: (
<Auth> <Auth>
<FileMachineProvider> <FileMachineProvider>
@ -75,11 +74,8 @@ const router = createBrowserRouter([
), ),
children: [ children: [
{ {
path: paths.FILE + '/:id',
loader: onboardingRedirectLoader, loader: onboardingRedirectLoader,
index: true,
element: <></>,
},
{
children: [ children: [
{ {
path: makeUrlPathRelative(paths.SETTINGS), path: makeUrlPathRelative(paths.SETTINGS),

View File

@ -30,7 +30,7 @@ describe('NetworkHealthIndicator tests', () => {
fireEvent.click(screen.getByTestId('network-toggle')) fireEvent.click(screen.getByTestId('network-toggle'))
expect(screen.getByTestId('network')).toHaveTextContent( expect(screen.getByTestId('network')).toHaveTextContent(
NETWORK_HEALTH_TEXT[NetworkHealthState.Ok] NETWORK_HEALTH_TEXT[NetworkHealthState.Issue]
) )
}) })

View File

@ -6,7 +6,8 @@ import {
ConnectingTypeGroup, ConnectingTypeGroup,
DisconnectingType, DisconnectingType,
engineCommandManager, engineCommandManager,
EngineConnectionState, EngineCommandManagerEvents,
EngineConnectionEvents,
EngineConnectionStateType, EngineConnectionStateType,
ErrorType, ErrorType,
initialConnectingTypeGroupState, initialConnectingTypeGroupState,
@ -81,37 +82,35 @@ const overallConnectionStateIcon: Record<
} }
export function useNetworkStatus() { export function useNetworkStatus() {
const [steps, setSteps] = useState(initialConnectingTypeGroupState) const [steps, setSteps] = useState(
structuredClone(initialConnectingTypeGroupState)
)
const [internetConnected, setInternetConnected] = useState<boolean>(true) const [internetConnected, setInternetConnected] = useState<boolean>(true)
const [overallState, setOverallState] = useState<NetworkHealthState>( const [overallState, setOverallState] = useState<NetworkHealthState>(
NetworkHealthState.Ok NetworkHealthState.Disconnected
) )
const [pingPongHealth, setPingPongHealth] = useState<'OK' | 'BAD'>('BAD')
const [hasCopied, setHasCopied] = useState<boolean>(false) const [hasCopied, setHasCopied] = useState<boolean>(false)
const [error, setError] = useState<ErrorType | undefined>(undefined) const [error, setError] = useState<ErrorType | undefined>(undefined)
const issues: Record<ConnectingTypeGroup, boolean> = { const hasIssue = (i: [ConnectingType, boolean | undefined]) =>
[ConnectingTypeGroup.WebSocket]: steps[ConnectingTypeGroup.WebSocket].some( i[1] === undefined ? i[1] : !i[1]
(a: [ConnectingType, boolean | undefined]) => a[1] === false
),
[ConnectingTypeGroup.ICE]: steps[ConnectingTypeGroup.ICE].some(
(a: [ConnectingType, boolean | undefined]) => a[1] === false
),
[ConnectingTypeGroup.WebRTC]: steps[ConnectingTypeGroup.WebRTC].some(
(a: [ConnectingType, boolean | undefined]) => a[1] === false
),
}
const hasIssues: boolean = const [issues, setIssues] = useState<
issues[ConnectingTypeGroup.WebSocket] || Record<ConnectingTypeGroup, boolean | undefined>
issues[ConnectingTypeGroup.ICE] || >({
issues[ConnectingTypeGroup.WebRTC] [ConnectingTypeGroup.WebSocket]: undefined,
[ConnectingTypeGroup.ICE]: undefined,
[ConnectingTypeGroup.WebRTC]: undefined,
})
const [hasIssues, setHasIssues] = useState<boolean | undefined>(undefined)
useEffect(() => { useEffect(() => {
setOverallState( setOverallState(
!internetConnected !internetConnected
? NetworkHealthState.Disconnected ? NetworkHealthState.Disconnected
: hasIssues : hasIssues || hasIssues === undefined
? NetworkHealthState.Issue ? NetworkHealthState.Issue
: NetworkHealthState.Ok : NetworkHealthState.Ok
) )
@ -134,19 +133,59 @@ export function useNetworkStatus() {
}, []) }, [])
useEffect(() => { useEffect(() => {
engineCommandManager.onConnectionStateChange( console.log(pingPongHealth)
(engineConnectionState: EngineConnectionState) => { }, [pingPongHealth])
let hasSetAStep = false
useEffect(() => {
const issues = {
[ConnectingTypeGroup.WebSocket]: steps[
ConnectingTypeGroup.WebSocket
].reduce(
(acc: boolean | undefined, a) =>
acc === true || acc === undefined ? acc : hasIssue(a),
false
),
[ConnectingTypeGroup.ICE]: steps[ConnectingTypeGroup.ICE].reduce(
(acc: boolean | undefined, a) =>
acc === true || acc === undefined ? acc : hasIssue(a),
false
),
[ConnectingTypeGroup.WebRTC]: steps[ConnectingTypeGroup.WebRTC].reduce(
(acc: boolean | undefined, a) =>
acc === true || acc === undefined ? acc : hasIssue(a),
false
),
}
setIssues(issues)
}, [steps])
useEffect(() => {
setHasIssues(
issues[ConnectingTypeGroup.WebSocket] ||
issues[ConnectingTypeGroup.ICE] ||
issues[ConnectingTypeGroup.WebRTC]
)
}, [issues])
useEffect(() => {
const onPingPongChange = ({ detail: state }: CustomEvent) => {
setPingPongHealth(state)
}
const onConnectionStateChange = ({
detail: engineConnectionState,
}: CustomEvent) => {
setSteps((steps) => {
let nextSteps = structuredClone(steps)
if ( if (
engineConnectionState.type === EngineConnectionStateType.Connecting engineConnectionState.type === EngineConnectionStateType.Connecting
) { ) {
const groups = Object.values(steps) const groups = Object.values(nextSteps)
for (let group of groups) { for (let group of groups) {
for (let step of group) { for (let step of group) {
if (step[0] !== engineConnectionState.value.type) continue if (step[0] !== engineConnectionState.value.type) continue
step[1] = true step[1] = true
hasSetAStep = true
} }
} }
} }
@ -154,7 +193,7 @@ export function useNetworkStatus() {
if ( if (
engineConnectionState.type === EngineConnectionStateType.Disconnecting engineConnectionState.type === EngineConnectionStateType.Disconnecting
) { ) {
const groups = Object.values(steps) const groups = Object.values(nextSteps)
for (let group of groups) { for (let group of groups) {
for (let step of group) { for (let step of group) {
if ( if (
@ -165,7 +204,6 @@ export function useNetworkStatus() {
?.type === step[0] ?.type === step[0]
) { ) {
step[1] = false step[1] = false
hasSetAStep = true
} }
} }
} }
@ -176,11 +214,50 @@ export function useNetworkStatus() {
} }
} }
if (hasSetAStep) { // Reset the state of all steps if we have disconnected.
setSteps(steps) if (
engineConnectionState.type === EngineConnectionStateType.Disconnected
) {
return structuredClone(initialConnectingTypeGroupState)
} }
}
return nextSteps
})
}
const onEngineAvailable = ({ detail: engineConnection }: CustomEvent) => {
engineConnection.addEventListener(
EngineConnectionEvents.PingPongChanged,
onPingPongChange as EventListener
)
engineConnection.addEventListener(
EngineConnectionEvents.ConnectionStateChanged,
onConnectionStateChange as EventListener
)
}
engineCommandManager.addEventListener(
EngineCommandManagerEvents.EngineAvailable,
onEngineAvailable as EventListener
) )
return () => {
engineCommandManager.removeEventListener(
EngineCommandManagerEvents.EngineAvailable,
onEngineAvailable as EventListener
)
// When the component is unmounted these should be assigned, but it's possible
// the component mounts and unmounts before engine is available.
engineCommandManager.engineConnection?.addEventListener(
EngineConnectionEvents.PingPongChanged,
onPingPongChange as EventListener
)
engineCommandManager.engineConnection?.addEventListener(
EngineConnectionEvents.ConnectionStateChanged,
onConnectionStateChange as EventListener
)
}
}, []) }, [])
return { return {
@ -192,6 +269,7 @@ export function useNetworkStatus() {
error, error,
setHasCopied, setHasCopied,
hasCopied, hasCopied,
pingPongHealth,
} }
} }
@ -256,18 +334,18 @@ export const NetworkHealthIndicator = () => {
size="lg" size="lg"
icon={ icon={
hasIssueToIcon[ hasIssueToIcon[
issues[name as ConnectingTypeGroup].toString() String(issues[name as ConnectingTypeGroup])
] ]
} }
iconClassName={ iconClassName={
hasIssueToIconColors[ hasIssueToIconColors[
issues[name as ConnectingTypeGroup].toString() String(issues[name as ConnectingTypeGroup])
].icon ].icon
} }
bgClassName={ bgClassName={
'rounded-sm ' + 'rounded-sm ' +
hasIssueToIconColors[ hasIssueToIconColors[
issues[name as ConnectingTypeGroup].toString() String(issues[name as ConnectingTypeGroup])
].bg ].bg
} }
/> />

View File

@ -32,6 +32,7 @@ export const Stream = ({ className = '' }: { className?: string }) => {
const { state } = useModelingContext() const { state } = useModelingContext()
const { isExecuting } = useKclContext() const { isExecuting } = useKclContext()
const { overallState } = useNetworkStatus() const { overallState } = useNetworkStatus()
const isNetworkOkay = overallState === NetworkHealthState.Ok const isNetworkOkay = overallState === NetworkHealthState.Ok
useEffect(() => { useEffect(() => {

View File

@ -1,5 +1,5 @@
import { PathToNode, Program, SourceRange } from 'lang/wasm' import { PathToNode, Program, SourceRange } from 'lang/wasm'
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_CONNECTION_TIMEOUT_MS } from 'env' import { VITE_KC_API_WS_MODELING_URL } from 'env'
import { Models } from '@kittycad/lib' import { Models } from '@kittycad/lib'
import { exportSave } from 'lib/exportSave' import { exportSave } from 'lib/exportSave'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
@ -8,6 +8,9 @@ import { sceneInfra } from 'clientSideScene/sceneInfra'
let lastMessage = '' let lastMessage = ''
// TODO(paultag): This ought to be tweakable.
const pingIntervalMs = 10000
interface CommandInfo { interface CommandInfo {
commandType: CommandTypes commandType: CommandTypes
range: SourceRange range: SourceRange
@ -37,11 +40,6 @@ export interface ArtifactMap {
[key: string]: ResultCommand | PendingCommand | FailedCommand [key: string]: ResultCommand | PendingCommand | FailedCommand
} }
interface NewTrackArgs {
conn: EngineConnection
mediaStream: MediaStream
}
// This looks funny, I know. This is needed because node and the browser // This looks funny, I know. This is needed because node and the browser
// disagree as to the type. In a browser it's a number, but in node it's a // disagree as to the type. In a browser it's a number, but in node it's a
// "Timeout". // "Timeout".
@ -158,10 +156,28 @@ export type EngineConnectionState =
| State<EngineConnectionStateType.Disconnecting, DisconnectingValue> | State<EngineConnectionStateType.Disconnecting, DisconnectingValue>
| State<EngineConnectionStateType.Disconnected, void> | State<EngineConnectionStateType.Disconnected, void>
export type PingPongState = 'OK' | 'BAD'
export enum EngineConnectionEvents {
// Fires for each ping-pong success or failure.
PingPongChanged = 'ping-pong-changed', // (state: PingPongState) => void
// For now, this is only used by the NetworkHealthIndicator.
// We can eventually use it for more, but one step at a time.
ConnectionStateChanged = 'connection-state-changed', // (state: EngineConnectionState) => void
// These are used for the EngineCommandManager and were created
// before onConnectionStateChange existed.
ConnectionStarted = 'connection-started', // (engineConnection: EngineConnection) => void
Opened = 'opened', // (engineConnection: EngineConnection) => void
Closed = 'closed', // (engineConnection: EngineConnection) => void
NewTrack = 'new-track', // (track: NewTrackArgs) => void
}
// EngineConnection encapsulates the connection(s) to the Engine // EngineConnection encapsulates the connection(s) to the Engine
// for the EngineCommandManager; namely, the underlying WebSocket // for the EngineCommandManager; namely, the underlying WebSocket
// and WebRTC connections. // and WebRTC connections.
class EngineConnection { class EngineConnection extends EventTarget {
websocket?: WebSocket websocket?: WebSocket
pc?: RTCPeerConnection pc?: RTCPeerConnection
unreliableDataChannel?: RTCDataChannel unreliableDataChannel?: RTCDataChannel
@ -195,7 +211,12 @@ class EngineConnection {
} }
} }
this._state = next this._state = next
this.onConnectionStateChange(this._state)
this.dispatchEvent(
new CustomEvent(EngineConnectionEvents.ConnectionStateChanged, {
detail: this._state,
})
)
} }
private failedConnTimeout: Timeout | null private failedConnTimeout: Timeout | null
@ -203,74 +224,39 @@ class EngineConnection {
readonly url: string readonly url: string
private readonly token?: string private readonly token?: string
// For now, this is only used by the NetworkHealthIndicator.
// We can eventually use it for more, but one step at a time.
private onConnectionStateChange: (state: EngineConnectionState) => void
// These are used for the EngineCommandManager and were created
// before onConnectionStateChange existed.
private onEngineConnectionOpen: (engineConnection: EngineConnection) => void
private onConnectionStarted: (engineConnection: EngineConnection) => void
private onClose: (engineConnection: EngineConnection) => void
private onNewTrack: (track: NewTrackArgs) => void
// TODO: actual type is ClientMetrics // TODO: actual type is ClientMetrics
private webrtcStatsCollector?: () => Promise<ClientMetrics> private webrtcStatsCollector?: () => Promise<ClientMetrics>
constructor({ private pingPongSpan: { ping?: Date; pong?: Date }
url,
token, constructor({ url, token }: { url: string; token?: string }) {
onConnectionStateChange = () => {}, super()
onNewTrack = () => {},
onEngineConnectionOpen = () => {},
onConnectionStarted = () => {},
onClose = () => {},
}: {
url: string
token?: string
onConnectionStateChange?: (state: EngineConnectionState) => void
onEngineConnectionOpen?: (engineConnection: EngineConnection) => void
onConnectionStarted?: (engineConnection: EngineConnection) => void
onClose?: (engineConnection: EngineConnection) => void
onNewTrack?: (track: NewTrackArgs) => void
}) {
this.url = url this.url = url
this.token = token this.token = token
this.failedConnTimeout = null this.failedConnTimeout = null
this.onConnectionStateChange = onConnectionStateChange
this.onEngineConnectionOpen = onEngineConnectionOpen
this.onConnectionStarted = onConnectionStarted
this.onClose = onClose this.pingPongSpan = { ping: undefined, pong: undefined }
this.onNewTrack = onNewTrack
// TODO(paultag): This ought to be tweakable.
const pingIntervalMs = 10000
// Without an interval ping, our connection will timeout. // Without an interval ping, our connection will timeout.
let pingInterval = setInterval(() => { setInterval(() => {
switch (this.state.type as EngineConnectionStateType) { switch (this.state.type as EngineConnectionStateType) {
case EngineConnectionStateType.ConnectionEstablished: case EngineConnectionStateType.ConnectionEstablished:
this.send({ type: 'ping' }) this.send({ type: 'ping' })
this.pingPongSpan.ping = new Date()
break break
case EngineConnectionStateType.Disconnecting: case EngineConnectionStateType.Disconnecting:
case EngineConnectionStateType.Disconnected: case EngineConnectionStateType.Disconnected:
clearInterval(pingInterval) // Reconnect if we have disconnected.
if (!this.isConnecting()) this.connect()
break break
default: default:
if (this.isConnecting()) break
// Means we never could do an initial connection. Reconnect everything.
if (!this.pingPongSpan.ping) this.connect()
break break
} }
}, pingIntervalMs) }, pingIntervalMs)
const connectionTimeoutMs = VITE_KC_CONNECTION_TIMEOUT_MS
let connectRetryInterval = setInterval(() => {
if (this.state.type !== EngineConnectionStateType.Disconnected) return
// Only try reconnecting when completely disconnected.
clearInterval(connectRetryInterval)
console.log('Trying to reconnect')
this.connect()
}, connectionTimeoutMs)
} }
isConnecting() { isConnecting() {
@ -352,7 +338,11 @@ class EngineConnection {
// dance is it safest to connect the video tracks / stream // dance is it safest to connect the video tracks / stream
case 'connected': case 'connected':
// Let the browser attach to the video stream now // Let the browser attach to the video stream now
this.onNewTrack({ conn: this, mediaStream: this.mediaStream! }) this.dispatchEvent(
new CustomEvent(EngineConnectionEvents.NewTrack, {
detail: { conn: this, mediaStream: this.mediaStream! },
})
)
break break
case 'failed': case 'failed':
this.disconnectAll() this.disconnectAll()
@ -468,7 +458,9 @@ class EngineConnection {
// Everything is now connected. // Everything is now connected.
this.state = { type: EngineConnectionStateType.ConnectionEstablished } this.state = { type: EngineConnectionStateType.ConnectionEstablished }
this.onEngineConnectionOpen(this) this.dispatchEvent(
new CustomEvent(EngineConnectionEvents.Opened, { detail: this })
)
}) })
this.unreliableDataChannel.addEventListener('close', (event) => { this.unreliableDataChannel.addEventListener('close', (event) => {
@ -510,6 +502,10 @@ class EngineConnection {
}, },
} }
// Send an initial ping
this.send({ type: 'ping' })
this.pingPongSpan.ping = new Date()
// This is required for when KCMA is running stand-alone / within Tauri. // This is required for when KCMA is running stand-alone / within Tauri.
// Otherwise when run in a browser, the token is sent implicitly via // Otherwise when run in a browser, the token is sent implicitly via
// the Cookie header. // the Cookie header.
@ -575,12 +571,34 @@ failed cmd type was ${artifactThatFailed?.commandType}`
let resp = message.resp let resp = message.resp
// If there's no body to the response, we can bail here. // If there's no body to the response, we can bail here.
// !resp.type is usually "pong" response for our "ping"
if (!resp || !resp.type) { if (!resp || !resp.type) {
return return
} }
switch (resp.type) { switch (resp.type) {
case 'pong':
this.pingPongSpan.pong = new Date()
if (this.pingPongSpan.ping && this.pingPongSpan.pong) {
if (
Math.abs(
this.pingPongSpan.pong.valueOf() -
this.pingPongSpan.ping.valueOf()
) >= pingIntervalMs
) {
this.dispatchEvent(
new CustomEvent(EngineConnectionEvents.PingPongChanged, {
detail: 'BAD',
})
)
} else {
this.dispatchEvent(
new CustomEvent(EngineConnectionEvents.PingPongChanged, {
detail: 'OK',
})
)
}
}
break
case 'ice_server_info': case 'ice_server_info':
let ice_servers = resp.data?.ice_servers let ice_servers = resp.data?.ice_servers
@ -727,27 +745,11 @@ failed cmd type was ${artifactThatFailed?.commandType}`
} }
}) })
const connectionTimeoutMs = VITE_KC_CONNECTION_TIMEOUT_MS this.dispatchEvent(
if (this.failedConnTimeout) { new CustomEvent(EngineConnectionEvents.ConnectionStarted, {
clearTimeout(this.failedConnTimeout) detail: this,
this.failedConnTimeout = null })
} )
this.failedConnTimeout = setTimeout(() => {
if (this.isReady()) {
return
}
this.failedConnTimeout = null
this.state = {
type: EngineConnectionStateType.Disconnecting,
value: {
type: DisconnectingType.Timeout,
},
}
this.disconnectAll()
this.finalizeIfAllConnectionsClosed()
}, connectionTimeoutMs)
this.onConnectionStarted(this)
} }
unreliableSend(message: object | string) { unreliableSend(message: object | string) {
// TODO(paultag): Add in logic to determine the connection state and // TODO(paultag): Add in logic to determine the connection state and
@ -796,6 +798,8 @@ interface UnreliableSubscription<T extends UnreliableResponses['type']> {
callback: (data: Extract<UnreliableResponses, { type: T }>) => void callback: (data: Extract<UnreliableResponses, { type: T }>) => void
} }
// TODO: Should eventually be replaced with native EventTarget event system,
// as it manages events in a more familiar way to other developers.
export interface Subscription<T extends ModelTypes> { export interface Subscription<T extends ModelTypes> {
event: T event: T
callback: ( callback: (
@ -823,7 +827,11 @@ export type CommandLog =
data: null data: null
} }
export class EngineCommandManager { export enum EngineCommandManagerEvents {
EngineAvailable = 'engine-available',
}
export class EngineCommandManager extends EventTarget {
artifactMap: ArtifactMap = {} artifactMap: ArtifactMap = {}
lastArtifactMap: ArtifactMap = {} lastArtifactMap: ArtifactMap = {}
sceneCommandArtifacts: ArtifactMap = {} sceneCommandArtifacts: ArtifactMap = {}
@ -857,10 +865,9 @@ export class EngineCommandManager {
} }
} = {} as any } = {} as any
callbacksEngineStateConnection: ((state: EngineConnectionState) => void)[] =
[]
constructor() { constructor() {
super()
this.engineConnection = undefined this.engineConnection = undefined
;(async () => { ;(async () => {
// circular dependency needs one to be lazy loaded // circular dependency needs one to be lazy loaded
@ -901,12 +908,17 @@ export class EngineCommandManager {
this.engineConnection = new EngineConnection({ this.engineConnection = new EngineConnection({
url, url,
token, token,
onConnectionStateChange: (state: EngineConnectionState) => { })
for (let cb of this.callbacksEngineStateConnection) {
cb(state) this.dispatchEvent(
} new CustomEvent(EngineCommandManagerEvents.EngineAvailable, {
}, detail: this.engineConnection,
onEngineConnectionOpen: () => { })
)
this.engineConnection.addEventListener(
EngineConnectionEvents.Opened,
() => {
// Make the axis gizmo. // Make the axis gizmo.
// We do this after the connection opened to avoid a race condition. // We do this after the connection opened to avoid a race condition.
// Connected opened is the last thing that happens when the stream // Connected opened is the last thing that happens when the stream
@ -941,78 +953,98 @@ export class EngineCommandManager {
setIsStreamReady(true) setIsStreamReady(true)
executeCode(undefined, true) executeCode(undefined, true)
}) })
}, }
onClose: () => { )
setIsStreamReady(false)
},
onConnectionStarted: (engineConnection) => {
engineConnection?.pc?.addEventListener('datachannel', (event) => {
let unreliableDataChannel = event.channel
unreliableDataChannel.addEventListener('message', (event) => { this.engineConnection.addEventListener(
const result: UnreliableResponses = JSON.parse(event.data) EngineConnectionEvents.Closed,
Object.values( () => {
this.unreliableSubscriptions[result.type] || {} setIsStreamReady(false)
).forEach( }
// TODO: There is only one response that uses the unreliable channel atm, )
// highlight_set_entity, if there are more it's likely they will all have the same
// sequence logic, but I'm not sure if we use a single global sequence or a sequence this.engineConnection.addEventListener(
// per unreliable subscription. EngineConnectionEvents.ConnectionStarted,
(callback) => { (({ detail: engineConnection }: CustomEvent) => {
if ( engineConnection?.pc?.addEventListener(
result?.data?.sequence && 'datachannel',
result?.data.sequence > this.inSequence && (event: RTCDataChannelEvent) => {
result.type === 'highlight_set_entity' let unreliableDataChannel = event.channel
) {
this.inSequence = result.data.sequence unreliableDataChannel.addEventListener(
callback(result) 'message',
} (event: MessageEvent) => {
const result: UnreliableResponses = JSON.parse(event.data)
Object.values(
this.unreliableSubscriptions[result.type] || {}
).forEach(
// TODO: There is only one response that uses the unreliable channel atm,
// highlight_set_entity, if there are more it's likely they will all have the same
// sequence logic, but I'm not sure if we use a single global sequence or a sequence
// per unreliable subscription.
(callback) => {
if (
result?.data?.sequence &&
result?.data.sequence > this.inSequence &&
result.type === 'highlight_set_entity'
) {
this.inSequence = result.data.sequence
callback(result)
}
}
)
} }
) )
}) }
}) )
// When the EngineConnection starts a connection, we want to register // When the EngineConnection starts a connection, we want to register
// callbacks into the WebSocket/PeerConnection. // callbacks into the WebSocket/PeerConnection.
engineConnection.websocket?.addEventListener('message', (event) => { engineConnection.websocket?.addEventListener(
if (event.data instanceof ArrayBuffer) { 'message',
// If the data is an ArrayBuffer, it's the result of an export command, (event: MessageEvent) => {
// because in all other cases we send JSON strings. But in the case of if (event.data instanceof ArrayBuffer) {
// export we send a binary blob. // If the data is an ArrayBuffer, it's the result of an export command,
// Pass this to our export function. // because in all other cases we send JSON strings. But in the case of
void exportSave(event.data) // export we send a binary blob.
} else { // Pass this to our export function.
const message: Models['WebSocketResponse_type'] = JSON.parse( void exportSave(event.data)
event.data } else {
) const message: Models['WebSocketResponse_type'] = JSON.parse(
if ( event.data
message.success && )
message.resp.type === 'modeling' && if (
message.request_id message.success &&
) { message.resp.type === 'modeling' &&
this.handleModelingCommand(message.resp, message.request_id) message.request_id
} else if ( ) {
!message.success && this.handleModelingCommand(message.resp, message.request_id)
message.request_id && } else if (
this.artifactMap[message.request_id] !message.success &&
) { message.request_id &&
this.handleFailedModelingCommand(message) this.artifactMap[message.request_id]
) {
this.handleFailedModelingCommand(message)
}
} }
} }
}) )
}, }) as EventListener
onNewTrack: ({ mediaStream }) => { )
console.log('received track', mediaStream)
mediaStream.getVideoTracks()[0].addEventListener('mute', () => { this.engineConnection.addEventListener(EngineConnectionEvents.NewTrack, (({
console.log('peer is not sending video to us') detail: { mediaStream },
// this.engineConnection?.close() }: CustomEvent) => {
// this.engineConnection?.connect() console.log('received track', mediaStream)
})
setMediaStream(mediaStream) mediaStream.getVideoTracks()[0].addEventListener('mute', () => {
}, console.log('peer is not sending video to us')
}) // this.engineConnection?.close()
// this.engineConnection?.connect()
})
setMediaStream(mediaStream)
}) as EventListener)
this.engineConnection?.connect() this.engineConnection?.connect()
} }
@ -1202,9 +1234,6 @@ export class EngineCommandManager {
) { ) {
delete this.unreliableSubscriptions[event][id] delete this.unreliableSubscriptions[event][id]
} }
onConnectionStateChange(callback: (state: EngineConnectionState) => void) {
this.callbacksEngineStateConnection.push(callback)
}
endSession() { endSession() {
// TODO: instead of sending a single command with `object_ids: Object.keys(this.artifactMap)` // TODO: instead of sending a single command with `object_ids: Object.keys(this.artifactMap)`
// we need to loop over them each individually because if the engine doesn't recognise a single // we need to loop over them each individually because if the engine doesn't recognise a single

View File

@ -149,7 +149,7 @@ export const lineTo: SketchLineHelper = {
pathToNode, pathToNode,
} }
}, },
addTag: addTag(), addTag: addTagWithTo('default'),
} }
export const line: SketchLineHelper = { export const line: SketchLineHelper = {
@ -240,7 +240,7 @@ export const line: SketchLineHelper = {
pathToNode, pathToNode,
} }
}, },
addTag: addTag(), addTag: addTagWithTo('default'),
} }
export const xLineTo: SketchLineHelper = { export const xLineTo: SketchLineHelper = {
@ -288,7 +288,7 @@ export const xLineTo: SketchLineHelper = {
pathToNode, pathToNode,
} }
}, },
addTag: addTag(), addTag: addTagWithTo('default'),
} }
export const yLineTo: SketchLineHelper = { export const yLineTo: SketchLineHelper = {
@ -336,7 +336,7 @@ export const yLineTo: SketchLineHelper = {
pathToNode, pathToNode,
} }
}, },
addTag: addTag(), addTag: addTagWithTo('default'),
} }
export const xLine: SketchLineHelper = { export const xLine: SketchLineHelper = {
@ -386,7 +386,7 @@ export const xLine: SketchLineHelper = {
pathToNode, pathToNode,
} }
}, },
addTag: addTag(), addTag: addTagWithTo('length'),
} }
export const yLine: SketchLineHelper = { export const yLine: SketchLineHelper = {
@ -430,7 +430,7 @@ export const yLine: SketchLineHelper = {
pathToNode, pathToNode,
} }
}, },
addTag: addTag(), addTag: addTagWithTo('length'),
} }
export const tangentialArcTo: SketchLineHelper = { export const tangentialArcTo: SketchLineHelper = {
@ -510,7 +510,7 @@ export const tangentialArcTo: SketchLineHelper = {
} }
}, },
// TODO copy-paste from angledLine // TODO copy-paste from angledLine
addTag: addTag(), addTag: addTagWithTo('angleLength'),
} }
export const angledLine: SketchLineHelper = { export const angledLine: SketchLineHelper = {
add: ({ add: ({
@ -576,7 +576,7 @@ export const angledLine: SketchLineHelper = {
pathToNode, pathToNode,
} }
}, },
addTag: addTag(), addTag: addTagWithTo('angleLength'),
} }
export const angledLineOfXLength: SketchLineHelper = { export const angledLineOfXLength: SketchLineHelper = {
@ -649,7 +649,7 @@ export const angledLineOfXLength: SketchLineHelper = {
pathToNode, pathToNode,
} }
}, },
addTag: addTag(), addTag: addTagWithTo('angleLength'),
} }
export const angledLineOfYLength: SketchLineHelper = { export const angledLineOfYLength: SketchLineHelper = {
@ -723,7 +723,7 @@ export const angledLineOfYLength: SketchLineHelper = {
pathToNode, pathToNode,
} }
}, },
addTag: addTag(), addTag: addTagWithTo('angleLength'),
} }
export const angledLineToX: SketchLineHelper = { export const angledLineToX: SketchLineHelper = {
@ -792,7 +792,7 @@ export const angledLineToX: SketchLineHelper = {
pathToNode, pathToNode,
} }
}, },
addTag: addTag(), addTag: addTagWithTo('angleTo'),
} }
export const angledLineToY: SketchLineHelper = { export const angledLineToY: SketchLineHelper = {
@ -862,7 +862,7 @@ export const angledLineToY: SketchLineHelper = {
pathToNode, pathToNode,
} }
}, },
addTag: addTag(), addTag: addTagWithTo('angleTo'),
} }
export const angledLineThatIntersects: SketchLineHelper = { export const angledLineThatIntersects: SketchLineHelper = {
@ -951,7 +951,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
pathToNode, pathToNode,
} }
}, },
addTag: addTag(), // TODO might be wrong addTag: addTagWithTo('angleTo'), // TODO might be wrong
} }
export const updateStartProfileAtArgs: SketchLineHelper['updateArgs'] = ({ export const updateStartProfileAtArgs: SketchLineHelper['updateArgs'] = ({
@ -1174,31 +1174,29 @@ function isAngleLiteral(lineArugement: Value): boolean {
type addTagFn = (a: ModifyAstBase) => { modifiedAst: Program; tag: string } type addTagFn = (a: ModifyAstBase) => { modifiedAst: Program; tag: string }
function addTag(): addTagFn { function addTagWithTo(
argType: 'angleLength' | 'angleTo' | 'default' | 'length'
): addTagFn {
return ({ node, pathToNode }) => { return ({ node, pathToNode }) => {
let tagName = findUniqueName(node, 'seg', 2)
const _node = { ...node } const _node = { ...node }
const { node: primaryCallExp } = getNodeFromPath<CallExpression>( const { node: callExpression } = getNodeFromPath<CallExpression>(
_node, _node,
pathToNode, pathToNode
'CallExpression'
) )
// Tag is always 3rd expression now, using arg index feels brittle const tagArg = callExpression.arguments?.[2]
// but we can come up with a better way to identify tag later. if (tagArg) {
const thirdArg = primaryCallExp.arguments?.[2]
const tagLiteral =
thirdArg || (createLiteral(findUniqueName(_node, 'seg', 2)) as Literal)
const isTagExisting = !!thirdArg
if (!isTagExisting) {
primaryCallExp.arguments[2] = tagLiteral
}
if ('value' in tagLiteral) {
// Now TypeScript knows tagLiteral has a value property
return { return {
modifiedAst: _node, modifiedAst: _node,
tag: String(tagLiteral.value), tag: String(tagArg),
} }
} else { } else {
throw new Error('Unable to assign tag without value') callExpression.arguments[2] = createLiteral(tagName)
return {
modifiedAst: _node,
tag: tagName,
}
} }
} }
} }

View File

@ -1471,9 +1471,7 @@ dependencies = [
name = "grackle" name = "grackle"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"image",
"kcl-lib", "kcl-lib",
"kcl-macros",
"kittycad", "kittycad",
"kittycad-execution-plan", "kittycad-execution-plan",
"kittycad-execution-plan-macros", "kittycad-execution-plan-macros",
@ -1484,7 +1482,6 @@ dependencies = [
"serde_json", "serde_json",
"thiserror", "thiserror",
"tokio", "tokio",
"twenty-twenty",
"uuid", "uuid",
] ]
@ -2010,7 +2007,7 @@ dependencies = [
[[package]] [[package]]
name = "kittycad-execution-plan" name = "kittycad-execution-plan"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#494225aaac06fab77c4822e7dc48738ecca35392" source = "git+https://github.com/KittyCAD/modeling-api?branch=main#0fa02b1829e16ab67aca2f6153dc29e3fe3a358b"
dependencies = [ dependencies = [
"bytes", "bytes",
"insta", "insta",
@ -2030,7 +2027,7 @@ dependencies = [
[[package]] [[package]]
name = "kittycad-execution-plan-macros" name = "kittycad-execution-plan-macros"
version = "0.1.9" version = "0.1.9"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#494225aaac06fab77c4822e7dc48738ecca35392" source = "git+https://github.com/KittyCAD/modeling-api?branch=main#0fa02b1829e16ab67aca2f6153dc29e3fe3a358b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2040,7 +2037,7 @@ dependencies = [
[[package]] [[package]]
name = "kittycad-execution-plan-traits" name = "kittycad-execution-plan-traits"
version = "0.1.13" version = "0.1.13"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#494225aaac06fab77c4822e7dc48738ecca35392" source = "git+https://github.com/KittyCAD/modeling-api?branch=main#0fa02b1829e16ab67aca2f6153dc29e3fe3a358b"
dependencies = [ dependencies = [
"serde", "serde",
"thiserror", "thiserror",
@ -2050,7 +2047,7 @@ dependencies = [
[[package]] [[package]]
name = "kittycad-modeling-cmds" name = "kittycad-modeling-cmds"
version = "0.1.32" version = "0.1.32"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#494225aaac06fab77c4822e7dc48738ecca35392" source = "git+https://github.com/KittyCAD/modeling-api?branch=main#0fa02b1829e16ab67aca2f6153dc29e3fe3a358b"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@ -2078,7 +2075,7 @@ dependencies = [
[[package]] [[package]]
name = "kittycad-modeling-cmds-macros" name = "kittycad-modeling-cmds-macros"
version = "0.1.2" version = "0.1.2"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#494225aaac06fab77c4822e7dc48738ecca35392" source = "git+https://github.com/KittyCAD/modeling-api?branch=main#0fa02b1829e16ab67aca2f6153dc29e3fe3a358b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2088,12 +2085,11 @@ dependencies = [
[[package]] [[package]]
name = "kittycad-modeling-session" name = "kittycad-modeling-session"
version = "0.1.1" version = "0.1.1"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#494225aaac06fab77c4822e7dc48738ecca35392" source = "git+https://github.com/KittyCAD/modeling-api?branch=main#0fa02b1829e16ab67aca2f6153dc29e3fe3a358b"
dependencies = [ dependencies = [
"futures", "futures",
"kittycad", "kittycad",
"kittycad-modeling-cmds", "kittycad-modeling-cmds",
"lsystem",
"reqwest", "reqwest",
"serde_json", "serde_json",
"thiserror", "thiserror",
@ -2191,12 +2187,6 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "lsystem"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23c47210f2a9f5ae2073e7b847026e3233bcb012aa6845201c69c26481762a81"
[[package]] [[package]]
name = "matchit" name = "matchit"
version = "0.7.3" version = "0.7.3"

View File

@ -6,9 +6,7 @@ description = "A new executor for KCL which compiles to Execution Plans"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
image = { version = "0.24.7", default-features = false, features = ["png"] }
kcl-lib = { path = "../kcl" } kcl-lib = { path = "../kcl" }
kcl-macros = { path = "../kcl-macros/" }
kittycad = { workspace = true } kittycad = { workspace = true }
kittycad-execution-plan = { workspace = true } kittycad-execution-plan = { workspace = true }
kittycad-execution-plan-traits = { workspace = true } kittycad-execution-plan-traits = { workspace = true }
@ -17,7 +15,6 @@ kittycad-modeling-cmds = { workspace = true }
kittycad-modeling-session = { workspace = true } kittycad-modeling-session = { workspace = true }
thiserror = "1.0.57" thiserror = "1.0.57"
tokio = { version = "1.36.0", features = ["macros", "rt"] } tokio = { version = "1.36.0", features = ["macros", "rt"] }
twenty-twenty = "0.7.0"
uuid = "1.7" uuid = "1.7"
[dev-dependencies] [dev-dependencies]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

View File

@ -2,16 +2,13 @@ use std::collections::HashMap;
use kcl_lib::ast::types::{LiteralIdentifier, LiteralValue}; use kcl_lib::ast::types::{LiteralIdentifier, LiteralValue};
use kittycad_execution_plan::constants;
use kittycad_execution_plan_traits::Primitive;
use super::{native_functions, Address}; use super::{native_functions, Address};
use crate::{CompileError, KclFunction}; use crate::{CompileError, KclFunction};
/// KCL values which can be written to KCEP memory. /// KCL values which can be written to KCEP memory.
/// This is recursive. For example, the bound value might be an array, which itself contains bound values. /// This is recursive. For example, the bound value might be an array, which itself contains bound values.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq))] #[cfg_attr(test, derive(Eq, PartialEq))]
pub enum EpBinding { pub enum EpBinding {
/// A KCL value which gets stored in a particular address in KCEP memory. /// A KCL value which gets stored in a particular address in KCEP memory.
Single(Address), Single(Address),
@ -26,8 +23,6 @@ pub enum EpBinding {
properties: HashMap<String, EpBinding>, properties: HashMap<String, EpBinding>,
}, },
/// Not associated with a KCEP address. /// Not associated with a KCEP address.
Constant(Primitive),
/// Not associated with a KCEP address.
Function(KclFunction), Function(KclFunction),
/// SketchGroups have their own storage. /// SketchGroups have their own storage.
SketchGroup { index: usize }, SketchGroup { index: usize },
@ -57,13 +52,11 @@ impl EpBinding {
EpBinding::SketchGroup { .. } => Err(CompileError::CannotIndex), EpBinding::SketchGroup { .. } => Err(CompileError::CannotIndex),
EpBinding::Single(_) => Err(CompileError::CannotIndex), EpBinding::Single(_) => Err(CompileError::CannotIndex),
EpBinding::Function(_) => Err(CompileError::CannotIndex), EpBinding::Function(_) => Err(CompileError::CannotIndex),
EpBinding::Constant(_) => Err(CompileError::CannotIndex),
}, },
// Objects can be indexed by string properties. // Objects can be indexed by string properties.
LiteralValue::String(property) => match self { LiteralValue::String(property) => match self {
EpBinding::Single(_) => Err(CompileError::NoProperties), EpBinding::Single(_) => Err(CompileError::NoProperties),
EpBinding::Function(_) => Err(CompileError::NoProperties), EpBinding::Function(_) => Err(CompileError::NoProperties),
EpBinding::Constant(_) => Err(CompileError::CannotIndex),
EpBinding::SketchGroup { .. } => Err(CompileError::NoProperties), EpBinding::SketchGroup { .. } => Err(CompileError::NoProperties),
EpBinding::Sequence { .. } => Err(CompileError::ArrayDoesNotHaveProperties), EpBinding::Sequence { .. } => Err(CompileError::ArrayDoesNotHaveProperties),
EpBinding::Map { EpBinding::Map {
@ -110,58 +103,8 @@ impl BindingScope {
// TODO: Actually put the stdlib prelude in here, // TODO: Actually put the stdlib prelude in here,
// things like `startSketchAt` and `line`. // things like `startSketchAt` and `line`.
ep_bindings: HashMap::from([ ep_bindings: HashMap::from([
("E".into(), EpBinding::Constant(constants::E)),
("PI".into(), EpBinding::Constant(constants::PI)),
("id".into(), EpBinding::from(KclFunction::Id(native_functions::Id))), ("id".into(), EpBinding::from(KclFunction::Id(native_functions::Id))),
("abs".into(), EpBinding::from(KclFunction::Abs(native_functions::Abs))),
(
"acos".into(),
EpBinding::from(KclFunction::Acos(native_functions::Acos)),
),
(
"asin".into(),
EpBinding::from(KclFunction::Asin(native_functions::Asin)),
),
(
"atan".into(),
EpBinding::from(KclFunction::Atan(native_functions::Atan)),
),
(
"ceil".into(),
EpBinding::from(KclFunction::Ceil(native_functions::Ceil)),
),
("cos".into(), EpBinding::from(KclFunction::Cos(native_functions::Cos))),
(
"floor".into(),
EpBinding::from(KclFunction::Floor(native_functions::Floor)),
),
("ln".into(), EpBinding::from(KclFunction::Ln(native_functions::Ln))),
(
"log10".into(),
EpBinding::from(KclFunction::Log10(native_functions::Log10)),
),
(
"log2".into(),
EpBinding::from(KclFunction::Log2(native_functions::Log2)),
),
("sin".into(), EpBinding::from(KclFunction::Sin(native_functions::Sin))),
(
"sqrt".into(),
EpBinding::from(KclFunction::Sqrt(native_functions::Sqrt)),
),
("tan".into(), EpBinding::from(KclFunction::Tan(native_functions::Tan))),
(
"toDegrees".into(),
EpBinding::from(KclFunction::ToDegrees(native_functions::ToDegrees)),
),
(
"toRadians".into(),
EpBinding::from(KclFunction::ToRadians(native_functions::ToRadians)),
),
("add".into(), EpBinding::from(KclFunction::Add(native_functions::Add))), ("add".into(), EpBinding::from(KclFunction::Add(native_functions::Add))),
("log".into(), EpBinding::from(KclFunction::Log(native_functions::Log))),
("max".into(), EpBinding::from(KclFunction::Max(native_functions::Max))),
("min".into(), EpBinding::from(KclFunction::Min(native_functions::Min))),
( (
"startSketchAt".into(), "startSketchAt".into(),
EpBinding::from(KclFunction::StartSketchAt(native_functions::sketch::StartSketchAt)), EpBinding::from(KclFunction::StartSketchAt(native_functions::sketch::StartSketchAt)),
@ -170,26 +113,6 @@ impl BindingScope {
"lineTo".into(), "lineTo".into(),
EpBinding::from(KclFunction::LineTo(native_functions::sketch::LineTo)), EpBinding::from(KclFunction::LineTo(native_functions::sketch::LineTo)),
), ),
(
"line".into(),
EpBinding::from(KclFunction::Line(native_functions::sketch::Line)),
),
(
"xLineTo".into(),
EpBinding::from(KclFunction::XLineTo(native_functions::sketch::XLineTo)),
),
(
"xLine".into(),
EpBinding::from(KclFunction::XLine(native_functions::sketch::XLine)),
),
(
"yLineTo".into(),
EpBinding::from(KclFunction::YLineTo(native_functions::sketch::YLineTo)),
),
(
"yLine".into(),
EpBinding::from(KclFunction::YLine(native_functions::sketch::YLine)),
),
( (
"extrude".into(), "extrude".into(),
EpBinding::from(KclFunction::Extrude(native_functions::sketch::Extrude)), EpBinding::from(KclFunction::Extrude(native_functions::sketch::Extrude)),

View File

@ -61,7 +61,7 @@ pub enum Error {
#[error("Failed on instruction {instruction_index}:\n{error}\n\nInstruction contents were {instruction:#?}")] #[error("Failed on instruction {instruction_index}:\n{error}\n\nInstruction contents were {instruction:#?}")]
Execution { Execution {
error: ExecutionError, error: ExecutionError,
instruction: Instruction, instruction: Option<Instruction>,
instruction_index: usize, instruction_index: usize,
}, },
} }
@ -76,7 +76,7 @@ impl From<ExecutionFailed> for Error {
) -> Self { ) -> Self {
Self::Execution { Self::Execution {
error, error,
instruction: instruction.expect("no instruction"), instruction,
instruction_index, instruction_index,
} }
} }

View File

@ -11,8 +11,6 @@ use kcl_lib::{
ast, ast,
ast::types::{BodyItem, FunctionExpressionParts, KclNone, LiteralValue, Program}, ast::types::{BodyItem, FunctionExpressionParts, KclNone, LiteralValue, Program},
}; };
extern crate alloc;
use kcl_macros::parse_file;
use kcl_value_group::into_single_value; use kcl_value_group::into_single_value;
use kittycad_execution_plan::{self as ep, Destination, Instruction}; use kittycad_execution_plan::{self as ep, Destination, Instruction};
use kittycad_execution_plan_traits as ept; use kittycad_execution_plan_traits as ept;
@ -26,11 +24,9 @@ use self::{
}; };
/// Execute a KCL program by compiling into an execution plan, then running that. /// Execute a KCL program by compiling into an execution plan, then running that.
/// Include a `prelude.kcl` inlined as a `Program` struct thanks to a proc_macro `parse!`. pub async fn execute(ast: Program, session: &mut Option<Session>) -> Result<ep::Memory, Error> {
/// This makes additional functions available to the user.
pub async fn execute(ast_user: Program, session: &mut Option<Session>) -> Result<ep::Memory, Error> {
let mut planner = Planner::new(); let mut planner = Planner::new();
let (plan, _retval) = planner.build_plan(ast_user)?; let (plan, _retval) = planner.build_plan(ast)?;
let mut mem = ep::Memory::default(); let mut mem = ep::Memory::default();
ep::execute(&mut mem, plan, session).await?; ep::execute(&mut mem, plan, session).await?;
Ok(mem) Ok(mem)
@ -58,9 +54,7 @@ impl Planner {
/// If successful, return the KCEP instructions for executing the given program. /// If successful, return the KCEP instructions for executing the given program.
/// If the program is a function with a return, then it also returns the KCL function's return value. /// If the program is a function with a return, then it also returns the KCL function's return value.
fn build_plan(&mut self, ast_user: Program) -> Result<(Vec<Instruction>, Option<EpBinding>), CompileError> { fn build_plan(&mut self, program: Program) -> Result<(Vec<Instruction>, Option<EpBinding>), CompileError> {
let ast_prelude = parse_file!("./prelude.kcl");
let program = ast_prelude.merge(ast_user);
program program
.body .body
.into_iter() .into_iter()
@ -265,32 +259,9 @@ impl Planner {
binding, binding,
} = match callee { } = match callee {
KclFunction::Id(f) => f.call(&mut ctx, args)?, KclFunction::Id(f) => f.call(&mut ctx, args)?,
KclFunction::Abs(f) => f.call(&mut ctx, args)?,
KclFunction::Acos(f) => f.call(&mut ctx, args)?,
KclFunction::Asin(f) => f.call(&mut ctx, args)?,
KclFunction::Atan(f) => f.call(&mut ctx, args)?,
KclFunction::Ceil(f) => f.call(&mut ctx, args)?,
KclFunction::Cos(f) => f.call(&mut ctx, args)?,
KclFunction::Floor(f) => f.call(&mut ctx, args)?,
KclFunction::Ln(f) => f.call(&mut ctx, args)?,
KclFunction::Log10(f) => f.call(&mut ctx, args)?,
KclFunction::Log2(f) => f.call(&mut ctx, args)?,
KclFunction::Sin(f) => f.call(&mut ctx, args)?,
KclFunction::Sqrt(f) => f.call(&mut ctx, args)?,
KclFunction::Tan(f) => f.call(&mut ctx, args)?,
KclFunction::ToDegrees(f) => f.call(&mut ctx, args)?,
KclFunction::ToRadians(f) => f.call(&mut ctx, args)?,
KclFunction::Log(f) => f.call(&mut ctx, args)?,
KclFunction::Max(f) => f.call(&mut ctx, args)?,
KclFunction::Min(f) => f.call(&mut ctx, args)?,
KclFunction::StartSketchAt(f) => f.call(&mut ctx, args)?, KclFunction::StartSketchAt(f) => f.call(&mut ctx, args)?,
KclFunction::Extrude(f) => f.call(&mut ctx, args)?, KclFunction::Extrude(f) => f.call(&mut ctx, args)?,
KclFunction::LineTo(f) => f.call(&mut ctx, args)?, KclFunction::LineTo(f) => f.call(&mut ctx, args)?,
KclFunction::Line(f) => f.call(&mut ctx, args)?,
KclFunction::XLineTo(f) => f.call(&mut ctx, args)?,
KclFunction::XLine(f) => f.call(&mut ctx, args)?,
KclFunction::YLineTo(f) => f.call(&mut ctx, args)?,
KclFunction::YLine(f) => f.call(&mut ctx, args)?,
KclFunction::Add(f) => f.call(&mut ctx, args)?, KclFunction::Add(f) => f.call(&mut ctx, args)?,
KclFunction::Close(f) => f.call(&mut ctx, args)?, KclFunction::Close(f) => f.call(&mut ctx, args)?,
KclFunction::UserDefined(f) => { KclFunction::UserDefined(f) => {
@ -658,32 +629,9 @@ impl Eq for UserDefinedFunction {}
#[cfg_attr(test, derive(Eq, PartialEq))] #[cfg_attr(test, derive(Eq, PartialEq))]
enum KclFunction { enum KclFunction {
Id(native_functions::Id), Id(native_functions::Id),
Abs(native_functions::Abs),
Acos(native_functions::Acos),
Asin(native_functions::Asin),
Atan(native_functions::Atan),
Ceil(native_functions::Ceil),
Cos(native_functions::Cos),
Floor(native_functions::Floor),
Ln(native_functions::Ln),
Log10(native_functions::Log10),
Log2(native_functions::Log2),
Sin(native_functions::Sin),
Sqrt(native_functions::Sqrt),
Tan(native_functions::Tan),
ToDegrees(native_functions::ToDegrees),
ToRadians(native_functions::ToRadians),
StartSketchAt(native_functions::sketch::StartSketchAt), StartSketchAt(native_functions::sketch::StartSketchAt),
LineTo(native_functions::sketch::LineTo), LineTo(native_functions::sketch::LineTo),
Line(native_functions::sketch::Line),
XLineTo(native_functions::sketch::XLineTo),
XLine(native_functions::sketch::XLine),
YLineTo(native_functions::sketch::YLineTo),
YLine(native_functions::sketch::YLine),
Add(native_functions::Add), Add(native_functions::Add),
Log(native_functions::Log),
Max(native_functions::Max),
Min(native_functions::Min),
UserDefined(UserDefinedFunction), UserDefined(UserDefinedFunction),
Extrude(native_functions::sketch::Extrude), Extrude(native_functions::sketch::Extrude),
Close(native_functions::sketch::Close), Close(native_functions::sketch::Close),

View File

@ -2,15 +2,18 @@
//! This includes some of the stdlib, e.g. `startSketchAt`. //! This includes some of the stdlib, e.g. `startSketchAt`.
//! But some other stdlib functions will be written in KCL. //! But some other stdlib functions will be written in KCL.
use kittycad_execution_plan::{ use kittycad_execution_plan::{BinaryArithmetic, Destination, Instruction};
BinaryArithmetic, BinaryOperation, Destination, Instruction, Operand, UnaryArithmetic, UnaryOperation,
};
use kittycad_execution_plan_traits::Address; use kittycad_execution_plan_traits::Address;
use crate::{CompileError, EpBinding, EvalPlan}; use crate::{CompileError, EpBinding, EvalPlan};
pub mod sketch; pub mod sketch;
/// The identity function. Always returns its first input.
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct Id;
pub trait Callable { pub trait Callable {
fn call(&self, ctx: &mut Context<'_>, args: Vec<EpBinding>) -> Result<EvalPlan, CompileError>; fn call(&self, ctx: &mut Context<'_>, args: Vec<EpBinding>) -> Result<EvalPlan, CompileError>;
} }
@ -29,65 +32,6 @@ impl<'a> Context<'a> {
} }
} }
/// Unary operator macro to quickly create new bindings.
macro_rules! define_unary {
() => {};
($fn_name:ident$( $rest:ident)*) => {
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct $fn_name;
impl Callable for $fn_name {
fn call(&self, ctx: &mut Context<'_>, mut args: Vec<EpBinding>) -> Result<EvalPlan, CompileError> {
if args.len() > 1 {
return Err(CompileError::TooManyArgs {
fn_name: "$fn_name".into(),
maximum: 1,
actual: args.len(),
});
}
let not_enough_args = CompileError::NotEnoughArgs {
fn_name: "$fn_name".into(),
required: 1,
actual: args.len(),
};
let EpBinding::Single(arg0) = args.pop().ok_or(not_enough_args.clone())? else {
return Err(CompileError::InvalidOperand("A single value binding is expected"));
};
let destination = ctx.next_address.offset_by(1);
let instructions = vec![
Instruction::UnaryArithmetic {
arithmetic: UnaryArithmetic {
operation: UnaryOperation::$fn_name,
operand: Operand::Reference(arg0)
},
destination: Destination::Address(destination)
}
];
Ok(EvalPlan {
instructions,
binding: EpBinding::Single(destination),
})
}
}
define_unary!($($rest)*);
};
}
define_unary!(Abs Acos Asin Atan Ceil Cos Floor Ln Log10 Log2 Sin Sqrt Tan ToDegrees ToRadians);
/// The identity function. Always returns its first input.
/// Implemented purely on the KCL side so it doesn't need to be in the
/// define_unary! macro above.
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct Id;
impl Callable for Id { impl Callable for Id {
fn call(&self, _: &mut Context<'_>, args: Vec<EpBinding>) -> Result<EvalPlan, CompileError> { fn call(&self, _: &mut Context<'_>, args: Vec<EpBinding>) -> Result<EvalPlan, CompileError> {
if args.len() > 1 { if args.len() > 1 {
@ -112,53 +56,44 @@ impl Callable for Id {
} }
} }
/// Binary operator macro to quickly create new bindings. /// A test function that adds two numbers.
macro_rules! define_binary { #[derive(Debug, Clone)]
() => {}; #[cfg_attr(test, derive(Eq, PartialEq))]
($fn_name:ident$( $rest:ident)*) => { pub struct Add;
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct $fn_name;
impl Callable for $fn_name { impl Callable for Add {
fn call(&self, ctx: &mut Context<'_>, mut args: Vec<EpBinding>) -> Result<EvalPlan, CompileError> { fn call(&self, ctx: &mut Context<'_>, mut args: Vec<EpBinding>) -> Result<EvalPlan, CompileError> {
let len = args.len(); let len = args.len();
if len > 2 { if len > 2 {
return Err(CompileError::TooManyArgs { return Err(CompileError::TooManyArgs {
fn_name: "$fn_name".into(), fn_name: "add".into(),
maximum: 2, maximum: 2,
actual: len, actual: len,
}); });
}
let not_enough_args = CompileError::NotEnoughArgs {
fn_name: "$fn_name".into(),
required: 2,
actual: len,
};
const ERR: &str = "cannot use composite values (e.g. array) as arguments to $fn_name";
let EpBinding::Single(arg1) = args.pop().ok_or(not_enough_args.clone())? else {
return Err(CompileError::InvalidOperand(ERR));
};
let EpBinding::Single(arg0) = args.pop().ok_or(not_enough_args)? else {
return Err(CompileError::InvalidOperand(ERR));
};
let destination = ctx.next_address.offset_by(1);
Ok(EvalPlan {
instructions: vec![Instruction::BinaryArithmetic {
arithmetic: BinaryArithmetic {
operation: BinaryOperation::$fn_name,
operand0: Operand::Reference(arg0),
operand1: Operand::Reference(arg1),
},
destination: Destination::Address(destination),
}],
binding: EpBinding::Single(destination),
})
} }
let not_enough_args = CompileError::NotEnoughArgs {
fn_name: "add".into(),
required: 2,
actual: len,
};
const ERR: &str = "cannot use composite values (e.g. array) as arguments to Add";
let EpBinding::Single(arg1) = args.pop().ok_or(not_enough_args.clone())? else {
return Err(CompileError::InvalidOperand(ERR));
};
let EpBinding::Single(arg0) = args.pop().ok_or(not_enough_args)? else {
return Err(CompileError::InvalidOperand(ERR));
};
let destination = ctx.next_address.offset_by(1);
Ok(EvalPlan {
instructions: vec![Instruction::BinaryArithmetic {
arithmetic: BinaryArithmetic {
operation: kittycad_execution_plan::BinaryOperation::Add,
operand0: kittycad_execution_plan::Operand::Reference(arg0),
operand1: kittycad_execution_plan::Operand::Reference(arg1),
},
destination: Destination::Address(destination),
}],
binding: EpBinding::Single(destination),
})
} }
define_binary!($($rest)*);
};
} }
define_binary!(Add Log Max Min);

View File

@ -3,4 +3,4 @@
pub mod helpers; pub mod helpers;
pub mod stdlib_functions; pub mod stdlib_functions;
pub use stdlib_functions::{Close, Extrude, Line, LineTo, StartSketchAt, XLine, XLineTo, YLine, YLineTo}; pub use stdlib_functions::{Close, Extrude, LineTo, StartSketchAt};

View File

@ -67,12 +67,6 @@ pub fn sg_binding(
actual: "function".to_owned(), actual: "function".to_owned(),
arg_number, arg_number,
}), }),
EpBinding::Constant(_) => Err(CompileError::ArgWrongType {
fn_name,
expected,
actual: "constant".to_owned(),
arg_number,
}),
} }
} }
pub fn single_binding( pub fn single_binding(
@ -107,12 +101,6 @@ pub fn single_binding(
actual: "function".to_owned(), actual: "function".to_owned(),
arg_number, arg_number,
}), }),
EpBinding::Constant(_) => Err(CompileError::ArgWrongType {
fn_name,
expected,
actual: "constant".to_owned(),
arg_number,
}),
} }
} }
@ -148,16 +136,10 @@ pub fn sequence_binding(
actual: "function".to_owned(), actual: "function".to_owned(),
arg_number, arg_number,
}), }),
EpBinding::Constant(_) => Err(CompileError::ArgWrongType {
fn_name,
expected,
actual: "constant".to_owned(),
arg_number,
}),
} }
} }
/// Extract a 2D point from an argument to a KCL Function. /// Extract a 2D point from an argument to a Cabble.
pub fn arg_point2d( pub fn arg_point2d(
arg: EpBinding, arg: EpBinding,
fn_name: &'static str, fn_name: &'static str,
@ -166,7 +148,7 @@ pub fn arg_point2d(
arg_number: usize, arg_number: usize,
) -> Result<Address, CompileError> { ) -> Result<Address, CompileError> {
let expected = "2D point (array with length 2)"; let expected = "2D point (array with length 2)";
let elements = sequence_binding(arg, fn_name, "an array of length 2", arg_number)?; let elements = sequence_binding(arg, "startSketchAt", "an array of length 2", arg_number)?;
if elements.len() != 2 { if elements.len() != 2 {
return Err(CompileError::ArgWrongType { return Err(CompileError::ArgWrongType {
fn_name, fn_name,
@ -183,12 +165,12 @@ pub fn arg_point2d(
let start_z = start + 2; let start_z = start + 2;
instructions.extend([ instructions.extend([
Instruction::Copy { Instruction::Copy {
source: single_binding(elements[0].clone(), fn_name, "number", arg_number)?, source: single_binding(elements[0].clone(), "startSketchAt", "number", arg_number)?,
destination: Destination::Address(start_x), destination: Destination::Address(start_x),
length: 1, length: 1,
}, },
Instruction::Copy { Instruction::Copy {
source: single_binding(elements[1].clone(), fn_name, "number", arg_number)?, source: single_binding(elements[1].clone(), "startSketchAt", "number", arg_number)?,
destination: Destination::Address(start_y), destination: Destination::Address(start_y),
length: 1, length: 1,
}, },

View File

@ -1,7 +1,7 @@
use kittycad_execution_plan::{ use kittycad_execution_plan::{
api_request::ApiRequest, api_request::ApiRequest,
sketch_types::{self, Axes, BasePath, Plane, SketchGroup}, sketch_types::{self, Axes, BasePath, Plane, SketchGroup},
BinaryArithmetic, BinaryOperation, Destination, Instruction, Operand, Destination, Instruction,
}; };
use kittycad_execution_plan_traits::{Address, InMemory, Primitive, Value}; use kittycad_execution_plan_traits::{Address, InMemory, Primitive, Value};
use kittycad_modeling_cmds::{ use kittycad_modeling_cmds::{
@ -13,22 +13,6 @@ use uuid::Uuid;
use super::helpers::{arg_point2d, no_arg_api_call, sg_binding, single_binding, stack_api_call}; use super::helpers::{arg_point2d, no_arg_api_call, sg_binding, single_binding, stack_api_call};
use crate::{binding_scope::EpBinding, error::CompileError, native_functions::Callable, EvalPlan}; use crate::{binding_scope::EpBinding, error::CompileError, native_functions::Callable, EvalPlan};
#[derive(PartialEq)]
pub enum At {
RelativeXY,
AbsoluteXY,
RelativeX,
AbsoluteX,
RelativeY,
AbsoluteY,
}
impl At {
pub fn is_relative(&self) -> bool {
*self == At::RelativeX || *self == At::RelativeY || *self == At::RelativeXY
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))] #[cfg_attr(test, derive(Eq, PartialEq))]
pub struct Close; pub struct Close;
@ -156,124 +140,25 @@ impl Callable for LineTo {
&self, &self,
ctx: &mut crate::native_functions::Context<'_>, ctx: &mut crate::native_functions::Context<'_>,
args: Vec<EpBinding>, args: Vec<EpBinding>,
) -> Result<EvalPlan, CompileError> {
LineBare::call(ctx, "lineTo", args, LineBareOptions { at: At::AbsoluteXY })
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct Line;
impl Callable for Line {
fn call(
&self,
ctx: &mut crate::native_functions::Context<'_>,
args: Vec<EpBinding>,
) -> Result<EvalPlan, CompileError> {
LineBare::call(ctx, "line", args, LineBareOptions { at: At::RelativeXY })
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct XLineTo;
impl Callable for XLineTo {
fn call(
&self,
ctx: &mut crate::native_functions::Context<'_>,
args: Vec<EpBinding>,
) -> Result<EvalPlan, CompileError> {
LineBare::call(ctx, "xLineTo", args, LineBareOptions { at: At::AbsoluteX })
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct XLine;
impl Callable for XLine {
fn call(
&self,
ctx: &mut crate::native_functions::Context<'_>,
args: Vec<EpBinding>,
) -> Result<EvalPlan, CompileError> {
LineBare::call(ctx, "xLine", args, LineBareOptions { at: At::RelativeX })
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct YLineTo;
impl Callable for YLineTo {
fn call(
&self,
ctx: &mut crate::native_functions::Context<'_>,
args: Vec<EpBinding>,
) -> Result<EvalPlan, CompileError> {
LineBare::call(ctx, "yLineTo", args, LineBareOptions { at: At::AbsoluteY })
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct YLine;
impl Callable for YLine {
fn call(
&self,
ctx: &mut crate::native_functions::Context<'_>,
args: Vec<EpBinding>,
) -> Result<EvalPlan, CompileError> {
LineBare::call(ctx, "yLine", args, LineBareOptions { at: At::RelativeY })
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
/// Exposes all the possible arguments the `line` modeling command can take.
/// Reduces code for the other line functions needed.
/// We do not expose this to the developer since it does not align with
/// the documentation (there is no "lineBare").
pub struct LineBare;
/// Used to configure the call to handle different line variants.
pub struct LineBareOptions {
/// Where to start coordinates at, ex: At::RelativeXY.
at: At,
}
impl LineBare {
fn call(
ctx: &mut crate::native_functions::Context<'_>,
fn_name: &'static str,
args: Vec<EpBinding>,
opts: LineBareOptions,
) -> Result<EvalPlan, CompileError> { ) -> Result<EvalPlan, CompileError> {
let mut instructions = Vec::new(); let mut instructions = Vec::new();
let fn_name = "lineTo";
let required = 2; // Get both required params.
let mut args_iter = args.into_iter(); let mut args_iter = args.into_iter();
let Some(to) = args_iter.next() else { let Some(to) = args_iter.next() else {
return Err(CompileError::NotEnoughArgs { return Err(CompileError::NotEnoughArgs {
fn_name: fn_name.into(), fn_name: fn_name.into(),
required, required: 2,
actual: args_iter.count(), actual: 0,
}); });
}; };
let Some(sketch_group) = args_iter.next() else { let Some(sketch_group) = args_iter.next() else {
return Err(CompileError::NotEnoughArgs { return Err(CompileError::NotEnoughArgs {
fn_name: fn_name.into(), fn_name: fn_name.into(),
required, required: 2,
actual: args_iter.count(), actual: 1,
}); });
}; };
let tag = match args_iter.next() { let tag = match args_iter.next() {
Some(a) => a, Some(a) => a,
None => { None => {
@ -286,90 +171,26 @@ impl LineBare {
EpBinding::Single(empty_string_addr) EpBinding::Single(empty_string_addr)
} }
}; };
// Check the type of required params. // Check the type of required params.
// We don't check `to` here because it can take on either a let to = arg_point2d(to, fn_name, &mut instructions, ctx, 0)?;
// EpBinding::Sequence or EpBinding::Single.
let sg = sg_binding(sketch_group, fn_name, "sketch group", 1)?; let sg = sg_binding(sketch_group, fn_name, "sketch group", 1)?;
let tag = single_binding(tag, fn_name, "string tag", 2)?; let tag = single_binding(tag, fn_name, "string tag", 2)?;
let id = Uuid::new_v4(); let id = Uuid::new_v4();
// Start of the path segment (which is a straight line). // Start of the path segment (which is a straight line).
let length_of_3d_point = Point3d::<f64>::default().into_parts().len(); let length_of_3d_point = Point3d::<f64>::default().into_parts().len();
let start_of_line = ctx.next_address.offset_by(1); let start_of_line = ctx.next_address.offset_by(1);
// Reserve space for the line's end, and the `relative: bool` field. // Reserve space for the line's end, and the `relative: bool` field.
ctx.next_address.offset_by(length_of_3d_point + 1); ctx.next_address.offset_by(length_of_3d_point + 1);
let new_sg_index = ctx.assign_sketch_group(); let new_sg_index = ctx.assign_sketch_group();
// Copy based on the options.
match opts {
LineBareOptions { at: At::AbsoluteXY, .. } | LineBareOptions { at: At::RelativeXY, .. } => {
// Push the `to` 2D point onto the stack.
let EpBinding::Sequence { elements, length_at: _ } = to.clone() else {
return Err(CompileError::InvalidOperand("Must pass a list of length 2"));
};
let &[EpBinding::Single(el0), EpBinding::Single(el1)] = elements.as_slice() else {
return Err(CompileError::InvalidOperand("Must pass a sequence here."));
};
instructions.extend([
Instruction::Copy {
// X
source: el0,
length: 1,
destination: Destination::StackPush,
},
Instruction::Copy {
// Y
source: el1,
length: 1,
destination: Destination::StackExtend,
},
Instruction::StackExtend { data: vec![0.0.into()] }, // Z
]);
}
LineBareOptions { at: At::AbsoluteX, .. } | LineBareOptions { at: At::RelativeX, .. } => {
let EpBinding::Single(addr) = to else {
return Err(CompileError::InvalidOperand("Must pass a single value here."));
};
instructions.extend([
Instruction::Copy {
// X
source: addr,
length: 1,
destination: Destination::StackPush,
},
Instruction::StackExtend {
data: vec![Primitive::from(0.0)],
}, // Y
Instruction::StackExtend {
data: vec![Primitive::from(0.0)],
}, // Z
]);
}
LineBareOptions { at: At::AbsoluteY, .. } | LineBareOptions { at: At::RelativeY, .. } => {
let EpBinding::Single(addr) = to else {
return Err(CompileError::InvalidOperand("Must pass a single value here."));
};
instructions.extend([
Instruction::StackPush {
data: vec![Primitive::from(0.0)],
}, // X
Instruction::Copy {
// Y
source: addr,
length: 1,
destination: Destination::StackExtend,
},
Instruction::StackExtend {
data: vec![Primitive::from(0.0)],
}, // Z
]);
}
}
instructions.extend([ instructions.extend([
// Push the `to` 2D point onto the stack.
Instruction::Copy {
source: to,
length: 2,
destination: Destination::StackPush,
},
// Make it a 3D point.
Instruction::StackExtend { data: vec![0.0.into()] },
// Append the new path segment to memory. // Append the new path segment to memory.
// First comes its tag. // First comes its tag.
Instruction::SetPrimitive { Instruction::SetPrimitive {
@ -383,7 +204,7 @@ impl LineBare {
// Then its `relative` field. // Then its `relative` field.
Instruction::SetPrimitive { Instruction::SetPrimitive {
address: start_of_line + 1 + length_of_3d_point, address: start_of_line + 1 + length_of_3d_point,
value: opts.at.is_relative().into(), value: false.into(),
}, },
// Push the path ID onto the stack. // Push the path ID onto the stack.
Instruction::SketchGroupCopyFrom { Instruction::SketchGroupCopyFrom {
@ -410,159 +231,16 @@ impl LineBare {
data: vec![Primitive::from("ToPoint".to_owned())], data: vec![Primitive::from("ToPoint".to_owned())],
}, },
// `BasePath::from` point. // `BasePath::from` point.
// Place them in the secondary stack to prepare ToPoint structure.
Instruction::SketchGroupGetLastPoint { Instruction::SketchGroupGetLastPoint {
source: sg, source: sg,
destination: Destination::StackExtend, destination: Destination::StackExtend,
}, },
]); // `BasePath::to` point.
Instruction::Copy {
// Reserve space for the segment last point source: start_of_line + 1,
let to_point_from = ctx.next_address.offset_by(2); length: 2,
destination: Destination::StackExtend,
instructions.extend([
// Copy to the primary stack as well to be worked with.
Instruction::SketchGroupGetLastPoint {
source: sg,
destination: Destination::Address(to_point_from),
}, },
]);
// `BasePath::to` point.
// The copy here depends on the incoming `to` data.
// Sometimes it's a list, sometimes it's single datum.
// And the relative/not relative matters. When relative, we need to
// copy coords from `from` into the new `to` coord that don't change.
// At least everything else can be built up from these "primitives".
if let EpBinding::Sequence { elements, length_at: _ } = to.clone() {
if let &[EpBinding::Single(el0), EpBinding::Single(el1)] = elements.as_slice() {
match opts {
// ToPoint { from: { x1, y1 }, to: { x1 + x2, y1 + y2 } }
LineBareOptions { at: At::RelativeXY, .. } => {
instructions.extend([
Instruction::BinaryArithmetic {
arithmetic: BinaryArithmetic {
operation: BinaryOperation::Add,
operand0: Operand::Reference(to_point_from + 0),
operand1: Operand::Reference(el0),
},
destination: Destination::StackExtend,
},
Instruction::BinaryArithmetic {
arithmetic: BinaryArithmetic {
operation: BinaryOperation::Add,
operand0: Operand::Reference(to_point_from + 1),
operand1: Operand::Reference(el1),
},
destination: Destination::StackExtend,
},
]);
}
// ToPoint { from: { x1, y1 }, to: { x2, y2 } }
LineBareOptions { at: At::AbsoluteXY, .. } => {
// Otherwise just directly copy the new points.
instructions.extend([
Instruction::Copy {
source: el0,
length: 1,
destination: Destination::StackExtend,
},
Instruction::Copy {
source: el1,
length: 1,
destination: Destination::StackExtend,
},
]);
}
_ => {
return Err(CompileError::InvalidOperand(
"A Sequence with At::...X or At::...Y is not valid here. Must be At::...XY.",
));
}
}
}
} else if let EpBinding::Single(addr) = to {
match opts {
// ToPoint { from: { x1, y1 }, to: { x1 + x2, y1 } }
LineBareOptions { at: At::RelativeX } => {
instructions.extend([
Instruction::BinaryArithmetic {
arithmetic: BinaryArithmetic {
operation: BinaryOperation::Add,
operand0: Operand::Reference(to_point_from + 0),
operand1: Operand::Reference(addr),
},
destination: Destination::StackExtend,
},
Instruction::Copy {
source: to_point_from + 1,
length: 1,
destination: Destination::StackExtend,
},
]);
}
// ToPoint { from: { x1, y1 }, to: { x2, y1 } }
LineBareOptions { at: At::AbsoluteX } => {
instructions.extend([
Instruction::Copy {
source: addr,
length: 1,
destination: Destination::StackExtend,
},
Instruction::Copy {
source: to_point_from + 1,
length: 1,
destination: Destination::StackExtend,
},
]);
}
// ToPoint { from: { x1, y1 }, to: { x1, y1 + y2 } }
LineBareOptions { at: At::RelativeY } => {
instructions.extend([
Instruction::Copy {
source: to_point_from + 0,
length: 1,
destination: Destination::StackExtend,
},
Instruction::BinaryArithmetic {
arithmetic: BinaryArithmetic {
operation: BinaryOperation::Add,
operand0: Operand::Reference(to_point_from + 1),
operand1: Operand::Reference(addr),
},
destination: Destination::StackExtend,
},
]);
}
// ToPoint { from: { x1, y1 }, to: { x1, y2 } }
LineBareOptions { at: At::AbsoluteY } => {
instructions.extend([
Instruction::Copy {
source: to_point_from + 0,
length: 1,
destination: Destination::StackExtend,
},
Instruction::Copy {
source: addr,
length: 1,
destination: Destination::StackExtend,
},
]);
}
_ => {
return Err(CompileError::InvalidOperand(
"A Single binding with At::...XY is not valid here.",
));
}
}
} else {
return Err(CompileError::InvalidOperand(
"Must be a sequence or single value binding.",
));
}
instructions.extend([
// `BasePath::name` string. // `BasePath::name` string.
Instruction::Copy { Instruction::Copy {
source: tag, source: tag,

View File

@ -1,7 +1,7 @@
use std::{collections::HashMap, env}; use std::{collections::HashMap, env};
use ep::{constants, sketch_types, Destination, UnaryArithmetic}; use ep::{sketch_types, Destination, UnaryArithmetic};
use ept::{ListHeader, ObjectHeader, Primitive}; use ept::{ListHeader, ObjectHeader};
use kittycad_modeling_cmds::shared::Point2d; use kittycad_modeling_cmds::shared::Point2d;
use kittycad_modeling_session::SessionBuilder; use kittycad_modeling_session::SessionBuilder;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
@ -1048,10 +1048,42 @@ fn store_object_with_array_property() {
/// Write the program's plan to the KCVM debugger's normal input file. /// Write the program's plan to the KCVM debugger's normal input file.
#[allow(unused)] #[allow(unused)]
fn kcvm_dbg(kcl_program: &str, path: &str) { fn kcvm_dbg(kcl_program: &str) {
let (plan, _scope, _) = must_plan(kcl_program); let (plan, _scope, _) = must_plan(kcl_program);
let plan_json = serde_json::to_string_pretty(&plan).unwrap(); let plan_json = serde_json::to_string_pretty(&plan).unwrap();
std::fs::write(path, plan_json).unwrap(); std::fs::write(
"/Users/adamchalmers/kc-repos/modeling-api/execution-plan-debugger/test_input.json",
plan_json,
)
.unwrap();
}
#[tokio::test]
async fn paul_qr_test() {
let program = include_str!("../testdata/paul_qr.kcl");
let ast = kcl_lib::parser::Parser::new(kcl_lib::token::lexer(program))
.ast()
.unwrap();
let mut client = Some(test_client().await);
crate::execute(ast, &mut client).await.unwrap();
use kittycad_modeling_cmds::{each_cmd, ok_response::OkModelingCmdResponse, ImageFormat};
let out = client
.unwrap()
.run_command(
uuid::Uuid::new_v4().into(),
each_cmd::TakeSnapshot {
format: ImageFormat::Png,
}
.into(),
)
.await
.unwrap();
let out = match out {
OkModelingCmdResponse::TakeSnapshot(b) => b,
other => panic!("wrong output: {other:?}"),
};
let out: Vec<u8> = out.contents.into();
std::fs::write("paul_qr_code.png", out).unwrap();
} }
#[tokio::test] #[tokio::test]
@ -1065,6 +1097,8 @@ async fn stdlib_cube_partial() {
|> close(%) |> close(%)
|> extrude(100.0, %) |> extrude(100.0, %)
"#; "#;
let (_plan, _scope, last_address) = must_plan(program);
assert_eq!(last_address, Address::ZERO + 66);
let ast = kcl_lib::parser::Parser::new(kcl_lib::token::lexer(program)) let ast = kcl_lib::parser::Parser::new(kcl_lib::token::lexer(program))
.ast() .ast()
.unwrap(); .unwrap();
@ -1107,115 +1141,23 @@ async fn stdlib_cube_partial() {
}, },
] ]
); );
use kittycad_modeling_cmds::{each_cmd, ok_response::OkModelingCmdResponse, ImageFormat}; // use kittycad_modeling_cmds::{each_cmd, ok_response::OkModelingCmdResponse, ImageFormat};
let out = client // let out = client
.unwrap() // .unwrap()
.run_command( // .run_command(
uuid::Uuid::new_v4().into(), // uuid::Uuid::new_v4().into(),
kittycad_modeling_cmds::ModelingCmd::from(each_cmd::TakeSnapshot { // each_cmd::TakeSnapshot {
format: ImageFormat::Png, // format: ImageFormat::Png,
}), // },
) // )
.await // .await
.unwrap(); // .unwrap();
// let out = match out {
let out = match out { // OkModelingCmdResponse::TakeSnapshot(b) => b,
OkModelingCmdResponse::TakeSnapshot(kittycad_modeling_cmds::output::TakeSnapshot { contents: b }) => b, // other => panic!("wrong output: {other:?}"),
other => panic!("wrong output: {other:?}"), // };
}; // let out: Vec<u8> = out.contents.into();
// std::fs::write("image.png", out).unwrap();
use image::io::Reader as ImageReader;
let img = ImageReader::new(std::io::Cursor::new(out))
.with_guessed_format()
.unwrap()
.decode()
.unwrap();
twenty_twenty::assert_image("fixtures/cube_lineTo.png", &img, 0.9999);
}
#[tokio::test]
async fn stdlib_cube_xline_yline() {
let program = r#"
let cube = startSketchAt([0.0, 0.0], "adam")
|> xLine(210.0, %, "side0")
|> yLine(210.0, %, "side1")
|> xLine(-210.0, %, "side2")
|> yLine(-210.0, %, "side3")
|> close(%)
|> extrude(100.0, %)
"#;
kcvm_dbg(
program,
"/home/lee/Code/Zoo/modeling-api/execution-plan-debugger/cube_xyline.json",
);
let (_plan, _scope, _last_address) = must_plan(program);
let ast = kcl_lib::parser::Parser::new(kcl_lib::token::lexer(program))
.ast()
.unwrap();
let mut client = Some(test_client().await);
let mem = match crate::execute(ast, &mut client).await {
Ok(mem) => mem,
Err(e) => panic!("{e}"),
};
let sg = &mem.sketch_groups.last().unwrap();
assert_eq!(
sg.path_rest,
vec![
sketch_types::PathSegment::ToPoint {
base: sketch_types::BasePath {
from: Point2d { x: 0.0, y: 0.0 },
to: Point2d { x: 210.0, y: 0.0 },
name: "side0".into(),
}
},
sketch_types::PathSegment::ToPoint {
base: sketch_types::BasePath {
from: Point2d { x: 210.0, y: 0.0 },
to: Point2d { x: 210.0, y: 210.0 },
name: "side1".into(),
}
},
sketch_types::PathSegment::ToPoint {
base: sketch_types::BasePath {
from: Point2d { x: 210.0, y: 210.0 },
to: Point2d { x: 0.0, y: 210.0 },
name: "side2".into(),
}
},
sketch_types::PathSegment::ToPoint {
base: sketch_types::BasePath {
from: Point2d { x: 0.0, y: 210.0 },
to: Point2d { x: 0.0, y: 0.0 },
name: "side3".into(),
}
},
]
);
use kittycad_modeling_cmds::{each_cmd, ok_response::OkModelingCmdResponse, ImageFormat};
let out = client
.unwrap()
.run_command(
uuid::Uuid::new_v4().into(),
kittycad_modeling_cmds::ModelingCmd::from(each_cmd::TakeSnapshot {
format: ImageFormat::Png,
}),
)
.await
.unwrap();
let out = match out {
OkModelingCmdResponse::TakeSnapshot(kittycad_modeling_cmds::output::TakeSnapshot { contents: b }) => b,
other => panic!("wrong output: {other:?}"),
};
use image::io::Reader as ImageReader;
let img = ImageReader::new(std::io::Cursor::new(out))
.with_guessed_format()
.unwrap()
.decode()
.unwrap();
twenty_twenty::assert_image("fixtures/cube_xyLine.png", &img, 0.9999);
} }
async fn test_client() -> Session { async fn test_client() -> Session {
@ -1414,51 +1356,3 @@ fn mod_and_pow() {
] ]
); );
} }
#[tokio::test]
async fn cos_sin_pi() {
let program = "
let x = cos(45.0)*10
let y = sin(45.0)*10
let z = PI
";
let (_plan, scope, _) = must_plan(program);
let Some(EpBinding::Single(x)) = scope.get("x") else {
panic!("Unexpected binding for variable 'x': {:?}", scope.get("x"));
};
let Some(EpBinding::Single(y)) = scope.get("y") else {
panic!("Unexpected binding for variable 'y': {:?}", scope.get("y"));
};
let Some(EpBinding::Constant(z)) = scope.get("z") else {
panic!("Unexpected binding for variable 'z': {:?}", scope.get("z"));
};
let ast = kcl_lib::parser::Parser::new(kcl_lib::token::lexer(program))
.ast()
.unwrap();
let mem = crate::execute(ast, &mut None).await.unwrap();
use ept::ReadMemory;
assert_eq!(*mem.get(x).unwrap(), Primitive::from(5.253219888177298));
assert_eq!(*mem.get(y).unwrap(), Primitive::from(8.509035245341185));
// Constants don't live in memory.
assert_eq!(*z, constants::PI);
}
#[tokio::test]
async fn kcl_prelude() {
let program = "
let the_answer_to_the_universe_is = hey_from_one_of_the_devs_of_the_past_i_wish_you_a_great_day_and_maybe_gave_you_a_little_smile_as_youve_found_this()
";
let (_plan, scope, _) = must_plan(program);
let Some(EpBinding::Single(the_answer_to_the_universe_is)) = scope.get("the_answer_to_the_universe_is") else {
panic!(
"Unexpected binding for variable 'the_answer_to_the_universe_is': {:?}",
scope.get("the_answer_to_the_universe_is")
);
};
let ast = kcl_lib::parser::Parser::new(kcl_lib::token::lexer(program))
.ast()
.unwrap();
let mem = crate::execute(ast, &mut None).await.unwrap();
use ept::ReadMemory;
assert_eq!(*mem.get(the_answer_to_the_universe_is).unwrap(), Primitive::from(42i64));
}

2655
src/wasm-lib/grackle/testdata/paul_qr.kcl vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,7 @@ use syn::{parse_macro_input, LitStr};
/// This macro takes exactly one argument: A string literal containing KCL. /// This macro takes exactly one argument: A string literal containing KCL.
/// # Examples /// # Examples
/// ``` /// ```
/// extern crate alloc;
/// use kcl_compile_macro::parse_kcl; /// use kcl_compile_macro::parse_kcl;
/// let ast: kcl_lib::ast::types::Program = parse_kcl!("const y = 4"); /// let ast: kcl_lib::ast::types::Program = parse_kcl!("const y = 4");
/// ``` /// ```
@ -20,14 +21,3 @@ pub fn parse(input: TokenStream) -> TokenStream {
let ast_struct = ast.bake(&Default::default()); let ast_struct = ast.bake(&Default::default());
quote!(#ast_struct).into() quote!(#ast_struct).into()
} }
/// Same as `parse!` but read in a file.
#[proc_macro]
pub fn parse_file(input: TokenStream) -> TokenStream {
let file_name = parse_macro_input!(input as LitStr);
let kcl_src = std::fs::read_to_string(file_name.value()).unwrap();
let tokens = kcl_lib::token::lexer(&kcl_src);
let ast = kcl_lib::parser::Parser::new(tokens).ast().unwrap();
let ast_struct = ast.bake(&Default::default());
quote!(#ast_struct).into()
}

View File

@ -7,10 +7,6 @@ name = "Inflector"
version = "0.11.4" version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
dependencies = [
"lazy_static",
"regex",
]
[[package]] [[package]]
name = "addr2line" name = "addr2line"
@ -48,26 +44,11 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.81" version = "1.0.80"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1"
dependencies = [ dependencies = [
"backtrace", "backtrace",
] ]
@ -111,7 +92,7 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.53", "syn 2.0.50",
] ]
[[package]] [[package]]
@ -122,7 +103,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.53", "syn 2.0.50",
] ]
[[package]] [[package]]
@ -287,17 +268,12 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.35" version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" checksum = "d87d9d13be47a5b7c3907137f1290b0459a7f80efb26be8c52afb11963bccb02"
dependencies = [ dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits", "num-traits",
"serde", "serde",
"wasm-bindgen",
"windows-targets 0.52.0",
] ]
[[package]] [[package]]
@ -344,21 +320,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "crc32fast"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.1.6" version = "0.1.6"
@ -407,7 +368,7 @@ checksum = "377af281d8f23663862a7c84623bc5dcf7f8c44b13c7496a590bdc157f941a43"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.53", "syn 2.0.50",
"synstructure", "synstructure",
] ]
@ -419,11 +380,10 @@ checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946"
[[package]] [[package]]
name = "derive-docs" name = "derive-docs"
version = "0.1.11" version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4e18a04fe569a325dd50743821f2605057ff5c4b48e60512270b4406907825b" checksum = "834580a8bd697658876ed8c9f7727e49f01d34f5b859ca921ac5b99ffc6adf77"
dependencies = [ dependencies = [
"Inflector",
"convert_case", "convert_case",
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
@ -431,7 +391,7 @@ dependencies = [
"regex", "regex",
"serde", "serde",
"serde_tokenstream", "serde_tokenstream",
"syn 2.0.53", "syn 2.0.50",
] ]
[[package]] [[package]]
@ -561,7 +521,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.53", "syn 2.0.50",
] ]
[[package]] [[package]]
@ -632,7 +592,7 @@ dependencies = [
"inflections", "inflections",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.53", "syn 2.0.50",
] ]
[[package]] [[package]]
@ -779,29 +739,6 @@ dependencies = [
"tokio-rustls 0.24.1", "tokio-rustls 0.24.1",
] ]
[[package]]
name = "iana-time-zone"
version = "0.1.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "idna" name = "idna"
version = "0.4.0" version = "0.4.0"
@ -870,23 +807,22 @@ dependencies = [
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.69" version = "0.3.68"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee"
dependencies = [ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]] [[package]]
name = "kcl-lib" name = "kcl-lib"
version = "0.1.46" version = "0.1.42"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"approx", "approx",
"async-recursion", "async-recursion",
"async-trait", "async-trait",
"bson", "bson",
"chrono",
"dashmap", "dashmap",
"databake", "databake",
"derive-docs", "derive-docs",
@ -897,14 +833,12 @@ dependencies = [
"kittycad-execution-plan-macros", "kittycad-execution-plan-macros",
"kittycad-execution-plan-traits", "kittycad-execution-plan-traits",
"lazy_static", "lazy_static",
"mime_guess",
"parse-display 0.9.0", "parse-display 0.9.0",
"reqwest", "reqwest",
"ropey", "ropey",
"schemars", "schemars",
"serde", "serde",
"serde_json", "serde_json",
"sha2",
"thiserror", "thiserror",
"tokio", "tokio",
"tokio-tungstenite", "tokio-tungstenite",
@ -915,7 +849,6 @@ dependencies = [
"wasm-bindgen-futures", "wasm-bindgen-futures",
"web-sys", "web-sys",
"winnow", "winnow",
"zip",
] ]
[[package]] [[package]]
@ -928,9 +861,9 @@ dependencies = [
[[package]] [[package]]
name = "kittycad" name = "kittycad"
version = "0.2.60" version = "0.2.54"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8aa5906d0730bd90f6b3331fe57c04951d00743169a29ee96408767b4060605" checksum = "13958174d876353f429ea8230dc92fe86f164819cea2e51bbf22e01a4c2a496e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -944,7 +877,6 @@ dependencies = [
"http 0.2.9", "http 0.2.9",
"itertools", "itertools",
"log", "log",
"mime_guess",
"parse-display 0.8.2", "parse-display 0.8.2",
"phonenumber", "phonenumber",
"rand", "rand",
@ -967,13 +899,14 @@ source = "git+https://github.com/KittyCAD/modeling-api?branch=main#1e6ef9601686c
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.53", "syn 2.0.50",
] ]
[[package]] [[package]]
name = "kittycad-execution-plan-traits" name = "kittycad-execution-plan-traits"
version = "0.1.13" version = "0.1.10"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#494225aaac06fab77c4822e7dc48738ecca35392" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3ec8efd57b59697eb140b63c0ffe7db44fdfe5a55f14e45513411eba2280ba5"
dependencies = [ dependencies = [
"serde", "serde",
"thiserror", "thiserror",
@ -1095,9 +1028,9 @@ dependencies = [
[[package]] [[package]]
name = "mio" name = "mio"
version = "0.8.11" version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
dependencies = [ dependencies = [
"libc", "libc",
"wasi", "wasi",
@ -1238,7 +1171,7 @@ dependencies = [
"regex", "regex",
"regex-syntax 0.7.5", "regex-syntax 0.7.5",
"structmeta 0.2.0", "structmeta 0.2.0",
"syn 2.0.53", "syn 2.0.50",
] ]
[[package]] [[package]]
@ -1252,7 +1185,7 @@ dependencies = [
"regex", "regex",
"regex-syntax 0.8.2", "regex-syntax 0.8.2",
"structmeta 0.3.0", "structmeta 0.3.0",
"syn 2.0.53", "syn 2.0.50",
] ]
[[package]] [[package]]
@ -1299,7 +1232,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.53", "syn 2.0.50",
] ]
[[package]] [[package]]
@ -1471,9 +1404,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.11.27" version = "0.11.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251"
dependencies = [ dependencies = [
"base64 0.21.3", "base64 0.21.3",
"bytes", "bytes",
@ -1759,7 +1692,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.53", "syn 2.0.50",
] ]
[[package]] [[package]]
@ -1793,7 +1726,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.53", "syn 2.0.50",
] ]
[[package]] [[package]]
@ -1805,7 +1738,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"serde", "serde",
"syn 2.0.53", "syn 2.0.50",
] ]
[[package]] [[package]]
@ -1843,17 +1776,6 @@ dependencies = [
"digest", "digest",
] ]
[[package]]
name = "sha2"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]] [[package]]
name = "signal-hook-registry" name = "signal-hook-registry"
version = "1.4.1" version = "1.4.1"
@ -1925,7 +1847,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"structmeta-derive 0.2.0", "structmeta-derive 0.2.0",
"syn 2.0.53", "syn 2.0.50",
] ]
[[package]] [[package]]
@ -1937,7 +1859,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"structmeta-derive 0.3.0", "structmeta-derive 0.3.0",
"syn 2.0.53", "syn 2.0.50",
] ]
[[package]] [[package]]
@ -1948,7 +1870,7 @@ checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.53", "syn 2.0.50",
] ]
[[package]] [[package]]
@ -1959,7 +1881,7 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.53", "syn 2.0.50",
] ]
[[package]] [[package]]
@ -2003,9 +1925,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.53" version = "2.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2026,7 +1948,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.53", "syn 2.0.50",
] ]
[[package]] [[package]]
@ -2082,7 +2004,7 @@ checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.53", "syn 2.0.50",
] ]
[[package]] [[package]]
@ -2155,7 +2077,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.53", "syn 2.0.50",
] ]
[[package]] [[package]]
@ -2261,7 +2183,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.53", "syn 2.0.50",
] ]
[[package]] [[package]]
@ -2290,7 +2212,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.53", "syn 2.0.50",
] ]
[[package]] [[package]]
@ -2328,7 +2250,7 @@ dependencies = [
"Inflector", "Inflector",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.53", "syn 2.0.50",
"termcolor", "termcolor",
] ]
@ -2460,9 +2382,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.92" version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"wasm-bindgen-macro", "wasm-bindgen-macro",
@ -2470,24 +2392,24 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-backend" name = "wasm-bindgen-backend"
version = "0.2.92" version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"log", "log",
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.53", "syn 2.0.50",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
[[package]] [[package]]
name = "wasm-bindgen-futures" name = "wasm-bindgen-futures"
version = "0.4.42" version = "0.4.41"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys", "js-sys",
@ -2497,9 +2419,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.92" version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@ -2507,22 +2429,22 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.92" version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.53", "syn 2.0.50",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.92" version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838"
[[package]] [[package]]
name = "wasm-streams" name = "wasm-streams"
@ -2539,9 +2461,9 @@ dependencies = [
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.69" version = "0.3.68"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
@ -2584,15 +2506,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-core"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-targets 0.52.0",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.48.0" version = "0.48.0"
@ -2767,14 +2680,3 @@ name = "zeroize"
version = "1.7.0" version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
[[package]]
name = "zip"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
dependencies = [
"byteorder",
"crc32fast",
"crossbeam-utils",
]

View File

@ -351,31 +351,6 @@ impl Program {
None None
} }
/// Merge two `Program`s together.
pub fn merge(self, ast_other: Program) -> Program {
let mut body_new: Vec<BodyItem> = vec![];
body_new.extend(self.body);
body_new.extend(ast_other.body);
let mut non_code_nodes_new: HashMap<usize, Vec<NonCodeNode>> = HashMap::new();
non_code_nodes_new.extend(self.non_code_meta.non_code_nodes);
non_code_nodes_new.extend(ast_other.non_code_meta.non_code_nodes);
let mut start_new: Vec<NonCodeNode> = vec![];
start_new.extend(self.non_code_meta.start);
start_new.extend(ast_other.non_code_meta.start);
Program {
start: self.start,
end: self.end + (ast_other.end - ast_other.start),
body: body_new,
non_code_meta: NonCodeMeta {
non_code_nodes: non_code_nodes_new,
start: start_new,
},
}
}
} }
pub trait ValueMeta { pub trait ValueMeta {

View File

@ -1,3 +0,0 @@
fn hey_from_one_of_the_devs_of_the_past_i_wish_you_a_great_day_and_maybe_gave_you_a_little_smile_as_youve_found_this = () => {
return 42
}