Compare commits
6 Commits
achalmers/
...
nightly-v2
Author | SHA1 | Date | |
---|---|---|---|
ae9d8be4e4 | |||
e4f73a6d5c | |||
a31fd608cf | |||
a98d5aa2fb | |||
4c6ef841bb | |||
997f539a8c |
31
Makefile
31
Makefile
@ -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}'
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
```
|
||||
|
||||
|
||||
|
@ -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
@ -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
|
||||
```
|
||||
|
||||
|
||||
|
@ -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
|
||||
```
|
||||
|
||||
|
||||
|
@ -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
|
||||
```
|
||||
|
||||
|
||||
|
@ -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()"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -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' }))
|
||||
|
@ -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'
|
||||
|
@ -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 }) => {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 }
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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 |
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
Reference in New Issue
Block a user