Refactor SourceRange and ModuleId to make them better encapsualated (#4636)

Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
Nick Cameron
2024-12-03 16:39:51 +13:00
committed by GitHub
parent 56771d561a
commit 68ae7e98f9
40 changed files with 371 additions and 312 deletions

View File

@ -6,6 +6,7 @@ use kcmc::{
};
use kittycad_modeling_cmds as kcmc;
use super::types::Node;
use crate::{
ast::types::{
ArrayExpression, CallExpression, ConstraintLevel, FormatOptions, Literal, PipeExpression, PipeSubstitution,
@ -13,12 +14,11 @@ use crate::{
},
engine::EngineManager,
errors::{KclError, KclErrorDetails},
executor::{Point2d, SourceRange},
executor::Point2d,
source_range::{ModuleId, SourceRange},
Program,
};
use super::types::{ModuleId, Node};
type Point3d = kcmc::shared::Point3d<f64>;
#[derive(Debug)]

View File

@ -27,8 +27,9 @@ pub use crate::ast::types::{
use crate::{
docs::StdLibFn,
errors::KclError,
executor::{ExecState, ExecutorContext, KclValue, Metadata, SourceRange, TagIdentifier},
executor::{ExecState, ExecutorContext, KclValue, Metadata, TagIdentifier},
parser::PIPE_OPERATOR,
source_range::{ModuleId, SourceRange},
std::kcl_stdlib::KclStdLibFn,
};
@ -62,7 +63,7 @@ pub struct Node<T> {
impl<T> Node<T> {
pub fn metadata(&self) -> Metadata {
Metadata {
source_range: SourceRange([self.start, self.end, self.module_id.0 as usize]),
source_range: SourceRange::new(self.start, self.end, self.module_id),
}
}
@ -122,7 +123,7 @@ impl<T> Node<T> {
}
pub fn as_source_range(&self) -> SourceRange {
SourceRange([self.start, self.end, self.module_id.as_usize()])
SourceRange::new(self.start, self.end, self.module_id)
}
pub fn as_source_ranges(&self) -> Vec<SourceRange> {
@ -150,21 +151,21 @@ impl<T: fmt::Display> fmt::Display for Node<T> {
}
}
impl<T> From<Node<T>> for crate::executor::SourceRange {
impl<T> From<Node<T>> for SourceRange {
fn from(v: Node<T>) -> Self {
Self([v.start, v.end, v.module_id.as_usize()])
Self::new(v.start, v.end, v.module_id)
}
}
impl<T> From<&Node<T>> for crate::executor::SourceRange {
impl<T> From<&Node<T>> for SourceRange {
fn from(v: &Node<T>) -> Self {
Self([v.start, v.end, v.module_id.as_usize()])
Self::new(v.start, v.end, v.module_id)
}
}
impl<T> From<&BoxNode<T>> for crate::executor::SourceRange {
impl<T> From<&BoxNode<T>> for SourceRange {
fn from(v: &BoxNode<T>) -> Self {
Self([v.start, v.end, v.module_id.as_usize()])
Self::new(v.start, v.end, v.module_id)
}
}
@ -553,29 +554,6 @@ impl Shebang {
}
}
/// Identifier of a source file. Uses a u32 to keep the size small.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize, ts_rs::TS, JsonSchema, Bake)]
#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
#[databake(path = kcl_lib::ast::types)]
#[ts(export)]
pub struct ModuleId(pub u32);
impl ModuleId {
pub fn from_usize(id: usize) -> Self {
Self(u32::try_from(id).expect("module ID should fit in a u32"))
}
pub fn as_usize(&self) -> usize {
usize::try_from(self.0).expect("module ID should fit in a usize")
}
/// Top-level file is the one being executed.
/// Represented by module ID of 0, i.e. the default value.
pub fn is_top_level(&self) -> bool {
*self == Self::default()
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
#[databake(path = kcl_lib::ast::types)]
#[ts(export)]
@ -609,13 +587,13 @@ impl BodyItem {
impl From<BodyItem> for SourceRange {
fn from(item: BodyItem) -> Self {
Self([item.start(), item.end(), item.module_id().as_usize()])
Self::new(item.start(), item.end(), item.module_id())
}
}
impl From<&BodyItem> for SourceRange {
fn from(item: &BodyItem) -> Self {
Self([item.start(), item.end(), item.module_id().as_usize()])
Self::new(item.start(), item.end(), item.module_id())
}
}
@ -837,13 +815,13 @@ impl Expr {
impl From<Expr> for SourceRange {
fn from(value: Expr) -> Self {
Self([value.start(), value.end(), value.module_id().as_usize()])
Self::new(value.start(), value.end(), value.module_id())
}
}
impl From<&Expr> for SourceRange {
fn from(value: &Expr) -> Self {
Self([value.start(), value.end(), value.module_id().as_usize()])
Self::new(value.start(), value.end(), value.module_id())
}
}
@ -864,13 +842,13 @@ pub enum BinaryPart {
impl From<BinaryPart> for SourceRange {
fn from(value: BinaryPart) -> Self {
Self([value.start(), value.end(), value.module_id().as_usize()])
Self::new(value.start(), value.end(), value.module_id())
}
}
impl From<&BinaryPart> for SourceRange {
fn from(value: &BinaryPart) -> Self {
Self([value.start(), value.end(), value.module_id().as_usize()])
Self::new(value.start(), value.end(), value.module_id())
}
}
@ -2329,13 +2307,13 @@ impl MemberObject {
impl From<MemberObject> for SourceRange {
fn from(obj: MemberObject) -> Self {
Self([obj.start(), obj.end(), obj.module_id().as_usize()])
Self::new(obj.start(), obj.end(), obj.module_id())
}
}
impl From<&MemberObject> for SourceRange {
fn from(obj: &MemberObject) -> Self {
Self([obj.start(), obj.end(), obj.module_id().as_usize()])
Self::new(obj.start(), obj.end(), obj.module_id())
}
}
@ -2366,13 +2344,13 @@ impl LiteralIdentifier {
impl From<LiteralIdentifier> for SourceRange {
fn from(id: LiteralIdentifier) -> Self {
Self([id.start(), id.end(), id.module_id().as_usize()])
Self::new(id.start(), id.end(), id.module_id())
}
}
impl From<&LiteralIdentifier> for SourceRange {
fn from(id: &LiteralIdentifier) -> Self {
Self([id.start(), id.end(), id.module_id().as_usize()])
Self::new(id.start(), id.end(), id.module_id())
}
}

View File

@ -1,15 +1,10 @@
use crate::executor::SourceRange;
use super::BoxNode;
use super::ConstraintLevel;
use super::Hover;
use super::Node;
use super::NodeList;
use super::{Digest, Expr};
use databake::*;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use super::{BoxNode, ConstraintLevel, Digest, Expr, Hover, Node, NodeList};
use crate::SourceRange;
// TODO: This should be its own type, similar to Program,
// but guaranteed to have an Expression as its final item.
// https://github.com/KittyCAD/modeling-app/issues/4015
@ -50,7 +45,7 @@ impl Node<IfExpression> {
impl Node<ElseIf> {
#[allow(dead_code)]
fn source_ranges(&self) -> Vec<SourceRange> {
vec![SourceRange([self.start, self.end, self.module_id.as_usize()])]
vec![SourceRange::new(self.start, self.end, self.module_id)]
}
}

View File

@ -1,5 +1,7 @@
use std::collections::HashMap;
use async_recursion::async_recursion;
use super::{
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, CallExpression,
CallExpressionKw, Expr, IfExpression, KclNone, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject,
@ -7,13 +9,10 @@ use super::{
};
use crate::{
errors::{KclError, KclErrorDetails},
executor::{
BodyType, ExecState, ExecutorContext, KclValue, Metadata, SourceRange, StatementKind, TagEngineInfo,
TagIdentifier,
},
executor::{BodyType, ExecState, ExecutorContext, KclValue, Metadata, StatementKind, TagEngineInfo, TagIdentifier},
source_range::SourceRange,
std::{args::Arg, FunctionKind},
};
use async_recursion::async_recursion;
const FLOAT_TO_INT_MAX_DELTA: f64 = 0.01;

View File

@ -3,9 +3,8 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value as JValue;
use crate::ast::types::{Expr, Literal};
use super::Node;
use crate::ast::types::{Expr, Literal};
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
#[databake(path = kcl_lib::ast::types)]

View File

@ -4,9 +4,8 @@ use databake::*;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::{ast::types::ConstraintLevel, executor::KclValue};
use super::Node;
use crate::{ast::types::ConstraintLevel, executor::KclValue};
const KCL_NONE_ID: &str = "KCL_NONE_ID";

View File

@ -1,4 +1,5 @@
use super::{BinaryPart, BodyItem, Expr, LiteralIdentifier, MemberObject, ModuleId};
use super::{BinaryPart, BodyItem, Expr, LiteralIdentifier, MemberObject};
use crate::source_range::ModuleId;
impl BodyItem {
pub fn module_id(&self) -> ModuleId {

View File

@ -18,14 +18,14 @@ use kittycad_modeling_cmds as kcmc;
use tokio::sync::{mpsc, oneshot, RwLock};
use tokio_tungstenite::tungstenite::Message as WsMsg;
use super::ExecutionKind;
use crate::{
engine::EngineManager,
errors::{KclError, KclErrorDetails},
executor::{DefaultPlanes, IdGenerator},
SourceRange,
};
use super::ExecutionKind;
#[derive(Debug, PartialEq)]
enum SocketHealth {
Active,
@ -41,8 +41,8 @@ pub struct EngineConnection {
#[allow(dead_code)]
tcp_read_handle: Arc<TcpReadHandle>,
socket_health: Arc<Mutex<SocketHealth>>,
batch: Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>>,
batch_end: Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, crate::executor::SourceRange)>>>,
batch: Arc<Mutex<Vec<(WebSocketRequest, SourceRange)>>>,
batch_end: Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>,
/// The default planes for the scene.
default_planes: Arc<RwLock<Option<DefaultPlanes>>>,
@ -311,11 +311,11 @@ impl EngineConnection {
#[async_trait::async_trait]
impl EngineManager for EngineConnection {
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>> {
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, SourceRange)>>> {
self.batch.clone()
}
fn batch_end(&self) -> Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, crate::executor::SourceRange)>>> {
fn batch_end(&self) -> Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>> {
self.batch_end.clone()
}
@ -334,7 +334,7 @@ impl EngineManager for EngineConnection {
async fn default_planes(
&self,
id_generator: &mut IdGenerator,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<DefaultPlanes, KclError> {
{
let opt = self.default_planes.read().await.as_ref().cloned();
@ -352,7 +352,7 @@ impl EngineManager for EngineConnection {
async fn clear_scene_post_hook(
&self,
id_generator: &mut IdGenerator,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<(), KclError> {
// Remake the default planes, since they would have been removed after the scene was cleared.
let new_planes = self.new_default_planes(id_generator, source_range).await?;
@ -364,9 +364,9 @@ impl EngineManager for EngineConnection {
async fn inner_send_modeling_cmd(
&self,
id: uuid::Uuid,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
cmd: WebSocketRequest,
_id_to_source_range: std::collections::HashMap<uuid::Uuid, crate::executor::SourceRange>,
_id_to_source_range: std::collections::HashMap<uuid::Uuid, SourceRange>,
) -> Result<WebSocketResponse, KclError> {
let (tx, rx) = oneshot::channel();

View File

@ -17,17 +17,17 @@ use kcmc::{
};
use kittycad_modeling_cmds::{self as kcmc};
use super::ExecutionKind;
use crate::{
errors::KclError,
executor::{DefaultPlanes, IdGenerator},
SourceRange,
};
use super::ExecutionKind;
#[derive(Debug, Clone)]
pub struct EngineConnection {
batch: Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>>,
batch_end: Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, crate::executor::SourceRange)>>>,
batch: Arc<Mutex<Vec<(WebSocketRequest, SourceRange)>>>,
batch_end: Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>,
execution_kind: Arc<Mutex<ExecutionKind>>,
}
@ -43,11 +43,11 @@ impl EngineConnection {
#[async_trait::async_trait]
impl crate::engine::EngineManager for EngineConnection {
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>> {
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, SourceRange)>>> {
self.batch.clone()
}
fn batch_end(&self) -> Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, crate::executor::SourceRange)>>> {
fn batch_end(&self) -> Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>> {
self.batch_end.clone()
}
@ -66,7 +66,7 @@ impl crate::engine::EngineManager for EngineConnection {
async fn default_planes(
&self,
_id_generator: &mut IdGenerator,
_source_range: crate::executor::SourceRange,
_source_range: SourceRange,
) -> Result<DefaultPlanes, KclError> {
Ok(DefaultPlanes::default())
}
@ -74,7 +74,7 @@ impl crate::engine::EngineManager for EngineConnection {
async fn clear_scene_post_hook(
&self,
_id_generator: &mut IdGenerator,
_source_range: crate::executor::SourceRange,
_source_range: SourceRange,
) -> Result<(), KclError> {
Ok(())
}
@ -82,9 +82,9 @@ impl crate::engine::EngineManager for EngineConnection {
async fn inner_send_modeling_cmd(
&self,
id: uuid::Uuid,
_source_range: crate::executor::SourceRange,
_source_range: SourceRange,
cmd: WebSocketRequest,
_id_to_source_range: std::collections::HashMap<uuid::Uuid, crate::executor::SourceRange>,
_id_to_source_range: std::collections::HashMap<uuid::Uuid, SourceRange>,
) -> Result<WebSocketResponse, KclError> {
match cmd {
WebSocketRequest::ModelingCmdBatchReq(ModelingBatch {

View File

@ -12,6 +12,7 @@ use crate::{
engine::ExecutionKind,
errors::{KclError, KclErrorDetails},
executor::{DefaultPlanes, IdGenerator},
SourceRange,
};
#[wasm_bindgen(module = "/../../lang/std/engineConnection.ts")]
@ -41,8 +42,8 @@ extern "C" {
#[derive(Debug, Clone)]
pub struct EngineConnection {
manager: Arc<EngineCommandManager>,
batch: Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>>,
batch_end: Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, crate::executor::SourceRange)>>>,
batch: Arc<Mutex<Vec<(WebSocketRequest, SourceRange)>>>,
batch_end: Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>,
execution_kind: Arc<Mutex<ExecutionKind>>,
}
@ -63,11 +64,11 @@ impl EngineConnection {
#[async_trait::async_trait]
impl crate::engine::EngineManager for EngineConnection {
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>> {
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, SourceRange)>>> {
self.batch.clone()
}
fn batch_end(&self) -> Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, crate::executor::SourceRange)>>> {
fn batch_end(&self) -> Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>> {
self.batch_end.clone()
}
@ -86,7 +87,7 @@ impl crate::engine::EngineManager for EngineConnection {
async fn default_planes(
&self,
_id_generator: &mut IdGenerator,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<DefaultPlanes, KclError> {
// Get the default planes.
let promise = self.manager.get_default_planes().map_err(|e| {
@ -128,7 +129,7 @@ impl crate::engine::EngineManager for EngineConnection {
async fn clear_scene_post_hook(
&self,
_id_generator: &mut IdGenerator,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<(), KclError> {
self.manager.clear_default_planes().map_err(|e| {
KclError::Engine(KclErrorDetails {
@ -158,9 +159,9 @@ impl crate::engine::EngineManager for EngineConnection {
async fn inner_send_modeling_cmd(
&self,
id: uuid::Uuid,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
cmd: WebSocketRequest,
id_to_source_range: std::collections::HashMap<uuid::Uuid, crate::executor::SourceRange>,
id_to_source_range: std::collections::HashMap<uuid::Uuid, SourceRange>,
) -> Result<WebSocketResponse, KclError> {
let source_range_str = serde_json::to_string(&source_range).map_err(|e| {
KclError::Engine(KclErrorDetails {

View File

@ -33,6 +33,7 @@ use uuid::Uuid;
use crate::{
errors::{KclError, KclErrorDetails},
executor::{DefaultPlanes, IdGenerator, Point3d},
SourceRange,
};
lazy_static::lazy_static! {
@ -61,10 +62,10 @@ impl ExecutionKind {
#[async_trait::async_trait]
pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
/// Get the batch of commands to be sent to the engine.
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, crate::executor::SourceRange)>>>;
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, SourceRange)>>>;
/// Get the batch of end commands to be sent to the engine.
fn batch_end(&self) -> Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, crate::executor::SourceRange)>>>;
fn batch_end(&self) -> Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, SourceRange)>>>;
/// Get the current execution kind.
fn execution_kind(&self) -> ExecutionKind;
@ -77,7 +78,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
async fn default_planes(
&self,
id_generator: &mut IdGenerator,
_source_range: crate::executor::SourceRange,
_source_range: SourceRange,
) -> Result<DefaultPlanes, crate::errors::KclError>;
/// Helpers to be called after clearing a scene.
@ -85,22 +86,22 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
async fn clear_scene_post_hook(
&self,
id_generator: &mut IdGenerator,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<(), crate::errors::KclError>;
/// Send a modeling command and wait for the response message.
async fn inner_send_modeling_cmd(
&self,
id: uuid::Uuid,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
cmd: WebSocketRequest,
id_to_source_range: HashMap<uuid::Uuid, crate::executor::SourceRange>,
id_to_source_range: HashMap<uuid::Uuid, SourceRange>,
) -> Result<kcmc::websocket::WebSocketResponse, crate::errors::KclError>;
async fn clear_scene(
&self,
id_generator: &mut IdGenerator,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<(), crate::errors::KclError> {
self.batch_modeling_cmd(
uuid::Uuid::new_v4(),
@ -123,7 +124,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
async fn batch_modeling_cmd(
&self,
id: uuid::Uuid,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
cmd: &ModelingCmd,
) -> Result<(), crate::errors::KclError> {
let execution_kind = self.execution_kind();
@ -147,7 +148,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
async fn batch_end_cmd(
&self,
id: uuid::Uuid,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
cmd: &ModelingCmd,
) -> Result<(), crate::errors::KclError> {
let req = WebSocketRequest::ModelingCmdReq(ModelingCmdReq {
@ -166,7 +167,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
async fn send_modeling_cmd(
&self,
id: uuid::Uuid,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
cmd: ModelingCmd,
) -> Result<OkWebSocketResponseData, crate::errors::KclError> {
self.batch_modeling_cmd(id, source_range, &cmd).await?;
@ -181,7 +182,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
// Whether or not to flush the end commands as well.
// We only do this at the very end of the file.
batch_end: bool,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<OkWebSocketResponseData, crate::errors::KclError> {
let all_requests = if batch_end {
let mut requests = self.batch().lock().unwrap().clone();
@ -303,7 +304,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
x_axis: Point3d,
y_axis: Point3d,
color: Option<Color>,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<uuid::Uuid, KclError> {
// Create new default planes.
let default_size = 100.0;
@ -339,7 +340,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
async fn new_default_planes(
&self,
id_generator: &mut IdGenerator,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<DefaultPlanes, KclError> {
let plane_settings: HashMap<PlaneName, (Uuid, Point3d, Point3d, Option<Color>)> = HashMap::from([
(
@ -450,7 +451,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
fn parse_websocket_response(
&self,
response: WebSocketResponse,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<OkWebSocketResponseData, crate::errors::KclError> {
match response {
WebSocketResponse::Success(success) => Ok(success.resp),
@ -469,7 +470,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
// The last response we are looking for.
id: uuid::Uuid,
// The mapping of source ranges to command IDs.
id_to_source_range: HashMap<uuid::Uuid, crate::executor::SourceRange>,
id_to_source_range: HashMap<uuid::Uuid, SourceRange>,
// The response from the engine.
responses: HashMap<uuid::Uuid, BatchResponse>,
) -> Result<OkWebSocketResponseData, crate::errors::KclError> {

View File

@ -2,7 +2,10 @@ use serde::{Deserialize, Serialize};
use thiserror::Error;
use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity};
use crate::{ast::types::ModuleId, executor::SourceRange, lsp::IntoDiagnostic};
use crate::{
lsp::IntoDiagnostic,
source_range::{ModuleId, SourceRange},
};
/// How did the KCL execution fail
#[derive(thiserror::Error, Debug)]

View File

@ -19,20 +19,18 @@ use kittycad_modeling_cmds::length_unit::LengthUnit;
use parse_display::{Display, FromStr};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use tower_lsp::lsp_types::{Position as LspPosition, Range as LspRange};
type Point2D = kcmc::shared::Point2d<f64>;
type Point3D = kcmc::shared::Point3d<f64>;
pub use crate::kcl_value::KclValue;
use crate::{
ast::types::{
BodyItem, Expr, FunctionExpression, ItemVisibility, KclNone, ModuleId, Node, NodeRef, TagDeclarator, TagNode,
},
ast::types::{BodyItem, Expr, FunctionExpression, ItemVisibility, KclNone, Node, NodeRef, TagDeclarator, TagNode},
engine::{EngineManager, ExecutionKind},
errors::{KclError, KclErrorDetails},
fs::{FileManager, FileSystem},
settings::types::UnitLength,
source_range::{ModuleId, SourceRange},
std::{args::Arg, StdLib},
ExecError, Program,
};
@ -1002,101 +1000,6 @@ pub struct ModuleInfo {
path: std::path::PathBuf,
}
#[derive(Debug, Default, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema, Hash, Eq)]
#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
#[ts(export)]
pub struct SourceRange(#[ts(type = "[number, number]")] pub [usize; 3]);
impl From<[usize; 3]> for SourceRange {
fn from(value: [usize; 3]) -> Self {
Self(value)
}
}
impl From<&SourceRange> for miette::SourceSpan {
fn from(source_range: &SourceRange) -> Self {
let length = source_range.end() - source_range.start();
let start = miette::SourceOffset::from(source_range.start());
Self::new(start, length)
}
}
impl From<SourceRange> for miette::SourceSpan {
fn from(source_range: SourceRange) -> Self {
Self::from(&source_range)
}
}
impl SourceRange {
/// Create a new source range.
pub fn new(start: usize, end: usize, module_id: ModuleId) -> Self {
Self([start, end, module_id.as_usize()])
}
/// A source range that doesn't correspond to any source code.
pub fn synthetic() -> Self {
Self::default()
}
/// Get the start of the range.
pub fn start(&self) -> usize {
self.0[0]
}
/// Get the end of the range.
pub fn end(&self) -> usize {
self.0[1]
}
/// Get the module ID of the range.
pub fn module_id(&self) -> ModuleId {
ModuleId::from_usize(self.0[2])
}
/// Check if the range contains a position.
pub fn contains(&self, pos: usize) -> bool {
pos >= self.start() && pos <= self.end()
}
pub fn start_to_lsp_position(&self, code: &str) -> LspPosition {
// Calculate the line and column of the error from the source range.
// Lines are zero indexed in vscode so we need to subtract 1.
let mut line = code.get(..self.start()).unwrap_or_default().lines().count();
if line > 0 {
line = line.saturating_sub(1);
}
let column = code[..self.start()].lines().last().map(|l| l.len()).unwrap_or_default();
LspPosition {
line: line as u32,
character: column as u32,
}
}
pub fn end_to_lsp_position(&self, code: &str) -> LspPosition {
let lines = code.get(..self.end()).unwrap_or_default().lines();
if lines.clone().count() == 0 {
return LspPosition { line: 0, character: 0 };
}
// Calculate the line and column of the error from the source range.
// Lines are zero indexed in vscode so we need to subtract 1.
let line = lines.clone().count() - 1;
let column = lines.last().map(|l| l.len()).unwrap_or_default();
LspPosition {
line: line as u32,
character: column as u32,
}
}
pub fn to_lsp_range(&self, code: &str) -> LspRange {
let start = self.start_to_lsp_position(code);
let end = self.end_to_lsp_position(code);
LspRange { start, end }
}
}
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema)]
#[ts(export)]
pub struct Point2d {
@ -2109,7 +2012,7 @@ impl ExecutorContext {
// True here tells the engine to flush all the end commands as well like fillets
// and chamfers where the engine would otherwise eat the ID of the segments.
true,
SourceRange([program.end, program.end, program.module_id.as_usize()]),
SourceRange::new(program.end, program.end, program.module_id),
)
.await?;
}
@ -2715,7 +2618,10 @@ const answer = returnX()"#;
err,
KclError::UndefinedValue(KclErrorDetails {
message: "memory item key `x` is not defined".to_owned(),
source_ranges: vec![SourceRange([64, 65, 0]), SourceRange([97, 106, 0])],
source_ranges: vec![
SourceRange::new(64, 65, ModuleId::default()),
SourceRange::new(97, 106, ModuleId::default())
],
}),
);
}
@ -2750,7 +2656,7 @@ let shape = layer() |> patternTransform(10, transform, %)
err,
KclError::UndefinedValue(KclErrorDetails {
message: "memory item key `x` is not defined".to_owned(),
source_ranges: vec![SourceRange([80, 81, 0])],
source_ranges: vec![SourceRange::new(80, 81, ModuleId::default())],
}),
);
}
@ -2846,7 +2752,7 @@ let notNull = !myNull
parse_execute(code1).await.unwrap_err().downcast::<KclError>().unwrap(),
KclError::Semantic(KclErrorDetails {
message: "Cannot apply unary operator ! to non-boolean value: number".to_owned(),
source_ranges: vec![SourceRange([56, 63, 0])],
source_ranges: vec![SourceRange::new(56, 63, ModuleId::default())],
})
);
@ -2855,7 +2761,7 @@ let notNull = !myNull
parse_execute(code2).await.unwrap_err().downcast::<KclError>().unwrap(),
KclError::Semantic(KclErrorDetails {
message: "Cannot apply unary operator ! to non-boolean value: number".to_owned(),
source_ranges: vec![SourceRange([14, 16, 0])],
source_ranges: vec![SourceRange::new(14, 16, ModuleId::default())],
})
);
@ -2866,7 +2772,7 @@ let notEmptyString = !""
parse_execute(code3).await.unwrap_err().downcast::<KclError>().unwrap(),
KclError::Semantic(KclErrorDetails {
message: "Cannot apply unary operator ! to non-boolean value: string (text)".to_owned(),
source_ranges: vec![SourceRange([22, 25, 0])],
source_ranges: vec![SourceRange::new(22, 25, ModuleId::default())],
})
);
@ -2878,7 +2784,7 @@ let notMember = !obj.a
parse_execute(code4).await.unwrap_err().downcast::<KclError>().unwrap(),
KclError::Semantic(KclErrorDetails {
message: "Cannot apply unary operator ! to non-boolean value: number".to_owned(),
source_ranges: vec![SourceRange([36, 42, 0])],
source_ranges: vec![SourceRange::new(36, 42, ModuleId::default())],
})
);
@ -2889,7 +2795,7 @@ let notArray = !a";
parse_execute(code5).await.unwrap_err().downcast::<KclError>().unwrap(),
KclError::Semantic(KclErrorDetails {
message: "Cannot apply unary operator ! to non-boolean value: array (list)".to_owned(),
source_ranges: vec![SourceRange([27, 29, 0])],
source_ranges: vec![SourceRange::new(27, 29, ModuleId::default())],
})
);
@ -2900,7 +2806,7 @@ let notObject = !x";
parse_execute(code6).await.unwrap_err().downcast::<KclError>().unwrap(),
KclError::Semantic(KclErrorDetails {
message: "Cannot apply unary operator ! to non-boolean value: object".to_owned(),
source_ranges: vec![SourceRange([28, 30, 0])],
source_ranges: vec![SourceRange::new(28, 30, ModuleId::default())],
})
);
@ -2953,7 +2859,7 @@ let notTagIdentifier = !myTag";
parse_execute(code10).await.unwrap_err().downcast::<KclError>().unwrap(),
KclError::Syntax(KclErrorDetails {
message: "Unexpected token: !".to_owned(),
source_ranges: vec![SourceRange([14, 15, 0])],
source_ranges: vec![SourceRange::new(14, 15, ModuleId::default())],
})
);
@ -2966,7 +2872,7 @@ let notPipeSub = 1 |> identity(!%))";
parse_execute(code11).await.unwrap_err().downcast::<KclError>().unwrap(),
KclError::Syntax(KclErrorDetails {
message: "Unexpected token: |>".to_owned(),
source_ranges: vec![SourceRange([54, 56, 0])],
source_ranges: vec![SourceRange::new(54, 56, ModuleId::default())],
})
);
@ -3010,10 +2916,10 @@ test([0, 0])
"#;
let result = parse_execute(ast).await;
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
r#"undefined value: KclErrorDetails { source_ranges: [SourceRange([10, 34, 0])], message: "Result of user-defined function test is undefined" }"#.to_owned()
);
assert!(result
.unwrap_err()
.to_string()
.contains("Result of user-defined function test is undefined"),);
}
#[tokio::test(flavor = "multi_thread")]
@ -3129,7 +3035,7 @@ let w = f() + f()
vec![req_param("x")],
vec![],
Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![SourceRange([0, 0, 0])],
source_ranges: vec![SourceRange::default()],
message: "Expected 1 arguments, got 0".to_owned(),
})),
),
@ -3147,7 +3053,7 @@ let w = f() + f()
vec![req_param("x"), opt_param("y")],
vec![],
Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![SourceRange([0, 0, 0])],
source_ranges: vec![SourceRange::default()],
message: "Expected 1-2 arguments, got 0".to_owned(),
})),
),
@ -3174,7 +3080,7 @@ let w = f() + f()
vec![req_param("x"), opt_param("y")],
vec![mem(1), mem(2), mem(3)],
Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![SourceRange([0, 0, 0])],
source_ranges: vec![SourceRange::default()],
message: "Expected 1-2 arguments, got 3".to_owned(),
})),
),

View File

@ -5,6 +5,7 @@ use anyhow::Result;
use crate::{
errors::{KclError, KclErrorDetails},
fs::FileSystem,
SourceRange,
};
#[derive(Debug, Clone)]
@ -27,7 +28,7 @@ impl FileSystem for FileManager {
async fn read<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<Vec<u8>, KclError> {
tokio::fs::read(&path).await.map_err(|e| {
KclError::Engine(KclErrorDetails {
@ -40,7 +41,7 @@ impl FileSystem for FileManager {
async fn read_to_string<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<String, KclError> {
tokio::fs::read_to_string(&path).await.map_err(|e| {
KclError::Engine(KclErrorDetails {
@ -53,7 +54,7 @@ impl FileSystem for FileManager {
async fn exists<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<bool, crate::errors::KclError> {
tokio::fs::metadata(&path).await.map(|_| true).or_else(|e| {
if e.kind() == std::io::ErrorKind::NotFound {
@ -70,7 +71,7 @@ impl FileSystem for FileManager {
async fn get_all_files<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<Vec<std::path::PathBuf>, crate::errors::KclError> {
let mut files = vec![];
let mut stack = vec![path.as_ref().to_path_buf()];

View File

@ -1,5 +1,9 @@
//! Functions for interacting with files on a machine.
use anyhow::Result;
use crate::SourceRange;
#[cfg(not(target_arch = "wasm32"))]
pub mod local;
#[cfg(not(target_arch = "wasm32"))]
@ -9,7 +13,6 @@ pub use local::FileManager;
#[cfg(not(test))]
pub mod wasm;
use anyhow::Result;
#[cfg(target_arch = "wasm32")]
#[cfg(not(test))]
pub use wasm::FileManager;
@ -20,27 +23,27 @@ pub trait FileSystem: Clone {
async fn read<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<Vec<u8>, crate::errors::KclError>;
/// Read a file from the local file system.
async fn read_to_string<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<String, crate::errors::KclError>;
/// Check if a file exists on the local file system.
async fn exists<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<bool, crate::errors::KclError>;
/// Get all the files in a directory recursively.
async fn get_all_files<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<Vec<std::path::PathBuf>, crate::errors::KclError>;
}

View File

@ -7,6 +7,7 @@ use crate::{
errors::{KclError, KclErrorDetails},
fs::FileSystem,
wasm::JsFuture,
SourceRange,
};
#[wasm_bindgen(module = "/../../lang/std/fileSystemManager.ts")]
@ -43,7 +44,7 @@ impl FileSystem for FileManager {
async fn read<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<Vec<u8>, KclError> {
let promise = self
.manager
@ -81,7 +82,7 @@ impl FileSystem for FileManager {
async fn read_to_string<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<String, KclError> {
let bytes = self.read(path, source_range).await?;
let string = String::from_utf8(bytes).map_err(|e| {
@ -97,7 +98,7 @@ impl FileSystem for FileManager {
async fn exists<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<bool, crate::errors::KclError> {
let promise = self
.manager
@ -139,7 +140,7 @@ impl FileSystem for FileManager {
async fn get_all_files<P: AsRef<std::path::Path> + std::marker::Send + std::marker::Sync>(
&self,
path: P,
source_range: crate::executor::SourceRange,
source_range: SourceRange,
) -> Result<Vec<std::path::PathBuf>, crate::errors::KclError> {
let promise = self
.manager

View File

@ -123,7 +123,7 @@ impl From<Vec<Box<Solid>>> for KclValue {
impl From<KclValue> for Vec<SourceRange> {
fn from(item: KclValue) -> Self {
match item {
KclValue::TagDeclarator(t) => vec![SourceRange([t.start, t.end, t.module_id.0 as usize])],
KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)],
KclValue::TagIdentifier(t) => to_vec_sr(&t.meta),
KclValue::Solid(e) => to_vec_sr(&e.meta),
KclValue::Solids { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(),
@ -152,7 +152,7 @@ fn to_vec_sr(meta: &[Metadata]) -> Vec<SourceRange> {
impl From<&KclValue> for Vec<SourceRange> {
fn from(item: &KclValue) -> Self {
match item {
KclValue::TagDeclarator(t) => vec![SourceRange([t.start, t.end, t.module_id.0 as usize])],
KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)],
KclValue::TagIdentifier(t) => to_vec_sr(&t.meta),
KclValue::Solid(e) => to_vec_sr(&e.meta),
KclValue::Solids { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(),

View File

@ -72,6 +72,7 @@ mod parser;
mod settings;
#[cfg(test)]
mod simulation_tests;
mod source_range;
mod std;
#[cfg(not(target_arch = "wasm32"))]
pub mod test_server;
@ -82,16 +83,17 @@ mod walk;
#[cfg(target_arch = "wasm32")]
mod wasm;
pub use ast::modify::modify_ast_for_sketch;
pub use ast::types::{FormatOptions, ModuleId};
pub use ast::{modify::modify_ast_for_sketch, types::FormatOptions};
pub use coredump::CoreDump;
pub use engine::{EngineManager, ExecutionKind};
pub use errors::{ConnectionError, ExecError, KclError};
pub use executor::{ExecState, ExecutorContext, ExecutorSettings, SourceRange};
pub use lsp::copilot::Backend as CopilotLspBackend;
pub use lsp::kcl::Backend as KclLspBackend;
pub use lsp::kcl::Server as KclLspServerSubCommand;
pub use executor::{ExecState, ExecutorContext, ExecutorSettings};
pub use lsp::{
copilot::Backend as CopilotLspBackend,
kcl::{Backend as KclLspBackend, Server as KclLspServerSubCommand},
};
pub use settings::types::{project::ProjectConfiguration, Configuration, UnitLength};
pub use source_range::{ModuleId, 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.
@ -101,9 +103,11 @@ pub mod exec {
#[cfg(target_arch = "wasm32")]
pub mod wasm_engine {
pub use crate::coredump::wasm::{CoreDumpManager, CoreDumper};
pub use crate::engine::conn_wasm::{EngineCommandManager, EngineConnection};
pub use crate::fs::wasm::FileSystemManager;
pub use crate::{
coredump::wasm::{CoreDumpManager, CoreDumper},
engine::conn_wasm::{EngineCommandManager, EngineConnection},
fs::wasm::FileSystemManager,
};
}
#[cfg(not(target_arch = "wasm32"))]
@ -115,9 +119,10 @@ pub mod std_utils {
pub use crate::std::utils::{get_tangential_arc_to_info, is_points_ccw_wasm, TangentialArcInfoInput};
}
use serde::{Deserialize, Serialize};
#[allow(unused_imports)]
use crate::log::{log, logln};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Program {

View File

@ -3,9 +3,9 @@ use convert_case::Casing;
use crate::{
ast::types::{ObjectProperty, VariableDeclarator},
executor::SourceRange,
lint::rule::{def_finding, Discovered, Finding},
walk::Node,
SourceRange,
};
def_finding!(

View File

@ -1,11 +1,13 @@
use std::collections::HashMap;
use anyhow::Result;
use crate::{
ast::types::{BinaryPart, Expr, LiteralValue, ObjectExpression, UnaryOperator},
executor::SourceRange,
lint::rule::{def_finding, Discovered, Finding},
walk::Node,
SourceRange,
};
use anyhow::Result;
use std::collections::HashMap;
def_finding!(
Z0003,

View File

@ -5,10 +5,10 @@ use anyhow::Result;
use crate::{
ast::types::{CallExpression, NodeRef},
docs::StdLibFn,
executor::SourceRange,
lint::rule::{def_finding, Discovered, Finding},
std::{FunctionKind, StdLib},
walk::Node,
SourceRange,
};
def_finding!(

View File

@ -3,7 +3,7 @@ use schemars::JsonSchema;
use serde::Serialize;
use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity};
use crate::{executor::SourceRange, lsp::IntoDiagnostic, walk::Node};
use crate::{lsp::IntoDiagnostic, walk::Node, SourceRange};
/// Check the provided AST for any found rule violations.
///

View File

@ -1,7 +1,8 @@
#![allow(dead_code)]
use std::env;
#[cfg(feature = "dhat-heap")]
use dhat::{HeapStats, Profiler};
use std::env;
use web_time::Instant;
const LOG_ENV_VAR: &str = "ZOO_LOG";

View File

@ -27,10 +27,12 @@ use tower_lsp::{
use crate::lsp::{
backend::Backend as _,
copilot::cache::CopilotCache,
copilot::types::{
CopilotAcceptCompletionParams, CopilotCompletionResponse, CopilotCompletionTelemetry, CopilotEditorInfo,
CopilotLspCompletionParams, CopilotRejectCompletionParams, DocParams,
copilot::{
cache::CopilotCache,
types::{
CopilotAcceptCompletionParams, CopilotCompletionResponse, CopilotCompletionTelemetry, CopilotEditorInfo,
CopilotLspCompletionParams, CopilotRejectCompletionParams, DocParams,
},
},
};

View File

@ -41,11 +41,11 @@ use tower_lsp::{
};
use crate::{
ast::types::{Expr, ModuleId, Node, VariableKind},
ast::types::{Expr, Node, VariableKind},
lsp::{backend::Backend as _, util::IntoDiagnostic},
parser::PIPE_OPERATOR,
token::TokenType,
ExecState, Program, SourceRange,
ExecState, ModuleId, Program, SourceRange,
};
lazy_static::lazy_static! {

View File

@ -1,9 +1,9 @@
use parser_impl::ParseContext;
use crate::{
ast::types::{ModuleId, Node, Program},
ast::types::{Node, Program},
errors::{KclError, KclErrorDetails},
executor::SourceRange,
source_range::{ModuleId, SourceRange},
token::{Token, TokenType},
};

View File

@ -1,7 +1,7 @@
use crate::{
ast::types::{BinaryExpression, BinaryOperator, BinaryPart, Node},
errors::{KclError, KclErrorDetails},
executor::SourceRange,
SourceRange,
};
/// Parses a list of tokens (in infix order, i.e. as the user typed them)
@ -66,7 +66,7 @@ fn source_range(tokens: &[BinaryExpressionToken]) -> Vec<SourceRange> {
})
.collect();
match (sources.first(), sources.last()) {
(Some((start, _, module_id)), Some((_, end, _))) => vec![SourceRange([*start, *end, module_id.as_usize()])],
(Some((start, _, module_id)), Some((_, end, _))) => vec![SourceRange::new(*start, *end, *module_id)],
_ => Vec::new(),
}
}
@ -126,7 +126,7 @@ impl From<BinaryOperator> for BinaryExpressionToken {
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::types::{Literal, ModuleId};
use crate::{ast::types::Literal, source_range::ModuleId};
#[test]
fn parse_and_evaluate() {

View File

@ -20,12 +20,12 @@ use crate::{
},
docs::StdLibFn,
errors::{KclError, KclErrorDetails},
executor::SourceRange,
parser::{
math::BinaryExpressionToken, parser_impl::error::ContextError, PIPE_OPERATOR, PIPE_SUBSTITUTION_OPERATOR,
},
token::{Token, TokenType},
unparser::ExprContext,
SourceRange,
};
pub(crate) mod error;
@ -1605,7 +1605,7 @@ fn declaration(i: TokenSlice) -> PResult<BoxNode<VariableDeclaration>> {
let ctxt_end = val.as_ref().map(|e| e.end()).unwrap_or(t.end);
ParseContext::warn(ParseError::with_suggestion(
t.as_source_range(),
Some(SourceRange([id.start, ctxt_end, module_id.as_usize()])),
Some(SourceRange::new(id.start, ctxt_end, module_id)),
"Unnecessary `=` in function declaration",
Some(""),
));
@ -1622,7 +1622,7 @@ fn declaration(i: TokenSlice) -> PResult<BoxNode<VariableDeclaration>> {
// Check the 'if' direction:
if matches!(val, Expr::FunctionExpression(_)) {
return Err(KclError::Syntax(KclErrorDetails {
source_ranges: vec![SourceRange([start, dec_end, module_id.as_usize()])],
source_ranges: vec![SourceRange::new(start, dec_end, module_id)],
message: format!("Expected a `fn` variable kind, found: `{}`", kind),
}));
}
@ -2287,7 +2287,10 @@ mod tests {
use pretty_assertions::assert_eq;
use super::*;
use crate::ast::types::{BodyItem, Expr, ModuleId, VariableKind};
use crate::{
ast::types::{BodyItem, Expr, VariableKind},
ModuleId,
};
fn assert_reserved(word: &str) {
// Try to use it as a variable name.
@ -2345,7 +2348,10 @@ mod tests {
let tokens = crate::token::lexer("|", ModuleId::default()).unwrap();
let err: super::error::ErrorKind = program.parse(&tokens).unwrap_err().into();
let err = err.unwrap_parse_error();
assert_eq!(vec![err.source_range], vec![SourceRange([0, 1, 0])]);
assert_eq!(
vec![err.source_range],
vec![SourceRange::new(0, 1, ModuleId::default())]
);
assert_eq!(err.message, "Unexpected token: |");
}
@ -2969,7 +2975,10 @@ const mySk1 = startSketchAt([0, 0])"#;
let err = function_decl.parse(&tokens).unwrap_err().into_inner();
let cause = err.cause.unwrap();
// This is the token `let`
assert_eq!(cause.source_ranges(), vec![SourceRange([1, 4, 2])]);
assert_eq!(
cause.source_ranges(),
vec![SourceRange::new(1, 4, ModuleId::from_usize(2))]
);
assert_eq!(cause.message(), "Cannot assign a variable to a reserved keyword: let");
}
@ -3289,7 +3298,8 @@ const mySk1 = startSketchAt([0, 0])"#;
let result = crate::parser::top_level_parse(p);
let err = &result.unwrap_errs()[0];
assert_eq!(err.message, msg);
assert_eq!(&err.source_range.0[..2], &src);
assert_eq!(err.source_range.start(), src[0]);
assert_eq!(err.source_range.end(), src[1]);
}
#[track_caller]
@ -3412,7 +3422,8 @@ const secondExtrude = startSketchOn('XY')
// TODO: Better errors when program cannot tokenize.
// https://github.com/KittyCAD/modeling-app/issues/696
assert_eq!(details.message, "found unknown token 'ޜ'");
assert_eq!(&details.source_ranges[0].0[..2], &[1, 2]);
assert_eq!(details.source_ranges[0].start(), 1);
assert_eq!(details.source_ranges[0].end(), 2);
}
#[test]
@ -3889,6 +3900,7 @@ int(42.3)"#;
#[cfg(test)]
mod snapshot_math_tests {
use super::*;
use crate::ModuleId;
// This macro generates a test function with the given function name.
// The macro takes a KCL program, ensures it tokenizes and parses, then compares
@ -3897,7 +3909,7 @@ mod snapshot_math_tests {
($func_name:ident, $test_kcl_program:expr) => {
#[test]
fn $func_name() {
let module_id = crate::ast::types::ModuleId::default();
let module_id = ModuleId::default();
let tokens = crate::token::lexer($test_kcl_program, module_id).unwrap();
ParseContext::init();
@ -3927,6 +3939,7 @@ mod snapshot_math_tests {
#[cfg(test)]
mod snapshot_tests {
use super::*;
use crate::ModuleId;
// This macro generates a test function with the given function name.
// The macro takes a KCL program, ensures it tokenizes and parses, then compares
@ -3935,7 +3948,7 @@ mod snapshot_tests {
($func_name:ident, $test_kcl_program:expr) => {
#[test]
fn $func_name() {
let module_id = crate::ast::types::ModuleId::default();
let module_id = ModuleId::default();
let tokens = crate::token::lexer($test_kcl_program, module_id).unwrap();
print_tokens(&tokens);
ParseContext::init();

View File

@ -2,8 +2,8 @@ use winnow::{error::StrContext, stream::Stream};
use crate::{
errors::{KclError, KclErrorDetails},
executor::SourceRange,
token::Token,
SourceRange,
};
/// Accumulate context while backtracking errors

View File

@ -1,8 +1,9 @@
use insta::rounded_redaction;
use crate::{
ast::types::{ModuleId, Node, Program},
ast::types::{Node, Program},
errors::KclError,
source_range::ModuleId,
};
/// Deserialize the data from a snapshot.

View File

@ -0,0 +1,132 @@
use databake::*;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use tower_lsp::lsp_types::{Position as LspPosition, Range as LspRange};
/// Identifier of a source file. Uses a u32 to keep the size small.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize, ts_rs::TS, JsonSchema, Bake)]
#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
#[databake(path = kcl_lib::ast::types)]
#[ts(export)]
pub struct ModuleId(u32);
impl ModuleId {
pub fn from_usize(id: usize) -> Self {
Self(u32::try_from(id).expect("module ID should fit in a u32"))
}
pub fn as_usize(&self) -> usize {
usize::try_from(self.0).expect("module ID should fit in a usize")
}
/// Top-level file is the one being executed.
/// Represented by module ID of 0, i.e. the default value.
pub fn is_top_level(&self) -> bool {
*self == Self::default()
}
}
// TODO Serialization and Python bindings expose the implementation of source ranges.
#[derive(Debug, Default, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema, Hash, Eq)]
#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
#[ts(export, as = "TsSourceRange")]
pub struct SourceRange([usize; 3]);
#[derive(Debug, Default, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema, Hash, Eq)]
struct TsSourceRange(#[ts(type = "[number, number]")] [usize; 2]);
impl From<[usize; 3]> for SourceRange {
fn from(value: [usize; 3]) -> Self {
Self(value)
}
}
impl From<SourceRange> for TsSourceRange {
fn from(value: SourceRange) -> Self {
Self([value.start(), value.end()])
}
}
impl From<&SourceRange> for miette::SourceSpan {
fn from(source_range: &SourceRange) -> Self {
let length = source_range.end() - source_range.start();
let start = miette::SourceOffset::from(source_range.start());
Self::new(start, length)
}
}
impl From<SourceRange> for miette::SourceSpan {
fn from(source_range: SourceRange) -> Self {
Self::from(&source_range)
}
}
impl SourceRange {
/// Create a new source range.
pub fn new(start: usize, end: usize, module_id: ModuleId) -> Self {
Self([start, end, module_id.as_usize()])
}
/// A source range that doesn't correspond to any source code.
pub fn synthetic() -> Self {
Self::default()
}
/// Get the start of the range.
pub fn start(&self) -> usize {
self.0[0]
}
/// Get the end of the range.
pub fn end(&self) -> usize {
self.0[1]
}
/// Get the module ID of the range.
pub fn module_id(&self) -> ModuleId {
ModuleId::from_usize(self.0[2])
}
/// Check if the range contains a position.
pub fn contains(&self, pos: usize) -> bool {
pos >= self.start() && pos <= self.end()
}
pub fn start_to_lsp_position(&self, code: &str) -> LspPosition {
// Calculate the line and column of the error from the source range.
// Lines are zero indexed in vscode so we need to subtract 1.
let mut line = code.get(..self.start()).unwrap_or_default().lines().count();
if line > 0 {
line = line.saturating_sub(1);
}
let column = code[..self.start()].lines().last().map(|l| l.len()).unwrap_or_default();
LspPosition {
line: line as u32,
character: column as u32,
}
}
pub fn end_to_lsp_position(&self, code: &str) -> LspPosition {
let lines = code.get(..self.end()).unwrap_or_default().lines();
if lines.clone().count() == 0 {
return LspPosition { line: 0, character: 0 };
}
// Calculate the line and column of the error from the source range.
// Lines are zero indexed in vscode so we need to subtract 1.
let line = lines.clone().count() - 1;
let column = lines.last().map(|l| l.len()).unwrap_or_default();
LspPosition {
line: line as u32,
character: column as u32,
}
}
pub fn to_lsp_range(&self, code: &str) -> LspRange {
let start = self.start_to_lsp_position(code);
let end = self.end_to_lsp_position(code);
LspRange { start, end }
}
}

View File

@ -4,19 +4,20 @@ use anyhow::Result;
use kcmc::{websocket::OkWebSocketResponseData, ModelingCmd};
use kittycad_modeling_cmds as kcmc;
use super::shapes::PolygonType;
use crate::{
ast::types::TagNode,
errors::{KclError, KclErrorDetails},
executor::{
ExecState, ExecutorContext, ExtrudeSurface, KclValue, Metadata, Sketch, SketchSet, SketchSurface, Solid,
SolidSet, SourceRange, TagIdentifier,
SolidSet, TagIdentifier,
},
kcl_value::KclObjectFields,
source_range::SourceRange,
std::{shapes::SketchOrSurface, sketch::FaceTag, FnAsArg},
ModuleId,
};
use super::shapes::PolygonType;
#[derive(Debug, Clone)]
pub struct Arg {
/// The evaluated argument.
@ -1221,7 +1222,6 @@ impl<'a> FromKclValue<'a> for crate::executor::GeoMeta {
let obj = arg.as_object()?;
let_field_of!(obj, id);
let_field_of!(obj, source_range "sourceRange");
let source_range = SourceRange(source_range);
Some(Self {
id,
metadata: Metadata { source_range },
@ -1314,9 +1314,22 @@ impl_from_kcl_for_vec!(super::fillet::EdgeReference);
impl_from_kcl_for_vec!(ExtrudeSurface);
impl_from_kcl_for_vec!(Sketch);
impl<'a> FromKclValue<'a> for crate::executor::SourceRange {
impl<'a> FromKclValue<'a> for SourceRange {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
FromKclValue::from_kcl_val(arg).map(crate::executor::SourceRange)
let KclValue::Array { value, meta: _ } = arg else {
return None;
};
if value.len() != 3 {
return None;
}
let v0 = value.first()?;
let v1 = value.get(1)?;
let v2 = value.get(2)?;
Some(SourceRange::new(
v0.as_usize()?,
v1.as_usize()?,
ModuleId::from_usize(v2.as_usize()?),
))
}
}

View File

@ -6,8 +6,9 @@ use super::{
};
use crate::{
errors::{KclError, KclErrorDetails},
executor::{ExecState, KclValue, SourceRange},
executor::{ExecState, KclValue},
function_param::FunctionParam,
source_range::SourceRange,
};
/// Apply a function to each element of an array.

View File

@ -16,18 +16,16 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use super::args::Arg;
use crate::{
errors::{KclError, KclErrorDetails},
executor::{
ExecState, Geometries, Geometry, KclValue, Point2d, Point3d, Sketch, SketchSet, Solid, SolidSet, SourceRange,
},
executor::{ExecState, Geometries, Geometry, KclValue, Point2d, Point3d, Sketch, SketchSet, Solid, SolidSet},
function_param::FunctionParam,
kcl_value::KclObjectFields,
std::Args,
SourceRange,
};
use super::args::Arg;
const MUST_HAVE_ONE_INSTANCE: &str = "There must be at least 1 instance of your geometry";
/// Data for a linear pattern on a 2D sketch.

View File

@ -12,8 +12,8 @@ use parse_display::{Display, FromStr};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::ast::types::TagNode;
use crate::{
ast::types::TagNode,
errors::{KclError, KclErrorDetails},
executor::{
BasePath, ExecState, Face, GeoMeta, KclValue, Path, Plane, Point2d, Point3d, Sketch, SketchSet, SketchSurface,
@ -2250,7 +2250,10 @@ mod tests {
use pretty_assertions::assert_eq;
use crate::{executor::TagIdentifier, std::sketch::calculate_circle_center, std::sketch::PlaneData};
use crate::{
executor::TagIdentifier,
std::sketch::{calculate_circle_center, PlaneData},
};
#[test]
fn test_deserialize_plane_data() {

View File

@ -4,7 +4,8 @@ use kittycad_modeling_cmds::shared::Angle;
use crate::{
errors::{KclError, KclErrorDetails},
executor::{Point2d, SourceRange},
executor::Point2d,
source_range::SourceRange,
};
/// Get the angle between these points
@ -234,7 +235,7 @@ mod tests {
use pretty_assertions::assert_eq;
use super::{get_x_component, get_y_component, Angle};
use crate::executor::SourceRange;
use crate::SourceRange;
static EACH_QUAD: [(i32, [i32; 2]); 12] = [
(-315, [1, 1]),
@ -354,7 +355,7 @@ mod tests {
super::Point2d { x: -1.0, y: 1.0 },
super::Point2d { x: -1.0, y: 0.0 },
1.0,
SourceRange(Default::default()),
SourceRange::default(),
)
.unwrap();
assert_eq!(angle_start.to_degrees().round(), 0.0);
@ -365,7 +366,7 @@ mod tests {
super::Point2d { x: -2.0, y: 0.0 },
super::Point2d { x: -1.0, y: 0.0 },
1.0,
SourceRange(Default::default()),
SourceRange::default(),
)
.unwrap();
assert_eq!(angle_start.to_degrees().round(), 0.0);
@ -376,7 +377,7 @@ mod tests {
super::Point2d { x: -20.0, y: 0.0 },
super::Point2d { x: -10.0, y: 0.0 },
10.0,
SourceRange(Default::default()),
SourceRange::default(),
)
.unwrap();
assert_eq!(angle_start.to_degrees().round(), 0.0);
@ -387,7 +388,7 @@ mod tests {
super::Point2d { x: 5.0, y: 5.0 },
super::Point2d { x: 10.0, y: -10.0 },
10.0,
SourceRange(Default::default()),
SourceRange::default(),
);
if let Err(err) = result {

View File

@ -8,9 +8,9 @@ use tower_lsp::lsp_types::SemanticTokenType;
use winnow::{error::ParseError, stream::ContainsToken};
use crate::{
ast::types::{ItemVisibility, ModuleId, VariableKind},
ast::types::{ItemVisibility, VariableKind},
errors::KclError,
executor::SourceRange,
source_range::{ModuleId, SourceRange},
};
mod tokeniser;
@ -207,7 +207,7 @@ impl Token {
}
pub fn as_source_range(&self) -> SourceRange {
SourceRange([self.start, self.end, self.module_id.as_usize()])
SourceRange::new(self.start, self.end, self.module_id)
}
pub fn as_source_ranges(&self) -> Vec<SourceRange> {
@ -241,13 +241,13 @@ impl Token {
impl From<Token> for SourceRange {
fn from(token: Token) -> Self {
Self([token.start, token.end, token.module_id.as_usize()])
Self::new(token.start, token.end, token.module_id)
}
}
impl From<&Token> for SourceRange {
fn from(token: &Token) -> Self {
Self([token.start, token.end, token.module_id.as_usize()])
Self::new(token.start, token.end, token.module_id)
}
}
@ -267,7 +267,7 @@ impl From<ParseError<Input<'_>, winnow::error::ContextError>> for KclError {
// the end of input (input.len()) on eof errors.
return KclError::Lexical(crate::errors::KclErrorDetails {
source_ranges: vec![SourceRange([offset, offset, module_id.as_usize()])],
source_ranges: vec![SourceRange::new(offset, offset, module_id)],
message: "unexpected EOF while parsing".to_string(),
});
}
@ -278,7 +278,7 @@ impl From<ParseError<Input<'_>, winnow::error::ContextError>> for KclError {
// TODO: Add the Winnow parser context to the error.
// See https://github.com/KittyCAD/modeling-app/issues/784
KclError::Lexical(crate::errors::KclErrorDetails {
source_ranges: vec![SourceRange([offset, offset + 1, module_id.as_usize()])],
source_ranges: vec![SourceRange::new(offset, offset + 1, module_id)],
message: format!("found unknown token '{}'", bad_token),
})
}

View File

@ -11,7 +11,7 @@ use winnow::{
};
use crate::{
ast::types::ModuleId,
source_range::ModuleId,
token::{Token, TokenType},
};

View File

@ -683,7 +683,7 @@ mod tests {
use pretty_assertions::assert_eq;
use super::*;
use crate::ast::types::{FormatOptions, ModuleId};
use crate::{ast::types::FormatOptions, source_range::ModuleId};
#[test]
fn test_recast_if_else_if_same() {

View File

@ -1,6 +1,6 @@
use crate::{
ast::types::{self, NodeRef},
executor::SourceRange,
source_range::SourceRange,
};
/// The "Node" type wraps all the AST elements we're able to find in a KCL
@ -65,9 +65,9 @@ impl From<&Node<'_>> for SourceRange {
Node::UnaryExpression(n) => SourceRange::from(*n),
Node::Parameter(p) => SourceRange::from(&p.identifier),
Node::ObjectProperty(n) => SourceRange::from(*n),
Node::MemberObject(m) => SourceRange([m.start(), m.end(), m.module_id().as_usize()]),
Node::MemberObject(m) => SourceRange::new(m.start(), m.end(), m.module_id()),
Node::IfExpression(n) => SourceRange::from(*n),
Node::LiteralIdentifier(l) => SourceRange([l.start(), l.end(), l.module_id().as_usize()]),
Node::LiteralIdentifier(l) => SourceRange::new(l.start(), l.end(), l.module_id()),
}
}
}