Compare commits

..

1 Commits

Author SHA1 Message Date
7711bf768c Iterate over binary ops (instead of recursing) 2025-04-08 17:15:33 -05:00
29 changed files with 2300 additions and 629 deletions

View File

@ -5,18 +5,11 @@ all: install build check
# INSTALL
ifeq ($(OS),Windows_NT)
export WINDOWS := true
ifndef MSYSTEM
export POWERSHELL := true
endif
endif
ifdef WINDOWS
CARGO ?= $(USERPROFILE)/.cargo/bin/cargo.exe
WASM_PACK ?= $(USERPROFILE)/.cargo/bin/wasm-pack.exe
CARGO ?= ~/.cargo/bin/cargo.exe
WASM_PACK ?= ~/.cargo/bin/wasm-pack.exe
else
CARGO ?= ~/.cargo/bin/cargo
WASM_PACK ?= ~/.cargo/bin/wasm-pack
CARGO ?= ~/.cargo/bin/cargo
WASM_PACK ?= ~/.cargo/bin/wasm-pack
endif
.PHONY: install
@ -24,21 +17,21 @@ install: node_modules/.yarn-integrity $(CARGO) $(WASM_PACK) ## Install dependenc
node_modules/.yarn-integrity: package.json yarn.lock
yarn install
ifdef POWERSHELL
ifeq ($(OS),Windows_NT)
@ type nul > $@
else
@ touch $@
endif
$(CARGO):
ifdef WINDOWS
ifeq ($(OS),Windows_NT)
yarn install:rust:windows
else
yarn install:rust
endif
$(WASM_PACK):
ifdef WINDOWS
ifeq ($(OS),Windows_NT)
yarn install:wasm-pack:cargo
else
yarn install:wasm-pack:sh
@ -64,7 +57,7 @@ build-web: install public/kcl_wasm_lib_bg.wasm build/index.html
build-desktop: install public/kcl_wasm_lib_bg.wasm .vite/build/main.js
public/kcl_wasm_lib_bg.wasm: $(CARGO_SOURCES) $(RUST_SOURCES)
ifdef WINDOWS
ifeq ($(OS),Windows_NT)
yarn build:wasm:dev:windows
else
yarn build:wasm:dev
@ -147,8 +140,8 @@ endif
.PHONY: clean
clean: ## Delete all artifacts
ifdef POWERSHELL
git clean --force -d -x --exclude=.env* --exclude=**/*.env
ifeq ($(OS),Windows_NT)
git clean --force -d -X
else
rm -rf .vite/ build/
rm -rf trace.zip playwright-report/ test-results/
@ -159,7 +152,7 @@ endif
.PHONY: help
help: install
ifdef POWERSHELL
ifeq ($(OS),Windows_NT)
@ powershell -Command "Get-Content $(MAKEFILE_LIST) | Select-String -Pattern '^[^\s]+:.*##\s.*$$' | ForEach-Object { $$line = $$_.Line -split ':.*?##\s+'; Write-Host -NoNewline $$line[0].PadRight(30) -ForegroundColor Cyan; Write-Host $$line[1] }"
else
@ grep -E '^[^[:space:]]+:.*## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

View File

@ -78,7 +78,7 @@ assertEqual(sum, 6, 0.00001, "1 + 2 + 3 summed is 6")
```js
// Declare a function that sketches a decagon.
fn decagon(radius) {
// Each side of the decagon is turned this many radians from the previous angle.
// Each side of the decagon is turned this many degrees from the previous angle.
stepAngle = 1 / 10 * TAU
// Start the decagon sketch at this point.

View File

@ -9,15 +9,7 @@ Create a helix.
```js
helix(
revolutions: number(_),
angleStart: number(deg),
ccw?: bool,
radius?: number(mm),
axis?: Axis3d | Edge,
length?: number(mm),
cylinder?: Solid,
): Helix
helix(revolutions: number(_), angleStart: number(deg), ccw?: bool, radius?: number(mm), axis?: Axis3d | Edge, length?: number(mm), cylinder?: Solid): Helix
```

View File

@ -6,14 +6,10 @@ layout: manual
Convert polar/sphere (azimuth, elevation, distance) coordinates to
cartesian (x/y/z grid) coordinates.
Convert polar/sphere (azimuth, elevation, distance) coordinates tocartesian (x/y/z grid) coordinates.
```js
polar(
angle: number(deg),
length: number(mm),
): [number(mm); 2]
polar(angle: number(deg), length: number(mm)): [number(mm); 2]
```

File diff suppressed because one or more lines are too long

View File

@ -18,14 +18,7 @@ You can provide more than one sketch to revolve, and they will all be
revolved around the same axis.
```js
revolve(
@sketches: [Sketch; 1+],
axis: Axis2d | Edge,
angle?: number(deg),
tolerance?: number(mm),
tagStart?: tag,
tagEnd?: tag,
): Solid
revolve(@sketches: [Sketch; 1+], axis: Axis2d | Edge, angle?: number(deg), tolerance?: number(mm), tagStart?: tag, tagEnd?: tag): Solid
```

View File

@ -6,16 +6,10 @@ layout: manual
Construct a 2-dimensional circle, of the specified radius, centered at
the provided (x, y) origin point.
Construct a 2-dimensional circle, of the specified radius, centered atthe provided (x, y) origin point.
```js
circle(
@sketch_or_surface: Sketch | Plane | Face,
center: Point2d,
radius: number,
tag?: tag,
): Sketch
circle(@sketch_or_surface: Sketch | Plane | Face, center: Point2d, radius: number, tag?: tag): Sketch
```

View File

@ -11,10 +11,7 @@ Only works on unclosed sketches for now.
Mirror occurs around a local sketch axis rather than a global axis.
```js
mirror2d(
@sketches: [Sketch; 1+],
axis: Axis2d | Edge,
): Sketch
mirror2d(@sketches: [Sketch; 1+], axis: Axis2d | Edge): Sketch
```

View File

@ -235702,7 +235702,7 @@
"examples": [
"// This function adds two numbers.\nfn add(a, b) {\n return a + b\n}\n\n// This function adds an array of numbers.\n// It uses the `reduce` function, to call the `add` function on every\n// element of the `arr` parameter. The starting value is 0.\nfn sum(arr) {\n return reduce(arr, 0, add)\n}\n\n/* The above is basically like this pseudo-code:\nfn sum(arr):\n sumSoFar = 0\n for i in arr:\n sumSoFar = add(sumSoFar, i)\n return sumSoFar */\n\n// We use `assertEqual` to check that our `sum` function gives the\n// expected result. It's good to check your work!\nassertEqual(sum([1, 2, 3]), 6, 0.00001, \"1 + 2 + 3 summed is 6\")",
"// This example works just like the previous example above, but it uses\n// an anonymous `add` function as its parameter, instead of declaring a\n// named function outside.\narr = [1, 2, 3]\nsum = reduce(arr, 0, fn(i, result_so_far) {\n return i + result_so_far\n})\n\n// We use `assertEqual` to check that our `sum` function gives the\n// expected result. It's good to check your work!\nassertEqual(sum, 6, 0.00001, \"1 + 2 + 3 summed is 6\")",
"// Declare a function that sketches a decagon.\nfn decagon(radius) {\n // Each side of the decagon is turned this many radians from the previous angle.\n stepAngle = 1 / 10 * TAU\n\n // Start the decagon sketch at this point.\n startOfDecagonSketch = startSketchOn(XY)\n |> startProfileAt([cos(0) * radius, sin(0) * radius], %)\n\n // Use a `reduce` to draw the remaining decagon sides.\n // For each number in the array 1..10, run the given function,\n // which takes a partially-sketched decagon and adds one more edge to it.\n fullDecagon = reduce([1..10], startOfDecagonSketch, fn(i, partialDecagon) {\n // Draw one edge of the decagon.\n x = cos(stepAngle * i) * radius\n y = sin(stepAngle * i) * radius\n return line(partialDecagon, end = [x, y])\n })\n\n return fullDecagon\n}\n\n/* The `decagon` above is basically like this pseudo-code:\nfn decagon(radius):\n stepAngle = (1/10) * TAU\n plane = startSketchOn('XY')\n startOfDecagonSketch = startProfileAt([(cos(0)*radius), (sin(0) * radius)], plane)\n\n // Here's the reduce part.\n partialDecagon = startOfDecagonSketch\n for i in [1..10]:\n x = cos(stepAngle * i) * radius\n y = sin(stepAngle * i) * radius\n partialDecagon = line(partialDecagon, end = [x, y])\n fullDecagon = partialDecagon // it's now full\n return fullDecagon */\n\n// Use the `decagon` function declared above, to sketch a decagon with radius 5.\ndecagon(5.0)\n |> close()"
"// Declare a function that sketches a decagon.\nfn decagon(radius) {\n // Each side of the decagon is turned this many degrees from the previous angle.\n stepAngle = 1 / 10 * TAU\n\n // Start the decagon sketch at this point.\n startOfDecagonSketch = startSketchOn(XY)\n |> startProfileAt([cos(0) * radius, sin(0) * radius], %)\n\n // Use a `reduce` to draw the remaining decagon sides.\n // For each number in the array 1..10, run the given function,\n // which takes a partially-sketched decagon and adds one more edge to it.\n fullDecagon = reduce([1..10], startOfDecagonSketch, fn(i, partialDecagon) {\n // Draw one edge of the decagon.\n x = cos(stepAngle * i) * radius\n y = sin(stepAngle * i) * radius\n return line(partialDecagon, end = [x, y])\n })\n\n return fullDecagon\n}\n\n/* The `decagon` above is basically like this pseudo-code:\nfn decagon(radius):\n stepAngle = (1/10) * TAU\n plane = startSketchOn('XY')\n startOfDecagonSketch = startProfileAt([(cos(0)*radius), (sin(0) * radius)], plane)\n\n // Here's the reduce part.\n partialDecagon = startOfDecagonSketch\n for i in [1..10]:\n x = cos(stepAngle * i) * radius\n y = sin(stepAngle * i) * radius\n partialDecagon = line(partialDecagon, end = [x, y])\n fullDecagon = partialDecagon // it's now full\n return fullDecagon */\n\n// Use the `decagon` function declared above, to sketch a decagon with radius 5.\ndecagon(5.0)\n |> close()"
]
},
{

View File

@ -49,9 +49,7 @@ export class SceneFixture {
constructor(page: Page) {
this.page = page
this.streamWrapper = page.getByTestId('stream')
this.networkToggleConnected = page
.getByTestId('network-toggle-ok')
.or(page.getByTestId('network-toggle-other'))
this.networkToggleConnected = page.getByTestId('network-toggle-ok')
this.startEditSketchBtn = page
.getByRole('button', { name: 'Start Sketch' })
.or(page.getByRole('button', { name: 'Edit Sketch' }))

View File

@ -61,7 +61,6 @@ function tomlStringOverWriteNamedViewUuids(toml: string): string {
}
test.describe('Named view tests', () => {
test.skip() // TODO: Jace is working on these
test('Verify project.toml is not created', async ({ page }, testInfo) => {
// Create project and load it
const projectName = 'named-views'

View File

@ -7,7 +7,6 @@ import { expect, test } from '@e2e/playwright/zoo-test'
* Test file menu actions that trigger something in the frontend
*/
test.describe('Native file menu', { tag: ['@electron'] }, () => {
test.skip() // TODO: Reimplement native file menu tests
test.describe('Home page', () => {
test.describe('File role', () => {
test('Home.File.Create project', async ({ tronApp, cmdBar, page }) => {

View File

@ -1,4 +1,4 @@
use std::{collections::HashSet, fmt, str::FromStr};
use std::{collections::HashSet, str::FromStr};
use regex::Regex;
use tower_lsp::lsp_types::{
@ -389,23 +389,21 @@ impl FnData {
pub fn fn_signature(&self) -> String {
let mut signature = String::new();
if self.args.is_empty() {
signature.push_str("()");
} else if self.args.len() == 1 {
signature.push('(');
signature.push_str(&self.args[0].to_string());
signature.push(')');
} else {
signature.push('(');
for a in &self.args {
signature.push_str("\n ");
signature.push_str(&a.to_string());
signature.push(',');
signature.push('(');
for (i, arg) in self.args.iter().enumerate() {
if i > 0 {
signature.push_str(", ");
}
match &arg.kind {
ArgKind::Special => signature.push_str(&format!("@{}", arg.name)),
ArgKind::Labelled(false) => signature.push_str(&arg.name),
ArgKind::Labelled(true) => signature.push_str(&format!("{}?", arg.name)),
}
if let Some(ty) = &arg.ty {
signature.push_str(&format!(": {ty}"));
}
signature.push('\n');
signature.push(')');
}
signature.push(')');
if let Some(ty) = &self.return_type {
signature.push_str(&format!(": {ty}"));
}
@ -517,20 +515,6 @@ pub struct ArgData {
pub docs: Option<String>,
}
impl fmt::Display for ArgData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.kind {
ArgKind::Special => write!(f, "@{}", self.name)?,
ArgKind::Labelled(false) => f.write_str(&self.name)?,
ArgKind::Labelled(true) => write!(f, "{}?", self.name)?,
}
if let Some(ty) = &self.ty {
write!(f, ": {ty}")?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ArgKind {
Special,
@ -782,8 +766,8 @@ trait ApplyMeta {
description = summary;
summary = None;
let d = description.as_mut().unwrap();
d.push('\n');
d.push_str(l);
d.push('\n');
}
continue;
}

View File

@ -918,165 +918,193 @@ impl Node<MemberExpression> {
impl Node<BinaryExpression> {
#[async_recursion]
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
let left_value = self.left.get_result(exec_state, ctx).await?;
let right_value = self.right.get_result(exec_state, ctx).await?;
let mut meta = left_value.metadata();
meta.extend(right_value.metadata());
// First check if we are doing string concatenation.
if self.operator == BinaryOperator::Add {
if let (KclValue::String { value: left, meta: _ }, KclValue::String { value: right, meta: _ }) =
(&left_value, &right_value)
{
return Ok(KclValue::String {
value: format!("{}{}", left, right),
meta,
});
}
let mut partial = self.right.get_result(exec_state, ctx).await?;
let mut next = &self.left;
let source_range = self.into();
// Don't recurse through a big chain of binary operations, iterate instead.
while let BinaryPart::BinaryExpression(next_binary_expr) = next {
let metadata = partial.metadata();
partial = do_binary_op(
partial,
next_binary_expr.right.get_result(exec_state, ctx).await?,
next_binary_expr.operator,
metadata,
source_range,
exec_state,
ctx,
)
.await?;
next = &next_binary_expr.left;
}
// Then check if we have solids.
if self.operator == BinaryOperator::Add || self.operator == BinaryOperator::Or {
if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
let args = crate::std::Args::new(Default::default(), self.into(), ctx.clone(), None);
let result =
crate::std::csg::inner_union(vec![*left.clone(), *right.clone()], exec_state, args).await?;
return Ok(result.into());
}
} else if self.operator == BinaryOperator::Sub {
// Check if we have solids.
if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
let args = crate::std::Args::new(Default::default(), self.into(), ctx.clone(), None);
let result =
crate::std::csg::inner_subtract(vec![*left.clone()], vec![*right.clone()], exec_state, args)
.await?;
return Ok(result.into());
}
} else if self.operator == BinaryOperator::And {
// Check if we have solids.
if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
let args = crate::std::Args::new(Default::default(), self.into(), ctx.clone(), None);
let result =
crate::std::csg::inner_intersect(vec![*left.clone(), *right.clone()], exec_state, args).await?;
return Ok(result.into());
}
}
// Check if we are doing logical operations on booleans.
if self.operator == BinaryOperator::Or || self.operator == BinaryOperator::And {
let KclValue::Bool {
value: left_value,
meta: _,
} = left_value
else {
return Err(KclError::Semantic(KclErrorDetails {
message: format!(
"Cannot apply logical operator to non-boolean value: {}",
left_value.human_friendly_type()
),
source_ranges: vec![self.left.clone().into()],
}));
};
let KclValue::Bool {
value: right_value,
meta: _,
} = right_value
else {
return Err(KclError::Semantic(KclErrorDetails {
message: format!(
"Cannot apply logical operator to non-boolean value: {}",
right_value.human_friendly_type()
),
source_ranges: vec![self.right.clone().into()],
}));
};
let raw_value = match self.operator {
BinaryOperator::Or => left_value || right_value,
BinaryOperator::And => left_value && right_value,
_ => unreachable!(),
};
return Ok(KclValue::Bool { value: raw_value, meta });
}
let left = number_as_f64(&left_value, self.left.clone().into())?;
let right = number_as_f64(&right_value, self.right.clone().into())?;
let value = match self.operator {
BinaryOperator::Add => {
let (l, r, ty) = NumericType::combine_eq(left, right);
self.warn_on_unknown(&ty, "Adding", exec_state);
KclValue::Number { value: l + r, meta, ty }
}
BinaryOperator::Sub => {
let (l, r, ty) = NumericType::combine_eq(left, right);
self.warn_on_unknown(&ty, "Subtracting", exec_state);
KclValue::Number { value: l - r, meta, ty }
}
BinaryOperator::Mul => {
let (l, r, ty) = NumericType::combine_mul(left, right);
self.warn_on_unknown(&ty, "Multiplying", exec_state);
KclValue::Number { value: l * r, meta, ty }
}
BinaryOperator::Div => {
let (l, r, ty) = NumericType::combine_div(left, right);
self.warn_on_unknown(&ty, "Dividing", exec_state);
KclValue::Number { value: l / r, meta, ty }
}
BinaryOperator::Mod => {
let (l, r, ty) = NumericType::combine_div(left, right);
self.warn_on_unknown(&ty, "Modulo of", exec_state);
KclValue::Number { value: l % r, meta, ty }
}
BinaryOperator::Pow => KclValue::Number {
value: left.n.powf(right.n),
meta,
ty: NumericType::Unknown,
},
BinaryOperator::Neq => {
let (l, r, ty) = NumericType::combine_eq(left, right);
self.warn_on_unknown(&ty, "Comparing", exec_state);
KclValue::Bool { value: l != r, meta }
}
BinaryOperator::Gt => {
let (l, r, ty) = NumericType::combine_eq(left, right);
self.warn_on_unknown(&ty, "Comparing", exec_state);
KclValue::Bool { value: l > r, meta }
}
BinaryOperator::Gte => {
let (l, r, ty) = NumericType::combine_eq(left, right);
self.warn_on_unknown(&ty, "Comparing", exec_state);
KclValue::Bool { value: l >= r, meta }
}
BinaryOperator::Lt => {
let (l, r, ty) = NumericType::combine_eq(left, right);
self.warn_on_unknown(&ty, "Comparing", exec_state);
KclValue::Bool { value: l < r, meta }
}
BinaryOperator::Lte => {
let (l, r, ty) = NumericType::combine_eq(left, right);
self.warn_on_unknown(&ty, "Comparing", exec_state);
KclValue::Bool { value: l <= r, meta }
}
BinaryOperator::Eq => {
let (l, r, ty) = NumericType::combine_eq(left, right);
self.warn_on_unknown(&ty, "Comparing", exec_state);
KclValue::Bool { value: l == r, meta }
}
BinaryOperator::And | BinaryOperator::Or => unreachable!(),
};
let next_value = next.get_result(exec_state, ctx).await?;
let mut meta = partial.metadata();
meta.extend(next_value.metadata());
let value = do_binary_op(partial, next_value, self.operator, meta, self.into(), exec_state, ctx).await?;
Ok(value)
}
}
fn warn_on_unknown(&self, ty: &NumericType, verb: &str, exec_state: &mut ExecState) {
if *CHECK_NUMERIC_TYPES && ty == &NumericType::Unknown {
// TODO suggest how to fix this
exec_state.warn(CompilationError::err(
self.as_source_range(),
format!("{} numbers which have unknown or incompatible units.", verb),
));
fn warn_on_unknown(source_range: SourceRange, ty: &NumericType, verb: &str, exec_state: &mut ExecState) {
if *CHECK_NUMERIC_TYPES && ty == &NumericType::Unknown {
// TODO suggest how to fix this
exec_state.warn(CompilationError::err(
source_range,
format!("{} numbers which have unknown or incompatible units.", verb),
));
}
}
async fn do_binary_op(
left_value: KclValue,
right_value: KclValue,
operator: BinaryOperator,
meta: Vec<Metadata>,
source_range: SourceRange,
exec_state: &mut ExecState,
ctx: &ExecutorContext,
) -> Result<KclValue, KclError> {
// First check if we are doing string concatenation.
if operator == BinaryOperator::Add {
if let (KclValue::String { value: left, meta: _ }, KclValue::String { value: right, meta: _ }) =
(&left_value, &right_value)
{
return Ok(KclValue::String {
value: format!("{}{}", left, right),
meta,
});
}
}
// Then check if we have solids.
if operator == BinaryOperator::Add || operator == BinaryOperator::Or {
if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
let args = crate::std::Args::new(Default::default(), source_range, ctx.clone(), None);
let result = crate::std::csg::inner_union(vec![*left.clone(), *right.clone()], exec_state, args).await?;
return Ok(result.into());
}
} else if operator == BinaryOperator::Sub {
// Check if we have solids.
if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
let args = crate::std::Args::new(Default::default(), source_range, ctx.clone(), None);
let result =
crate::std::csg::inner_subtract(vec![*left.clone()], vec![*right.clone()], exec_state, args).await?;
return Ok(result.into());
}
} else if operator == BinaryOperator::And {
// Check if we have solids.
if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
let args = crate::std::Args::new(Default::default(), source_range, ctx.clone(), None);
let result =
crate::std::csg::inner_intersect(vec![*left.clone(), *right.clone()], exec_state, args).await?;
return Ok(result.into());
}
}
// Check if we are doing logical operations on booleans.
if operator == BinaryOperator::Or || operator == BinaryOperator::And {
let KclValue::Bool {
value: left_value,
meta: _,
} = left_value
else {
return Err(KclError::Semantic(KclErrorDetails {
message: format!(
"Cannot apply logical operator to non-boolean value: {}",
left_value.human_friendly_type()
),
source_ranges: left_value.into(),
}));
};
let KclValue::Bool {
value: right_value,
meta: _,
} = right_value
else {
return Err(KclError::Semantic(KclErrorDetails {
message: format!(
"Cannot apply logical operator to non-boolean value: {}",
right_value.human_friendly_type()
),
source_ranges: right_value.into(),
}));
};
let raw_value = match operator {
BinaryOperator::Or => left_value || right_value,
BinaryOperator::And => left_value && right_value,
_ => unreachable!(),
};
return Ok(KclValue::Bool { value: raw_value, meta });
}
let left = number_as_f64(&left_value, (&left_value).into())?;
let right = number_as_f64(&right_value, (&left_value).into())?;
let value = match operator {
BinaryOperator::Add => {
let (l, r, ty) = NumericType::combine_eq(left, right);
warn_on_unknown(source_range, &ty, "Adding", exec_state);
KclValue::Number { value: l + r, meta, ty }
}
BinaryOperator::Sub => {
let (l, r, ty) = NumericType::combine_eq(left, right);
warn_on_unknown(source_range, &ty, "Subtracting", exec_state);
KclValue::Number { value: l - r, meta, ty }
}
BinaryOperator::Mul => {
let (l, r, ty) = NumericType::combine_mul(left, right);
warn_on_unknown(source_range, &ty, "Multiplying", exec_state);
KclValue::Number { value: l * r, meta, ty }
}
BinaryOperator::Div => {
let (l, r, ty) = NumericType::combine_div(left, right);
warn_on_unknown(source_range, &ty, "Dividing", exec_state);
KclValue::Number { value: l / r, meta, ty }
}
BinaryOperator::Mod => {
let (l, r, ty) = NumericType::combine_div(left, right);
warn_on_unknown(source_range, &ty, "Modulo of", exec_state);
KclValue::Number { value: l % r, meta, ty }
}
BinaryOperator::Pow => KclValue::Number {
value: left.n.powf(right.n),
meta,
ty: NumericType::Unknown,
},
BinaryOperator::Neq => {
let (l, r, ty) = NumericType::combine_eq(left, right);
warn_on_unknown(source_range, &ty, "Comparing", exec_state);
KclValue::Bool { value: l != r, meta }
}
BinaryOperator::Gt => {
let (l, r, ty) = NumericType::combine_eq(left, right);
warn_on_unknown(source_range, &ty, "Comparing", exec_state);
KclValue::Bool { value: l > r, meta }
}
BinaryOperator::Gte => {
let (l, r, ty) = NumericType::combine_eq(left, right);
warn_on_unknown(source_range, &ty, "Comparing", exec_state);
KclValue::Bool { value: l >= r, meta }
}
BinaryOperator::Lt => {
let (l, r, ty) = NumericType::combine_eq(left, right);
warn_on_unknown(source_range, &ty, "Comparing", exec_state);
KclValue::Bool { value: l < r, meta }
}
BinaryOperator::Lte => {
let (l, r, ty) = NumericType::combine_eq(left, right);
warn_on_unknown(source_range, &ty, "Comparing", exec_state);
KclValue::Bool { value: l <= r, meta }
}
BinaryOperator::Eq => {
let (l, r, ty) = NumericType::combine_eq(left, right);
warn_on_unknown(source_range, &ty, "Comparing", exec_state);
KclValue::Bool { value: l == r, meta }
}
BinaryOperator::And | BinaryOperator::Or => unreachable!(),
};
Ok(value)
}
impl Node<UnaryExpression> {

View File

@ -360,6 +360,15 @@ impl KclValue {
result
}
/// Put the number into a KCL value.
pub const fn from_number(f: f64, meta: Vec<Metadata>) -> Self {
Self::Number {
value: f,
meta,
ty: NumericType::Unknown,
}
}
pub const fn from_number_with_type(f: f64, ty: NumericType, meta: Vec<Metadata>) -> Self {
Self::Number { value: f, meta, ty }
}

View File

@ -19,7 +19,7 @@ use crate::{
};
lazy_static::lazy_static! {
pub(crate) static ref CHECK_NUMERIC_TYPES: bool = {
pub(super) static ref CHECK_NUMERIC_TYPES: bool = {
let env_var = std::env::var("ZOO_NUM_TYS");
let Ok(env_var) = env_var else {
return false;
@ -416,80 +416,6 @@ impl NumericType {
}
}
pub fn combine_eq_array(input: &[TyF64]) -> (Vec<f64>, NumericType) {
use NumericType::*;
let mut result = input.iter().map(|t| t.n).collect();
let mut ty = Any;
// Invariant mismatch is true => ty is Known
let mut mismatch = false;
for i in input {
if i.ty == Any || ty == i.ty {
continue;
}
match (&ty, &i.ty) {
(Any, t) => {
ty = t.clone();
}
(_, Unknown) | (Default { .. }, Default { .. }) => return (result, Unknown),
// Known types and compatible, but needs adjustment.
(Known(UnitType::Length(_)), Known(UnitType::Length(_)))
| (Known(UnitType::Angle(_)), Known(UnitType::Angle(_))) => {
mismatch = true;
}
// Known but incompatible.
(Known(_), Known(_)) => return (result, Unknown),
// Known and unknown, no adjustment for counting numbers.
(Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
ty = Known(UnitType::Count);
}
(Known(UnitType::Length(l1)), Default { len: l2, .. }) => {
mismatch |= l1 != l2;
}
(Known(UnitType::Angle(a1)), Default { angle: a2, .. }) => {
mismatch |= a1 != a2;
}
(Default { len: l1, .. }, Known(UnitType::Length(l2))) => {
mismatch |= l1 != l2;
ty = Known(UnitType::Length(*l2));
}
(Default { angle: a1, .. }, Known(UnitType::Angle(a2))) => {
mismatch |= a1 != a2;
ty = Known(UnitType::Angle(*a2));
}
(Unknown, _) | (_, Any) => unreachable!(),
}
}
if !mismatch {
return (result, ty);
}
result = result
.into_iter()
.zip(input)
.map(|(n, i)| match (&ty, &i.ty) {
(Known(UnitType::Length(l1)), Known(UnitType::Length(l2)) | Default { len: l2, .. }) => {
l2.adjust_to(n, *l1)
}
(Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2)) | Default { angle: a2, .. }) => {
a2.adjust_to(n, *a1)
}
_ => unreachable!(),
})
.collect();
(result, ty)
}
/// Combine two types for multiplication-like operations.
pub fn combine_mul(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
use NumericType::*;
@ -695,7 +621,7 @@ pub enum UnitLen {
impl UnitLen {
fn adjust_to(self, value: f64, to: UnitLen) -> f64 {
if !*CHECK_NUMERIC_TYPES || self == to {
if self == to {
return value;
}
@ -808,11 +734,6 @@ impl UnitAngle {
fn adjust_to(self, value: f64, to: UnitAngle) -> f64 {
use std::f64::consts::PI;
use UnitAngle::*;
if !*CHECK_NUMERIC_TYPES {
return value;
}
match (self, to) {
(Degrees, Degrees) => value,
(Degrees, Radians) => (value / 180.0) * PI,
@ -1926,16 +1847,11 @@ n = 10inch / 2mm
o = 3mm / 3
p = 3_ / 4
q = 4inch / 2_
r = min(0, 3, 42)
s = min(0, 3mm, -42)
t = min(100, 3in, 142mm)
u = min(3rad, 4in)
"#;
let result = parse_execute(program).await.unwrap();
if *CHECK_NUMERIC_TYPES {
assert_eq!(result.exec_state.errors().len(), 3);
assert_eq!(result.exec_state.errors().len(), 2);
} else {
assert!(result.exec_state.errors().is_empty());
}
@ -1945,9 +1861,7 @@ u = min(3rad, 4in)
assert_value_and_type("c", &result, 13.0, NumericType::mm());
assert_value_and_type("d", &result, 13.0, NumericType::mm());
assert_value_and_type("e", &result, 13.0, NumericType::mm());
if *CHECK_NUMERIC_TYPES {
assert_value_and_type("f", &result, 5.0, NumericType::mm());
}
assert_value_and_type("f", &result, 5.0, NumericType::mm());
assert_value_and_type("g", &result, 20.0, NumericType::default());
assert_value_and_type("h", &result, 20.0, NumericType::mm());
@ -1957,30 +1871,9 @@ u = min(3rad, 4in)
assert_value_and_type("l", &result, 0.0, NumericType::count());
assert_value_and_type("m", &result, 2.0, NumericType::count());
if *CHECK_NUMERIC_TYPES {
assert_value_and_type("n", &result, 127.0, NumericType::count());
}
assert_value_and_type("n", &result, 127.0, NumericType::count());
assert_value_and_type("o", &result, 1.0, NumericType::mm());
assert_value_and_type("p", &result, 1.0, NumericType::count());
assert_value_and_type("q", &result, 2.0, NumericType::Known(UnitType::Length(UnitLen::Inches)));
assert_value_and_type("r", &result, 0.0, NumericType::default());
assert_value_and_type("s", &result, -42.0, NumericType::mm());
assert_value_and_type("t", &result, 3.0, NumericType::Known(UnitType::Length(UnitLen::Inches)));
assert_value_and_type("u", &result, 3.0, NumericType::Unknown);
}
#[tokio::test(flavor = "multi_thread")]
async fn bad_typed_arithmetic() {
let program = r#"
a = 1rad
b = 180 / PI * a + 360
"#;
let result = parse_execute(program).await.unwrap();
assert_value_and_type("a", &result, 1.0, NumericType::radians());
// TODO type is not ideal
assert_value_and_type("b", &result, 417.0, NumericType::radians());
}
}

View File

@ -2832,7 +2832,7 @@ impl BinaryExpression {
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, FromStr, Display)]
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS, JsonSchema, FromStr, Display)]
#[ts(export)]
#[serde(rename_all = "snake_case")]
#[display(style = "snake_case")]

View File

@ -523,6 +523,15 @@ impl Args {
})
}
pub(super) fn make_user_val_from_f64(&self, f: f64) -> KclValue {
KclValue::from_number(
f,
vec![Metadata {
source_range: self.source_range,
}],
)
}
pub(super) fn make_user_val_from_f64_with_type(&self, f: TyF64) -> KclValue {
KclValue::from_number_with_type(
f.n,

View File

@ -133,7 +133,7 @@ pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// ```no_run
/// // Declare a function that sketches a decagon.
/// fn decagon(radius) {
/// // Each side of the decagon is turned this many radians from the previous angle.
/// // Each side of the decagon is turned this many degrees from the previous angle.
/// stepAngle = (1/10) * TAU
///
/// // Start the decagon sketch at this point.

View File

@ -6,31 +6,18 @@ use kcl_derive_docs::stdlib;
use super::args::FromArgs;
use crate::{
errors::{KclError, KclErrorDetails},
execution::{
types::{self, NumericType},
ExecState, KclValue,
},
execution::{types::NumericType, ExecState, KclValue},
std::args::{Args, TyF64},
CompilationError,
};
/// Compute the remainder after dividing `num` by `div`.
/// If `num` is negative, the result will be too.
pub async fn rem(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let n: TyF64 = args.get_unlabeled_kw_arg("number to divide")?;
let d: TyF64 = args.get_kw_arg("divisor")?;
let (n, d, ty) = NumericType::combine_div(n, d);
if *types::CHECK_NUMERIC_TYPES && ty == NumericType::Unknown {
// TODO suggest how to fix this
exec_state.warn(CompilationError::err(
args.source_range,
"Remainder of numbers which have unknown or incompatible units.",
));
}
pub async fn rem(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let n = args.get_unlabeled_kw_arg("number to divide")?;
let d = args.get_kw_arg("divisor")?;
let remainder = inner_rem(n, d);
Ok(args.make_user_val_from_f64_with_type(TyF64::new(remainder, ty)))
Ok(args.make_user_val_from_f64(remainder))
}
/// Compute the remainder after dividing `num` by `div`.
@ -256,19 +243,11 @@ fn inner_ceil(num: f64) -> Result<f64, KclError> {
}
/// Compute the minimum of the given arguments.
pub async fn min(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let nums = args.get_number_array_with_types()?;
let (nums, ty) = NumericType::combine_eq_array(&nums);
if *types::CHECK_NUMERIC_TYPES && ty == NumericType::Unknown {
// TODO suggest how to fix this
exec_state.warn(CompilationError::err(
args.source_range,
"Calling `min` on numbers which have unknown or incompatible units.",
));
}
pub async fn min(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let nums = args.get_number_array()?;
let result = inner_min(nums);
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, ty)))
Ok(args.make_user_val_from_f64(result))
}
/// Compute the minimum of the given arguments.
@ -301,19 +280,11 @@ fn inner_min(args: Vec<f64>) -> f64 {
}
/// Compute the maximum of the given arguments.
pub async fn max(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let nums = args.get_number_array_with_types()?;
let (nums, ty) = NumericType::combine_eq_array(&nums);
if *types::CHECK_NUMERIC_TYPES && ty == NumericType::Unknown {
// TODO suggest how to fix this
exec_state.warn(CompilationError::err(
args.source_range,
"Calling `max` on numbers which have unknown or incompatible units.",
));
}
pub async fn max(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let nums = args.get_number_array()?;
let result = inner_max(nums);
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, ty)))
Ok(args.make_user_val_from_f64(result))
}
/// Compute the maximum of the given arguments.

View File

@ -69,7 +69,7 @@ export fn cos(@num: number(rad)): number(_) {}
/// |> startProfileAt([0, 0], %)
/// |> angledLine({
/// angle = 50,
/// length = 15 / sin(toRadians(135)),
/// length = 15 / sin(toDegrees(135)),
/// }, %)
/// |> yLine(endAbsolute = 0)
/// |> close()

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,6 @@ fn f(i) {
return i * 2
}
x = f(0) + f(1) + f(2) + f(3) + f(4) + f(5) + f(6) + f(7) + f(8) + f(9) + f(10) + f(11) + f(12) + f(13) + f(14) + f(15) + f(16) + f(17) + f(18) + f(19) + f(20) + f(21) + f(22) + f(23) + f(24) + f(25) + f(26) + f(27) + f(28) + f(29) + f(30) + f(31) + f(32) + f(33) + f(34) + f(35) + f(36) + f(37) + f(38) + f(39) + f(40) + f(41) + f(42) + f(43) + f(44) + f(45) + f(46) + f(47) + f(48) + f(49) + f(50) + f(51) + f(52) + f(53) + f(54) + f(55) + f(56) + f(57) + f(58) + f(59) + f(60) + f(61) + f(62) + f(63) + f(64) + f(65) + f(66) + f(67) + f(68) + f(69) + f(70)
x = f(0) + f(1) + f(2) + f(3) + f(4) + f(5) + f(6) + f(7) + f(8) + f(9) + f(10) + f(11) + f(12) + f(13) + f(14) + f(15) + f(16) + f(17) + f(18) + f(19) + f(20) + f(21) + f(22) + f(23) + f(24) + f(25) + f(26) + f(27) + f(28) + f(29) + f(30) + f(31) + f(32) + f(33) + f(34) + f(35) + f(36) + f(37) + f(38) + f(39) + f(40) + f(41) + f(42) + f(43) + f(44) + f(45) + f(46) + f(47) + f(48) + f(49) + f(50) + f(51) + f(52) + f(53) + f(54) + f(55) + f(56) + f(57) + f(58) + f(59) + f(60) + f(61) + f(62) + f(63) + f(64) + f(65) + f(66) + f(67) + f(68) + f(69) + f(70) + f(71) + f(72) + f(73) + f(74) + f(75) + f(76) + f(77) + f(78) + f(79) + f(80) + f(81) + f(82) + f(83) + f(84) + f(85) + f(86) + f(87) + f(88) + f(89) + f(90) + f(91) + f(92) + f(93) + f(94) + f(95) + f(96) + f(97) + f(98) + f(99) + f(100)
assertEqual(x, 4970, 0.1, "Big sum")
assertEqual(x, 10100, 0.1, "Big sum")

View File

@ -1278,6 +1278,546 @@ description: Operations executed add_lots.kcl
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": "f",
"functionSourceRange": [
4,
26,
0
],
"unlabeledArg": null,
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": "f",
"functionSourceRange": [
4,
26,
0
],
"unlabeledArg": null,
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": "f",
"functionSourceRange": [
4,
26,
0
],
"unlabeledArg": null,
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": "f",
"functionSourceRange": [
4,
26,
0
],
"unlabeledArg": null,
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": "f",
"functionSourceRange": [
4,
26,
0
],
"unlabeledArg": null,
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": "f",
"functionSourceRange": [
4,
26,
0
],
"unlabeledArg": null,
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": "f",
"functionSourceRange": [
4,
26,
0
],
"unlabeledArg": null,
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": "f",
"functionSourceRange": [
4,
26,
0
],
"unlabeledArg": null,
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": "f",
"functionSourceRange": [
4,
26,
0
],
"unlabeledArg": null,
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": "f",
"functionSourceRange": [
4,
26,
0
],
"unlabeledArg": null,
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": "f",
"functionSourceRange": [
4,
26,
0
],
"unlabeledArg": null,
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": "f",
"functionSourceRange": [
4,
26,
0
],
"unlabeledArg": null,
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": "f",
"functionSourceRange": [
4,
26,
0
],
"unlabeledArg": null,
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": "f",
"functionSourceRange": [
4,
26,
0
],
"unlabeledArg": null,
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": "f",
"functionSourceRange": [
4,
26,
0
],
"unlabeledArg": null,
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": "f",
"functionSourceRange": [
4,
26,
0
],
"unlabeledArg": null,
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": "f",
"functionSourceRange": [
4,
26,
0
],
"unlabeledArg": null,
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": "f",
"functionSourceRange": [
4,
26,
0
],
"unlabeledArg": null,
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": "f",
"functionSourceRange": [
4,
26,
0
],
"unlabeledArg": null,
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": "f",
"functionSourceRange": [
4,
26,
0
],
"unlabeledArg": null,
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": "f",
"functionSourceRange": [
4,
26,
0
],
"unlabeledArg": null,
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": "f",
"functionSourceRange": [
4,
26,
0
],
"unlabeledArg": null,
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": "f",
"functionSourceRange": [
4,
26,
0
],
"unlabeledArg": null,
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": "f",
"functionSourceRange": [
4,
26,
0
],
"unlabeledArg": null,
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": "f",
"functionSourceRange": [
4,
26,
0
],
"unlabeledArg": null,
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": "f",
"functionSourceRange": [
4,
26,
0
],
"unlabeledArg": null,
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": "f",
"functionSourceRange": [
4,
26,
0
],
"unlabeledArg": null,
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": "f",
"functionSourceRange": [
4,
26,
0
],
"unlabeledArg": null,
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": "f",
"functionSourceRange": [
4,
26,
0
],
"unlabeledArg": null,
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "FunctionCall",
"name": "f",
"functionSourceRange": [
4,
26,
0
],
"unlabeledArg": null,
"labeledArgs": {}
},
"sourceRange": []
},
{
"type": "GroupEnd"
}

View File

@ -8,7 +8,7 @@ description: Variables in memory after executing add_lots.kcl
},
"x": {
"type": "Number",
"value": 4970.0,
"value": 10100.0,
"ty": {
"type": "Default",
"len": {

View File

@ -6,6 +6,6 @@ fn f(i) {
return i * 2
}
x = f(0) + f(1) + f(2) + f(3) + f(4) + f(5) + f(6) + f(7) + f(8) + f(9) + f(10) + f(11) + f(12) + f(13) + f(14) + f(15) + f(16) + f(17) + f(18) + f(19) + f(20) + f(21) + f(22) + f(23) + f(24) + f(25) + f(26) + f(27) + f(28) + f(29) + f(30) + f(31) + f(32) + f(33) + f(34) + f(35) + f(36) + f(37) + f(38) + f(39) + f(40) + f(41) + f(42) + f(43) + f(44) + f(45) + f(46) + f(47) + f(48) + f(49) + f(50) + f(51) + f(52) + f(53) + f(54) + f(55) + f(56) + f(57) + f(58) + f(59) + f(60) + f(61) + f(62) + f(63) + f(64) + f(65) + f(66) + f(67) + f(68) + f(69) + f(70)
x = f(0) + f(1) + f(2) + f(3) + f(4) + f(5) + f(6) + f(7) + f(8) + f(9) + f(10) + f(11) + f(12) + f(13) + f(14) + f(15) + f(16) + f(17) + f(18) + f(19) + f(20) + f(21) + f(22) + f(23) + f(24) + f(25) + f(26) + f(27) + f(28) + f(29) + f(30) + f(31) + f(32) + f(33) + f(34) + f(35) + f(36) + f(37) + f(38) + f(39) + f(40) + f(41) + f(42) + f(43) + f(44) + f(45) + f(46) + f(47) + f(48) + f(49) + f(50) + f(51) + f(52) + f(53) + f(54) + f(55) + f(56) + f(57) + f(58) + f(59) + f(60) + f(61) + f(62) + f(63) + f(64) + f(65) + f(66) + f(67) + f(68) + f(69) + f(70) + f(71) + f(72) + f(73) + f(74) + f(75) + f(76) + f(77) + f(78) + f(79) + f(80) + f(81) + f(82) + f(83) + f(84) + f(85) + f(86) + f(87) + f(88) + f(89) + f(90) + f(91) + f(92) + f(93) + f(94) + f(95) + f(96) + f(97) + f(98) + f(99) + f(100)
assertEqual(x, 4970, 0.1, "Big sum")
assertEqual(x, 10100, 0.1, "Big sum")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 63 KiB

View File

@ -27,31 +27,12 @@ function userCall(name: string): Operation {
sourceRange: defaultSourceRange(),
}
}
function userReturn(): Operation {
return {
type: 'GroupEnd',
}
}
function moduleBegin(name: string): Operation {
return {
type: 'GroupBegin',
group: {
type: 'ModuleInstance',
name,
moduleId: 0,
},
sourceRange: defaultSourceRange(),
}
}
function moduleEnd(): Operation {
return {
type: 'GroupEnd',
}
}
describe('operations filtering', () => {
it('drops stdlib operations inside a user-defined function call', async () => {
const operations = [
@ -84,25 +65,6 @@ describe('operations filtering', () => {
const actual = filterOperations(operations)
expect(actual).toEqual([stdlib('std1'), stdlib('std2'), stdlib('std3')])
})
it('does not drop module instances that contain no operations', async () => {
const operations = [
stdlib('std1'),
moduleBegin('foo'),
moduleEnd(),
stdlib('std2'),
moduleBegin('bar'),
moduleEnd(),
stdlib('std3'),
]
const actual = filterOperations(operations)
expect(actual).toEqual([
stdlib('std1'),
moduleBegin('foo'),
stdlib('std2'),
moduleBegin('bar'),
stdlib('std3'),
])
})
it('preserves user-defined function calls at the end of the list', async () => {
const operations = [stdlib('std1'), userCall('foo')]
const actual = filterOperations(operations)

View File

@ -1168,7 +1168,7 @@ export function filterOperations(operations: Operation[]): Operation[] {
* for use in the feature tree UI
*/
const operationFilters = [
isNotUserFunctionWithNoOperations,
isNotGroupWithNoOperations,
isNotInsideGroup,
isNotGroupEnd,
]
@ -1202,28 +1202,22 @@ function isNotInsideGroup(operations: Operation[]): Operation[] {
/**
* A filter to exclude GroupBegin operations and their corresponding GroupEnd
* that don't have any operations inside them from a list of operations, if it's
* a function call.
* that don't have any operations inside them from a list of operations.
*/
function isNotUserFunctionWithNoOperations(
operations: Operation[]
): Operation[] {
function isNotGroupWithNoOperations(operations: Operation[]): Operation[] {
return operations.filter((op, index) => {
if (
op.type === 'GroupBegin' &&
op.group.type === 'FunctionCall' &&
// If this is a "begin" at the end of the array, it's preserved.
// If this is a begin at the end of the array, it's preserved.
index < operations.length - 1 &&
operations[index + 1].type === 'GroupEnd'
)
return false
const previousOp = index > 0 ? operations[index - 1] : undefined
if (
op.type === 'GroupEnd' &&
// If this is an "end" at the beginning of the array, it's preserved.
previousOp !== undefined &&
previousOp.type === 'GroupBegin' &&
previousOp.group.type === 'FunctionCall'
// If this is an end at the beginning of the array, it's preserved.
index > 0 &&
operations[index - 1].type === 'GroupBegin'
)
return false