Compare commits

...

6 Commits

Author SHA1 Message Date
ae9d8be4e4 Don't apply numeric adjustments in arithmetic yet (#6222)
Don't apply numeric adjustments yet

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-04-09 14:16:58 +12:00
e4f73a6d5c Continue running tests with weak network (#6223) 2025-04-09 02:13:38 +00:00
a31fd608cf Improve hovers for KCL std lib fns (#6208)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-04-09 12:15:12 +12:00
a98d5aa2fb Fix to not filter out empty modules from the Feature Tree (#6224) 2025-04-08 23:56:27 +00:00
4c6ef841bb Handle PowerShell vs. Git Bash on Windows (#6207) 2025-04-08 23:47:56 +00:00
997f539a8c More numeric type propagations (#6221)
Last few numeric type propagations

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-04-09 11:46:54 +12:00
22 changed files with 299 additions and 82 deletions

View File

@ -5,33 +5,40 @@ all: install build check
# INSTALL
ifeq ($(OS),Windows_NT)
CARGO ?= ~/.cargo/bin/cargo.exe
WASM_PACK ?= ~/.cargo/bin/wasm-pack.exe
else
CARGO ?= ~/.cargo/bin/cargo
WASM_PACK ?= ~/.cargo/bin/wasm-pack
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
else
CARGO ?= ~/.cargo/bin/cargo
WASM_PACK ?= ~/.cargo/bin/wasm-pack
endif
.PHONY: install
install: node_modules/.yarn-integrity $(CARGO) $(WASM_PACK) ## Install dependencies
node_modules/.yarn-integrity: package.json yarn.lock
yarn install
ifeq ($(OS),Windows_NT)
ifdef POWERSHELL
@ type nul > $@
else
@ touch $@
endif
$(CARGO):
ifeq ($(OS),Windows_NT)
ifdef WINDOWS
yarn install:rust:windows
else
yarn install:rust
endif
$(WASM_PACK):
ifeq ($(OS),Windows_NT)
ifdef WINDOWS
yarn install:wasm-pack:cargo
else
yarn install:wasm-pack:sh
@ -57,7 +64,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)
ifeq ($(OS),Windows_NT)
ifdef WINDOWS
yarn build:wasm:dev:windows
else
yarn build:wasm:dev
@ -140,8 +147,8 @@ endif
.PHONY: clean
clean: ## Delete all artifacts
ifeq ($(OS),Windows_NT)
git clean --force -d -X
ifdef POWERSHELL
git clean --force -d -x --exclude=.env* --exclude=**/*.env
else
rm -rf .vite/ build/
rm -rf trace.zip playwright-report/ test-results/
@ -152,7 +159,7 @@ endif
.PHONY: help
help: install
ifeq ($(OS),Windows_NT)
ifdef POWERSHELL
@ 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 degrees from the previous angle.
// Each side of the decagon is turned this many radians from the previous angle.
stepAngle = 1 / 10 * TAU
// Start the decagon sketch at this point.

View File

@ -9,7 +9,15 @@ 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,10 +6,14 @@ layout: manual
Convert polar/sphere (azimuth, elevation, distance) coordinates tocartesian (x/y/z grid) coordinates.
Convert polar/sphere (azimuth, elevation, distance) coordinates to
cartesian (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,7 +18,14 @@ 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,10 +6,16 @@ layout: manual
Construct a 2-dimensional circle, of the specified radius, centered atthe provided (x, y) origin point.
Construct a 2-dimensional circle, of the specified radius, centered at
the 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,7 +11,10 @@ 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 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()"
"// 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()"
]
},
{

View File

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

View File

@ -61,6 +61,7 @@ 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,6 +7,7 @@ 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, str::FromStr};
use std::{collections::HashSet, fmt, str::FromStr};
use regex::Regex;
use tower_lsp::lsp_types::{
@ -389,21 +389,23 @@ impl FnData {
pub fn fn_signature(&self) -> String {
let mut signature = String::new();
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}"));
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('\n');
signature.push(')');
}
signature.push(')');
if let Some(ty) = &self.return_type {
signature.push_str(&format!(": {ty}"));
}
@ -515,6 +517,20 @@ 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,
@ -766,8 +782,8 @@ trait ApplyMeta {
description = summary;
summary = None;
let d = description.as_mut().unwrap();
d.push_str(l);
d.push('\n');
d.push_str(l);
}
continue;
}

View File

@ -360,15 +360,6 @@ 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(super) static ref CHECK_NUMERIC_TYPES: bool = {
pub(crate) 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,6 +416,80 @@ 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::*;
@ -621,7 +695,7 @@ pub enum UnitLen {
impl UnitLen {
fn adjust_to(self, value: f64, to: UnitLen) -> f64 {
if self == to {
if !*CHECK_NUMERIC_TYPES || self == to {
return value;
}
@ -734,6 +808,11 @@ 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,
@ -1847,11 +1926,16 @@ 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(), 2);
assert_eq!(result.exec_state.errors().len(), 3);
} else {
assert!(result.exec_state.errors().is_empty());
}
@ -1861,7 +1945,9 @@ q = 4inch / 2_
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());
assert_value_and_type("f", &result, 5.0, NumericType::mm());
if *CHECK_NUMERIC_TYPES {
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());
@ -1871,9 +1957,30 @@ q = 4inch / 2_
assert_value_and_type("l", &result, 0.0, NumericType::count());
assert_value_and_type("m", &result, 2.0, NumericType::count());
assert_value_and_type("n", &result, 127.0, NumericType::count());
if *CHECK_NUMERIC_TYPES {
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

@ -523,15 +523,6 @@ 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 degrees from the previous angle.
/// // Each side of the decagon is turned this many radians from the previous angle.
/// stepAngle = (1/10) * TAU
///
/// // Start the decagon sketch at this point.

View File

@ -6,18 +6,31 @@ use kcl_derive_docs::stdlib;
use super::args::FromArgs;
use crate::{
errors::{KclError, KclErrorDetails},
execution::{types::NumericType, ExecState, KclValue},
execution::{
types::{self, 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 = args.get_unlabeled_kw_arg("number to divide")?;
let d = args.get_kw_arg("divisor")?;
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.",
));
}
let remainder = inner_rem(n, d);
Ok(args.make_user_val_from_f64(remainder))
Ok(args.make_user_val_from_f64_with_type(TyF64::new(remainder, ty)))
}
/// Compute the remainder after dividing `num` by `div`.
@ -243,11 +256,19 @@ 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()?;
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.",
));
}
let result = inner_min(nums);
Ok(args.make_user_val_from_f64(result))
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, ty)))
}
/// Compute the minimum of the given arguments.
@ -280,11 +301,19 @@ 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()?;
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.",
));
}
let result = inner_max(nums);
Ok(args.make_user_val_from_f64(result))
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, ty)))
}
/// 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(toDegrees(135)),
/// length = 15 / sin(toRadians(135)),
/// }, %)
/// |> yLine(endAbsolute = 0)
/// |> close()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 58 KiB

View File

@ -27,12 +27,31 @@ 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 = [
@ -65,6 +84,25 @@ 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 = [
isNotGroupWithNoOperations,
isNotUserFunctionWithNoOperations,
isNotInsideGroup,
isNotGroupEnd,
]
@ -1202,22 +1202,28 @@ 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.
* that don't have any operations inside them from a list of operations, if it's
* a function call.
*/
function isNotGroupWithNoOperations(operations: Operation[]): Operation[] {
function isNotUserFunctionWithNoOperations(
operations: Operation[]
): Operation[] {
return operations.filter((op, index) => {
if (
op.type === 'GroupBegin' &&
// If this is a begin at the end of the array, it's preserved.
op.group.type === 'FunctionCall' &&
// 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.
index > 0 &&
operations[index - 1].type === 'GroupBegin'
// If this is an "end" at the beginning of the array, it's preserved.
previousOp !== undefined &&
previousOp.type === 'GroupBegin' &&
previousOp.group.type === 'FunctionCall'
)
return false