Files
modeling-app/rust/kcl-lib/src/lib.rs

294 lines
8.4 KiB
Rust
Raw Normal View History

//! Rust support for KCL (aka the KittyCAD Language).
//!
//! KCL is written in Rust. This crate contains the compiler tooling (e.g. parser, lexer, code generation),
//! the standard library implementation, a LSP implementation, generator for the docs, and more.
#![recursion_limit = "1024"]
Fix sharing a sketch surface between profiles (#2744) * udpates Signed-off-by: Jess Frazelle <github@jessfraz.com> * dont call until startprofileAt Signed-off-by: Jess Frazelle <github@jessfraz.com> * add a comment Signed-off-by: Jess Frazelle <github@jessfraz.com> * bump version Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * cleanup typescript code Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * cleanup redundant data Signed-off-by: Jess Frazelle <github@jessfraz.com> * fmt Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * cleanup position and rotation Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix clippy Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * upfates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * empty * new images Signed-off-by: Jess Frazelle <github@jessfraz.com> * new docs Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixes; Signed-off-by: Jess Frazelle <github@jessfraz.com> * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * fixups Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-06-21 19:54:18 -07:00
#![allow(clippy::boxed_local)]
#[allow(unused_macros)]
macro_rules! println {
($($rest:tt)*) => {
#[cfg(all(feature = "disable-println", not(test)))]
{
let _ = format!($($rest)*);
}
#[cfg(any(not(feature = "disable-println"), test))]
std::println!($($rest)*)
}
}
#[allow(unused_macros)]
macro_rules! eprintln {
($($rest:tt)*) => {
#[cfg(all(feature = "disable-println", not(test)))]
{
let _ = format!($($rest)*);
}
#[cfg(any(not(feature = "disable-println"), test))]
std::eprintln!($($rest)*)
}
}
#[allow(unused_macros)]
macro_rules! print {
($($rest:tt)*) => {
#[cfg(all(feature = "disable-println", not(test)))]
{
let _ = format!($($rest)*);
}
#[cfg(any(not(feature = "disable-println"), test))]
std::print!($($rest)*)
}
}
#[allow(unused_macros)]
macro_rules! eprint {
($($rest:tt)*) => {
#[cfg(all(feature = "disable-println", not(test)))]
{
let _ = format!($($rest)*);
}
#[cfg(any(not(feature = "disable-println"), test))]
std::eprint!($($rest)*)
}
}
#[cfg(feature = "dhat-heap")]
#[global_allocator]
static ALLOC: dhat::Alloc = dhat::Alloc;
mod coredump;
mod docs;
mod engine;
mod errors;
mod execution;
mod fs;
Add in a prototype KCL linter (#2521) * Add in a prototype KCL linter This is a fork-and-replce of an experimental project I hacked up called "kcl-vet", which was mostly the same code. This integrates kcl-vet into the kcl_lib crate, which will let us use this from the zoo cli, as well as via wasm in the lsp. this contains the intial integration with the lsp, adding all lints as informational to start. I need to go back and clean some of this up (and merge some of this back into other parts of kcl_lib); but this has some pretty good progress already. Co-authored-by: jess@zoo.dev Signed-off-by: Paul R. Tagliamonte <paul@zoo.dev> * ty clippy :) * add in a lint test * add in some docstrings * whoops * sigh * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * uno reverse card * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * wtf stop it robot fuck * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * Revert "A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)" This reverts commit 5b18f3c0355d19b4816d060ce824003cd1107d41. * hurm * try harder to type slower * try harder? this all passes locally. * try this now * simplify, add debugging for trace * fix enter use * re-order again * reorder a bit more * enter * ok fine no other enters? * nerd * wip * move control of clearing to typescript * move result out * err check * remove log * remove clear * remove add to diag * THERE CAN BE ONLY ONE * _err * dedupe * Revert "dedupe" This reverts commit f66de88200feda3910059fef651c54b9a7935d08. * attempt to dedupe * clear diagnostics on mock execute, too * handle dupe diagnostics * fmt * dedupe tsc * == vs === * fix dedupe * return this to the wasm for now * clear the map every go around this is different than the old code isnce it won't republish --------- Signed-off-by: Paul R. Tagliamonte <paul@zoo.dev> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-06-11 19:23:35 -04:00
pub mod lint;
mod log;
mod lsp;
mod modules;
mod parsing;
mod settings;
KCL: New simulation test pipeline (#4351) The idea behind this is to test all the various stages of executing KCL separately, i.e. - Start with a program - Tokenize it - Parse those tokens into an AST - Recast the AST - Execute the AST, outputting - a PNG of the rendered model - serialized program memory Each of these steps reads some input and writes some output to disk. The output of one step becomes the input to the next step. These intermediate artifacts are also snapshotted (like expectorate or 2020) to ensure we're aware of any changes to how KCL works. A change could be a bug, or it could be harmless, or deliberate, but keeping it checked into the repo means we can easily track changes. Note: UUIDs sent back by the engine are currently nondeterministic, so they would break all the snapshot tests. So, the snapshots use a regex filter and replace anything that looks like a uuid with [uuid] when writing program memory to a snapshot. In the future I hope our UUIDs will be seedable and easy to make deterministic. At that point, we can stop filtering the UUIDs. We run this pipeline on many different KCL programs. Each keeps its inputs (KCL programs), outputs (PNG, program memory snapshot) and intermediate artifacts (AST, token lists, etc) in that directory. I also added a new `just` command to easily generate these tests. You can run `just new-sim-test gear $(cat gear.kcl)` to set up a new gear test directory and generate all the intermediate artifacts for the first time. This doesn't need any macros, it just appends some new lines of normal Rust source code to `tests.rs`, so it's easy to see exactly what the code is doing. This uses `cargo insta` for convenient snapshot testing of artifacts as JSON, and `twenty-twenty` for snapshotting PNGs. This was heavily inspired by Predrag Gruevski's talk at EuroRust 2024 about deterministic simulation testing, and how it can both reduce bugs and also reduce testing/CI time. Very grateful to him for chatting with me about this over the last couple of weeks.
2024-10-30 12:14:17 -05:00
#[cfg(test)]
mod simulation_tests;
mod source_range;
pub mod std;
#[cfg(not(target_arch = "wasm32"))]
pub mod test_server;
mod thread;
mod unparser;
mod walk;
#[cfg(target_arch = "wasm32")]
mod wasm;
pub use coredump::CoreDump;
pub use engine::{EngineManager, ExecutionKind};
pub use errors::{
CompilationError, ConnectionError, ExecError, KclError, KclErrorWithOutputs, Report, ReportWithOutputs,
};
pub use execution::{
bust_cache, clear_mem_cache, ExecOutcome, ExecState, ExecutorContext, ExecutorSettings, MetaSettings, Point2d,
};
pub use lsp::{
copilot::Backend as CopilotLspBackend,
kcl::{Backend as KclLspBackend, Server as KclLspServerSubCommand},
};
pub use modules::ModuleId;
pub use parsing::ast::{modify::modify_ast_for_sketch, types::FormatOptions};
pub use settings::types::{project::ProjectConfiguration, Configuration, UnitLength};
pub use source_range::SourceRange;
// Rather than make executor public and make lots of it pub(crate), just re-export into a new module.
// Ideally we wouldn't export these things at all, they should only be used for testing.
pub mod exec {
pub use crate::execution::{ArtifactCommand, DefaultPlanes, IdGenerator, KclValue, PlaneType, Sketch};
}
#[cfg(target_arch = "wasm32")]
pub mod wasm_engine {
pub use crate::{
coredump::wasm::{CoreDumpManager, CoreDumper},
engine::conn_wasm::{EngineCommandManager, EngineConnection},
fs::wasm::{FileManager, FileSystemManager},
};
}
#[cfg(not(target_arch = "wasm32"))]
pub mod native_engine {
pub use crate::engine::conn::EngineConnection;
}
pub mod std_utils {
pub use crate::std::utils::{get_tangential_arc_to_info, is_points_ccw_wasm, TangentialArcInfoInput};
}
pub mod pretty {
pub use crate::{parsing::token::NumericSuffix, unparser::format_number};
}
use serde::{Deserialize, Serialize};
#[allow(unused_imports)]
use crate::log::{log, logln};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Program {
#[serde(flatten)]
start of cache: don't re-execute on whitespace / top level code comment changes (#4663) * start Signed-off-by: Jess Frazelle <github@jessfraz.com> working for whitespace Signed-off-by: Jess Frazelle <github@jessfraz.com> pull thru Signed-off-by: Jess Frazelle <github@jessfraz.com> fix wasm Signed-off-by: Jess Frazelle <github@jessfraz.com> pull thru to js start Signed-off-by: Jess Frazelle <github@jessfraz.com> actually use the cache in ts Signed-off-by: Jess Frazelle <github@jessfraz.com> rust owns clearing the scene Signed-off-by: Jess Frazelle <github@jessfraz.com> fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> empty stupid log Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> fix tests Signed-off-by: Jess Frazelle <github@jessfraz.com> updatez Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> save the state Signed-off-by: Jess Frazelle <github@jessfraz.com> save the state Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> use the old memory Signed-off-by: Jess Frazelle <github@jessfraz.com> cleanup to use the old exec state Signed-off-by: Jess Frazelle <github@jessfraz.com> fices Signed-off-by: Jess Frazelle <github@jessfraz.com> updates; Signed-off-by: Jess Frazelle <github@jessfraz.com> fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> fmt Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> cleanup Signed-off-by: Jess Frazelle <github@jessfraz.com> fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> * rebase and compile Signed-off-by: Jess Frazelle <github@jessfraz.com> * Look at this (photo)Graph *in the voice of Nickelback* * fix the lsp to use the cache Signed-off-by: Jess Frazelle <github@jessfraz.com> * add comment Signed-off-by: Jess Frazelle <github@jessfraz.com> * use a global static instead; Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix rust test Signed-off-by: Jess Frazelle <github@jessfraz.com> * cleanup more Signed-off-by: Jess Frazelle <github@jessfraz.com> * cleanups Signed-off-by: Jess Frazelle <github@jessfraz.com> * cleanup the api even more Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * Look at this (photo)Graph *in the voice of Nickelback* * bust the cache on unit changes Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * Look at this (photo)Graph *in the voice of Nickelback* * stupid codespell Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-05 19:51:06 -08:00
pub ast: parsing::ast::types::Node<parsing::ast::types::Program>,
// The ui doesn't need to know about this.
// It's purely used for saving the contents of the original file, so we can use it for errors.
// Because in the case of the root file, we don't want to read the file from disk again.
#[serde(skip)]
pub original_file_contents: String,
}
#[cfg(any(test, feature = "lsp-test-util"))]
pub use lsp::test_util::copilot_lsp_server;
#[cfg(any(test, feature = "lsp-test-util"))]
pub use lsp::test_util::kcl_lsp_server;
impl Program {
pub fn parse(input: &str) -> Result<(Option<Program>, Vec<CompilationError>), KclError> {
let module_id = ModuleId::default();
let tokens = parsing::token::lex(input, module_id)?;
let (ast, errs) = parsing::parse_tokens(tokens).0?;
Ok((
ast.map(|ast| Program {
ast,
original_file_contents: input.to_string(),
}),
errs,
))
}
pub fn parse_no_errs(input: &str) -> Result<Program, KclError> {
let module_id = ModuleId::default();
let tokens = parsing::token::lex(input, module_id)?;
let ast = parsing::parse_tokens(tokens).parse_errs_as_err()?;
Ok(Program {
ast,
original_file_contents: input.to_string(),
})
}
pub fn compute_digest(&mut self) -> parsing::ast::digest::Digest {
self.ast.compute_digest()
}
/// Get the meta settings for the kcl file from the annotations.
pub fn meta_settings(&self) -> Result<Option<crate::MetaSettings>, KclError> {
self.ast.meta_settings()
}
/// Change the meta settings for the kcl file.
pub fn change_meta_settings(&self, settings: crate::MetaSettings) -> Result<Self, KclError> {
Ok(Self {
ast: self.ast.change_meta_settings(settings)?,
original_file_contents: self.original_file_contents.clone(),
})
}
pub fn lint_all(&self) -> Result<Vec<lint::Discovered>, anyhow::Error> {
self.ast.lint_all()
}
pub fn lint<'a>(&'a self, rule: impl lint::Rule<'a>) -> Result<Vec<lint::Discovered>, anyhow::Error> {
self.ast.lint(rule)
}
pub fn recast(&self) -> String {
// Use the default options until we integrate into the UI the ability to change them.
self.ast.recast(&Default::default(), 0)
}
pub fn recast_with_options(&self, options: &FormatOptions) -> String {
self.ast.recast(options, 0)
}
}
#[inline]
fn try_f64_to_usize(f: f64) -> Option<usize> {
let i = f as usize;
if i as f64 == f {
Some(i)
} else {
None
}
}
#[inline]
fn try_f64_to_u32(f: f64) -> Option<u32> {
let i = f as u32;
if i as f64 == f {
Some(i)
} else {
None
}
}
#[inline]
fn try_f64_to_u64(f: f64) -> Option<u64> {
let i = f as u64;
if i as f64 == f {
Some(i)
} else {
None
}
}
#[inline]
fn try_f64_to_i64(f: f64) -> Option<i64> {
let i = f as i64;
if i as f64 == f {
Some(i)
} else {
None
}
}
/// Get the version of the KCL library.
pub fn version() -> &'static str {
env!("CARGO_PKG_VERSION")
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn convert_int() {
assert_eq!(try_f64_to_usize(0.0), Some(0));
assert_eq!(try_f64_to_usize(42.0), Some(42));
assert_eq!(try_f64_to_usize(0.00000000001), None);
assert_eq!(try_f64_to_usize(-1.0), None);
assert_eq!(try_f64_to_usize(f64::NAN), None);
assert_eq!(try_f64_to_usize(f64::INFINITY), None);
assert_eq!(try_f64_to_usize((0.1 + 0.2) * 10.0), None);
assert_eq!(try_f64_to_u32(0.0), Some(0));
assert_eq!(try_f64_to_u32(42.0), Some(42));
assert_eq!(try_f64_to_u32(0.00000000001), None);
assert_eq!(try_f64_to_u32(-1.0), None);
assert_eq!(try_f64_to_u32(f64::NAN), None);
assert_eq!(try_f64_to_u32(f64::INFINITY), None);
assert_eq!(try_f64_to_u32((0.1 + 0.2) * 10.0), None);
assert_eq!(try_f64_to_u64(0.0), Some(0));
assert_eq!(try_f64_to_u64(42.0), Some(42));
assert_eq!(try_f64_to_u64(0.00000000001), None);
assert_eq!(try_f64_to_u64(-1.0), None);
assert_eq!(try_f64_to_u64(f64::NAN), None);
assert_eq!(try_f64_to_u64(f64::INFINITY), None);
assert_eq!(try_f64_to_u64((0.1 + 0.2) * 10.0), None);
assert_eq!(try_f64_to_i64(0.0), Some(0));
assert_eq!(try_f64_to_i64(42.0), Some(42));
assert_eq!(try_f64_to_i64(0.00000000001), None);
assert_eq!(try_f64_to_i64(-1.0), Some(-1));
assert_eq!(try_f64_to_i64(f64::NAN), None);
assert_eq!(try_f64_to_i64(f64::INFINITY), None);
assert_eq!(try_f64_to_i64((0.1 + 0.2) * 10.0), None);
}
}