diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..f0dba102
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,48 @@
+# Shared editor settings. Language formatters remain authoritative for syntax.
+# Modern K&R:
+# - Opening braces stay on the same line.
+# - Block bodies stay expanded and readable.
+# - Keep exactly one blank line between function and method definitions.
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+indent_style = space
+indent_size = 4
+tab_width = 4
+trim_trailing_whitespace = true
+
+# Soft guide only. Language formatters remain responsible for wrapping.
+max_line_length = 100
+
+[*.rs]
+# Rust source layout.
+indent_style = space
+indent_size = 4
+tab_width = 4
+
+# Keep function/method definitions visually separated by one blank line.
+blank_lines_around_function = 1
+blank_lines_around_method = 1
+
+[*.{yml,yaml,json}]
+indent_size = 2
+
+[*.md]
+# Preserve Markdown's two-space hard line breaks.
+trim_trailing_whitespace = false
+
+[{Makefile,makefile,GNUmakefile,*.mk}]
+indent_style = tab
+indent_size = tab
+
+[tests/fixtures/**]
+# Fixture bytes may deliberately exercise whitespace and missing final newlines.
+trim_trailing_whitespace = false
+insert_final_newline = false
+charset = unset
+end_of_line = unset
+max_line_length = off
\ No newline at end of file
diff --git a/.gitattributes b/.gitattributes
index 31d9647c..e7277b55 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,4 +1,6 @@
*.wave linguist-language=Wave
tests/** linguist-detectable=false
+# Match rustfmt's Unix newline style even with core.autocrlf enabled on Windows.
+*.rs text eol=lf
# AST snapshots assert UTF-8 byte offsets, independently of checkout defaults.
tests/fixtures/ast/* text eol=lf
diff --git a/.gitignore b/.gitignore
index 26af3e59..ef57148a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -94,3 +94,30 @@ cmake-build-debug/
cmake-build-release/
.DS_Store
+
+# AI assistant instructions, configuration, and local state (at any depth)
+AGENTS.md
+AGENTS.override.md
+CLAUDE.md
+CLAUDE.local.md
+GEMINI.md
+.codex
+.agents
+.claude
+.cursor
+.cursorrules
+.cursorignore
+.windsurf
+.windsurfrules
+.continue
+.clinerules
+.roo
+.roomodes
+.aider.conf.yml
+.aiderignore
+.aider.chat.history.md
+.aider.input.history
+.github/copilot-instructions.md
+.github/instructions/
+.github/prompts/
+.github/agents/
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index a56fbe2b..965d2598 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -68,7 +68,7 @@ dependencies = [
[[package]]
name = "ir"
version = "0.1.0"
-source = "git+https://github.com/wavefnd/Whale.git?branch=master#4f45fdb53d07882932cc251856316206a3bb3947"
+source = "git+https://github.com/wavefnd/Whale.git?branch=master#020d2a4cf312123518ec75cc865eea1d257c5689"
dependencies = [
"whale-target",
]
@@ -238,4 +238,4 @@ dependencies = [
[[package]]
name = "whale-target"
version = "0.1.0"
-source = "git+https://github.com/wavefnd/Whale.git?branch=master#4f45fdb53d07882932cc251856316206a3bb3947"
+source = "git+https://github.com/wavefnd/Whale.git?branch=master#020d2a4cf312123518ec75cc865eea1d257c5689"
diff --git a/README.md b/README.md
index 52250bd7..12eb175a 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,9 @@
-
+
-
Wave
+---
A general-purpose programming language built as a modern evolution of C.
Native compilation and direct control, rebuilt for modern software and tooling.
@@ -97,14 +97,13 @@ which controls the C ABI boundary. `main` is always a private entry point, so
Linux and macOS:
```shell
-curl -fsSL https://wave-lang.dev/install.sh | bash -s -- latest
+curl -fsSL https://wave-lang.dev/install.sh | bash
```
Windows PowerShell:
```powershell
-irm https://wave-lang.dev/install.ps1 -OutFile install.ps1
-powershell -ExecutionPolicy Bypass -File .\install.ps1 -Latest
+irm https://wave-lang.dev/install.ps1 -OutFile install.ps1; powershell -ExecutionPolicy Bypass -File .\install.ps1
```
See the [installation guide](https://wave-lang.dev/docs/getting-started/install) for platform requirements and release selection.
diff --git a/build.rs b/build.rs
index 8d07ac06..890ea9e3 100644
--- a/build.rs
+++ b/build.rs
@@ -3,11 +3,9 @@ use std::{env, path::Path, process::Command};
fn git(args: &[&str]) -> Option
{
let result = Command::new("git").args(args).output().ok()?;
- result
- .status
- .success()
- .then(|| String::from_utf8_lossy(&result.stdout).trim().to_owned())
+ result.status.success().then(|| String::from_utf8_lossy(&result.stdout).trim().to_owned())
}
+
fn main() {
println!("cargo:rerun-if-env-changed=WAVE_STD_REVISION");
// Track both detached HEAD and branch ref changes, including worktrees.
@@ -32,9 +30,7 @@ fn main() {
// Do not accidentally record an enclosing, unrelated repository.
let root = git(&["rev-parse", "--show-toplevel"])?;
if Path::new(&root).canonicalize().ok()?
- != Path::new(&env::var("CARGO_MANIFEST_DIR").ok()?)
- .canonicalize()
- .ok()?
+ != Path::new(&env::var("CARGO_MANIFEST_DIR").ok()?).canonicalize().ok()?
{
return None;
}
diff --git a/front/error/src/error.rs b/front/error/src/error.rs
index 4304f182..2f46e6f8 100644
--- a/front/error/src/error.rs
+++ b/front/error/src/error.rs
@@ -285,12 +285,8 @@ impl WaveError {
("help", Value::optional_string(self.help.as_deref())),
("suggestions", Value::strings(&self.suggestions)),
]);
- wson::dumps(
- &Value::object([("error", error)]),
- wson::Format::Json,
- false,
- )
- .expect("diagnostic schema contains only shallow JSON values")
+ wson::dumps(&Value::object([("error", error)]), wson::Format::Json, false)
+ .expect("diagnostic schema contains only shallow JSON values")
}
pub fn display_auto(&self) {
@@ -305,10 +301,7 @@ impl WaveError {
pub fn stdlib_requires_vex(module: &str, file: &str, line: usize, column: usize) -> Self {
Self::new(
WaveErrorKind::VexIntegrationRequired(module.to_string()),
- format!(
- "standard library module 'std::{}' requires Vex package manager",
- module
- ),
+ format!("standard library module 'std::{}' requires Vex package manager", module),
file,
line,
column,
@@ -334,10 +327,7 @@ impl WaveError {
expected: expected.to_string(),
found: found.to_string(),
},
- format!(
- "mismatched types: expected `{}`, found `{}`",
- expected, found
- ),
+ format!("mismatched types: expected `{}`, found `{}`", expected, found),
file,
line,
column,
@@ -380,10 +370,8 @@ impl WaveError {
if let Some(source_code) = &self.source_code {
// `lines()` drops the empty line containing EOF after a final newline.
- let lines: Vec<&str> = crate::span::source_lines(source_code)
- .into_iter()
- .map(|(_, line)| line)
- .collect();
+ let lines: Vec<&str> =
+ crate::span::source_lines(source_code).into_iter().map(|(_, line)| line).collect();
if line <= lines.len() {
let idx = line - 1;
let start = idx.saturating_sub(1);
@@ -395,23 +383,16 @@ impl WaveError {
utils::display_width::diagnostic_line(source_line, col, self.span_len);
let ln = i + 1;
let ln_str = format!("{:>width$}", ln, width = width);
- eprintln!(
- " {} {} {}",
- ln_str.color("38,139,235").bold(),
- pipe,
- rendered.text
- );
+ eprintln!(" {} {} {}", ln_str.color("38,139,235").bold(), pipe, rendered.text);
if ln == line {
let pad = " ".repeat(width);
let spaces = " ".repeat(rendered.caret_offset);
- let marks = "^"
- .repeat(rendered.caret_width)
- .color(self.severity_color())
- .bold();
+ let marks =
+ "^".repeat(rendered.caret_width).color(self.severity_color()).bold();
match &self.label {
Some(label) => {
eprintln!(" {} {} {}{} {}", pad, pipe, spaces, marks, label.dim())
- }
+ },
None => eprintln!(" {} {} {}{}", pad, pipe, spaces, marks),
}
}
@@ -424,19 +405,11 @@ impl WaveError {
let rendered = utils::display_width::diagnostic_line(source_line, col, self.span_len);
let width = line.to_string().len().max(2);
let ln_str = format!("{:>width$}", line, width = width);
- eprintln!(
- " {} {} {}",
- ln_str.color("38,139,235").bold(),
- pipe,
- rendered.text
- );
+ eprintln!(" {} {} {}", ln_str.color("38,139,235").bold(), pipe, rendered.text);
let pad = " ".repeat(width);
let spaces = " ".repeat(rendered.caret_offset);
- let marks = "^"
- .repeat(rendered.caret_width)
- .color(self.severity_color())
- .bold();
+ let marks = "^".repeat(rendered.caret_width).color(self.severity_color()).bold();
match &self.label {
Some(label) => eprintln!(" {} {} {}{} {}", pad, pipe, spaces, marks, label.dim()),
None => eprintln!(" {} {} {}{}", pad, pipe, spaces, marks),
@@ -458,12 +431,7 @@ impl WaveError {
let code = self
.code
.as_ref()
- .map(|c| {
- format!("[{}]", c)
- .color(self.severity_color())
- .bold()
- .to_string()
- })
+ .map(|c| format!("[{}]", c).color(self.severity_color()).bold().to_string())
.unwrap_or_default();
if code.is_empty() {
@@ -585,14 +553,10 @@ impl WaveError {
}
if errors.len() > 1 {
- let error_count = errors
- .iter()
- .filter(|e| matches!(e.severity, ErrorSeverity::Error))
- .count();
- let warning_count = errors
- .iter()
- .filter(|e| matches!(e.severity, ErrorSeverity::Warning))
- .count();
+ let error_count =
+ errors.iter().filter(|e| matches!(e.severity, ErrorSeverity::Error)).count();
+ let warning_count =
+ errors.iter().filter(|e| matches!(e.severity, ErrorSeverity::Warning)).count();
eprintln!();
if error_count > 0 {
diff --git a/front/error/src/span.rs b/front/error/src/span.rs
index b1daf264..4c78c2c2 100644
--- a/front/error/src/span.rs
+++ b/front/error/src/span.rs
@@ -19,12 +19,7 @@ impl SourceSpan {
if self.file != last.file {
return self.clone();
}
- Self {
- end: last.end,
- end_line: last.end_line,
- end_column: last.end_column,
- ..self.clone()
- }
+ Self { end: last.end, end_line: last.end_line, end_column: last.end_column, ..self.clone() }
}
pub fn generated(mut self, reason: impl Into) -> Self {
diff --git a/front/error/tests/diagnostic_rendering.rs b/front/error/tests/diagnostic_rendering.rs
index 30abdeca..5f7fdf06 100644
--- a/front/error/tests/diagnostic_rendering.rs
+++ b/front/error/tests/diagnostic_rendering.rs
@@ -4,14 +4,8 @@ use std::process::Command;
use utils::wson::{self, Value};
fn error() -> WaveError {
- WaveError::new(
- WaveErrorKind::UnexpectedEndOfFile,
- "missing closer",
- "input.wave",
- 2,
- 1,
- )
- .with_label("close the block")
+ WaveError::new(WaveErrorKind::UnexpectedEndOfFile, "missing closer", "input.wave", 2, 1)
+ .with_label("close the block")
}
#[test]
@@ -22,21 +16,17 @@ fn renderer_fixture() {
match case.as_str() {
"batch" => {
WaveError::display_batch(&[error(), error().with_severity(ErrorSeverity::Warning)])
- }
+ },
"empty-batch" => WaveError::display_batch(&[]),
"single-batch" => WaveError::display_batch(&[error()]),
"lf" => error().with_source_code("fun main() {\n").display(),
"cr" => error().with_source_code("fun main() {\r").display(),
"crlf" => error().with_source_code("fun main() {\r\n").display(),
- "empty" => WaveError::new(
- WaveErrorKind::UnexpectedEndOfFile,
- "expected item",
- "empty.wave",
- 1,
- 1,
- )
- .with_source_code("")
- .display(),
+ "empty" => {
+ WaveError::new(WaveErrorKind::UnexpectedEndOfFile, "expected item", "empty.wave", 1, 1)
+ .with_source_code("")
+ .display()
+ },
"invalid-line" => error().with_source_code("only one line").display(),
_ => panic!("unknown fixture"),
}
@@ -63,10 +53,7 @@ fn json_preserves_labels_and_escapes_them() {
&WaveError::new(WaveErrorKind::UnexpectedEndOfFile, "end", "x", 1, 1).to_json(),
)
.unwrap();
- assert!(matches!(
- value.get("error").unwrap().get("label"),
- Some(Value::Null)
- ));
+ assert!(matches!(value.get("error").unwrap().get("label"), Some(Value::Null)));
}
#[test]
@@ -79,14 +66,8 @@ fn json_batches_contain_only_one_json_record_per_diagnostic() {
.collect();
assert_eq!(records.len(), count, "{output}");
if count == 2 {
- assert_eq!(
- records[0].get("error").unwrap().get_str("severity"),
- Some("error")
- );
- assert_eq!(
- records[1].get("error").unwrap().get_str("severity"),
- Some("warning")
- );
+ assert_eq!(records[0].get("error").unwrap().get_str("severity"), Some("error"));
+ assert_eq!(records[1].get("error").unwrap().get_str("severity"), Some("warning"));
}
}
}
@@ -94,10 +75,7 @@ fn json_batches_contain_only_one_json_record_per_diagnostic() {
#[test]
fn human_batches_keep_summary_counts() {
let output = rendered("batch", "human");
- assert!(
- output.contains("error: aborting due to 1 previous error"),
- "{output}"
- );
+ assert!(output.contains("error: aborting due to 1 previous error"), "{output}");
assert!(output.contains("warning: 1 warning emitted"), "{output}");
}
@@ -106,10 +84,7 @@ fn eof_carets_render_on_empty_final_source_lines() {
for case in ["lf", "crlf", "cr", "empty"] {
let output = rendered(case, "human");
let lines: Vec<_> = output.lines().collect();
- let marker = lines
- .iter()
- .position(|line| line.contains('^'))
- .expect("EOF needs a caret");
+ let marker = lines.iter().position(|line| line.contains('^')).expect("EOF needs a caret");
let source_line = if case == "empty" { " 1 | " } else { " 2 | " };
assert_eq!(lines[marker - 1], source_line, "{output}");
assert!(!output.contains('\r'), "{output}");
diff --git a/front/hir/src/async_lower.rs b/front/hir/src/async_lower.rs
index bd0e9245..c7c217bc 100644
--- a/front/hir/src/async_lower.rs
+++ b/front/hir/src/async_lower.rs
@@ -21,12 +21,7 @@ pub enum Transition {
Jump(usize),
Branch(Expression, usize, usize),
Match(Expression, Vec<(MatchPattern, Vec, usize)>),
- Await {
- future: Expression,
- output: String,
- result: WaveType,
- next: usize,
- },
+ Await { future: Expression, output: String, result: WaveType, next: usize },
Complete(Option),
}
#[derive(Debug, Clone)]
@@ -68,11 +63,9 @@ pub fn plan(
lower.block(&function.body)?;
Ok(lower.frame)
}
+
fn empty_state() -> AsyncState {
- AsyncState {
- operations: vec![],
- transition: Transition::Complete(None),
- }
+ AsyncState { operations: vec![], transition: Transition::Complete(None) }
}
struct Lower<'a> {
program: &'a TypedProgram,
@@ -84,12 +77,11 @@ struct Lower<'a> {
fn var(name: impl Into) -> Expression {
Expression::Variable(name.into())
}
+
fn field(name: impl Into) -> Expression {
- Expression::FieldAccess {
- object: Box::new(var("$async$frame")),
- field: name.into(),
- }
+ Expression::FieldAccess { object: Box::new(var("$async$frame")), field: name.into() }
}
+
fn store(target: Expression, value: Expression) -> ASTNode {
ASTNode::Statement(StatementNode::Expression(Expression::Assignment {
target: Box::new(target),
@@ -107,6 +99,7 @@ impl Lower<'_> {
.cloned(),
}
}
+
fn ty(
&self,
e: &Expression,
@@ -116,34 +109,36 @@ impl Lower<'_> {
Some(HirExpressionType::Resolved(t)) => Ok(t.clone()),
Some(HirExpressionType::IntegerLiteral) => {
Ok(expected.cloned().unwrap_or(WaveType::Int(32)))
- }
+ },
Some(HirExpressionType::FloatLiteral) => {
Ok(expected.cloned().unwrap_or(WaveType::Float(32)))
- }
+ },
_ => expected
.cloned()
.ok_or_else(|| self.error(e, "async temporary needs a concrete type")),
}
}
+
fn new_slot(&mut self, ty: WaveType) -> String {
let name = format!("v{}", self.frame.slots.len());
- self.frame.slots.push(FrameSlot {
- name: name.clone(),
- ty,
- });
+ self.frame.slots.push(FrameSlot { name: name.clone(), ty });
name
}
+
fn state(&mut self) -> usize {
let id = self.frame.states.len();
self.frame.states.push(empty_state());
id
}
+
fn emit(&mut self, n: ASTNode) {
self.frame.states[self.current].operations.push(n);
}
+
fn end(&mut self, t: Transition) {
self.frame.states[self.current].transition = t;
}
+
fn snapshot(&mut self, e: Expression, ty: WaveType) -> Expression {
if ty == WaveType::Void {
self.emit(ASTNode::Statement(StatementNode::Expression(e)));
@@ -153,6 +148,7 @@ impl Lower<'_> {
self.emit(store(field(&name), e));
field(name)
}
+
fn place(&mut self, e: &Expression) -> Result {
Ok(match e.unspanned() {
Expression::Variable(n) => self.bindings.get(n).map_or_else(|| var(n), field),
@@ -170,21 +166,15 @@ impl Lower<'_> {
target_type: t,
}))
}
- }
- Expression::FieldAccess {
- object,
- field: name,
- } => {
+ },
+ Expression::FieldAccess { object, field: name } => {
let obj = if matches!(self.ty(object, None)?, WaveType::Pointer(_)) {
self.expr(object, None)?
} else {
self.place(object)?
};
- Expression::FieldAccess {
- object: Box::new(obj),
- field: name.clone(),
- }
- }
+ Expression::FieldAccess { object: Box::new(obj), field: name.clone() }
+ },
Expression::IndexAccess { target, index } => {
let obj = if matches!(self.ty(target, None)?, WaveType::Array(..)) {
self.place(target)?
@@ -192,14 +182,12 @@ impl Lower<'_> {
self.expr(target, None)?
};
let index = self.expr(index, None)?;
- Expression::IndexAccess {
- target: Box::new(obj),
- index: Box::new(index),
- }
- }
+ Expression::IndexAccess { target: Box::new(obj), index: Box::new(index) }
+ },
_ => return Err(self.error(e, "unsupported address across async suspension")),
})
}
+
fn expr(
&mut self,
e: &Expression,
@@ -209,24 +197,16 @@ impl Lower<'_> {
let value = match e.unspanned() {
Expression::Await(inner) => {
let future = self.expr(inner, None)?;
- let output = self.new_slot(if ty == WaveType::Void {
- WaveType::Byte
- } else {
- ty.clone()
- });
+ let output =
+ self.new_slot(if ty == WaveType::Void { WaveType::Byte } else { ty.clone() });
let wait = self.state();
let next = self.state();
self.end(Transition::Jump(wait));
self.current = wait;
- self.end(Transition::Await {
- future,
- output: output.clone(),
- result: ty,
- next,
- });
+ self.end(Transition::Await { future, output: output.clone(), result: ty, next });
self.current = next;
return Ok(field(output));
- }
+ },
Expression::Variable(n) => self.bindings.get(n).map_or_else(|| var(n), field),
Expression::Literal(_) | Expression::Null => e.clone(),
Expression::Grouped(inner) => return self.expr(inner, expected),
@@ -244,7 +224,7 @@ impl Lower<'_> {
target_type: t,
}))
}
- }
+ },
Expression::Unary { operator, expr } => Expression::Unary {
operator: operator.clone(),
expr: Box::new(self.expr(expr, Some(&ty))?),
@@ -256,7 +236,7 @@ impl Lower<'_> {
Some(HirExpressionType::IntegerLiteral) => match target_type {
WaveType::Int(_) | WaveType::Uint(_) | WaveType::Byte | WaveType::Char => {
Some(target_type.clone())
- }
+ },
WaveType::Pointer(_) | WaveType::Float(_) => Some(WaveType::Int(64)),
_ => None,
},
@@ -268,19 +248,17 @@ impl Lower<'_> {
if matches!(target_type, WaveType::Pointer(_)) =>
{
Some(target_type.clone())
- }
+ },
_ => None,
};
Expression::Cast {
expr: Box::new(self.expr(expr, source_context.as_ref())?),
target_type: target_type.clone(),
}
- }
- Expression::BinaryExpression {
- left,
- operator,
- right,
- } if matches!(operator, Operator::LogicalAnd | Operator::LogicalOr) => {
+ },
+ Expression::BinaryExpression { left, operator, right }
+ if matches!(operator, Operator::LogicalAnd | Operator::LogicalOr) =>
+ {
let value = self.expr(left, Some(&WaveType::Bool))?;
let output = self.new_slot(WaveType::Bool);
self.emit(store(field(&output), value.clone()));
@@ -298,12 +276,8 @@ impl Lower<'_> {
self.end(Transition::Jump(done));
self.current = done;
return Ok(field(output));
- }
- Expression::BinaryExpression {
- left,
- operator,
- right,
- } => {
+ },
+ Expression::BinaryExpression { left, operator, right } => {
let lt = self.ty(
left,
if ty == WaveType::Bool {
@@ -323,26 +297,19 @@ impl Lower<'_> {
operator: operator.clone(),
right: Box::new(right),
}
- }
- Expression::FunctionCall {
- name,
- type_args,
- args,
- } => {
+ },
+ Expression::FunctionCall { name, type_args, args } => {
let parameters = self
.program
.syntax()
.iter()
.find_map(|n| match n {
ASTNode::Function(f) if &f.name == name => Some(
- f.parameters
- .iter()
- .map(|p| p.param_type.clone())
- .collect::>(),
+ f.parameters.iter().map(|p| p.param_type.clone()).collect::>(),
),
ASTNode::ExternFunction(f) if &f.name == name => {
Some(f.params.iter().map(|p| p.1.clone()).collect())
- }
+ },
ASTNode::Struct(s) => s
.methods
.iter()
@@ -361,30 +328,18 @@ impl Lower<'_> {
.enumerate()
.map(|(i, a)| self.expr(a, parameters.get(i)))
.collect::>()?;
- Expression::FunctionCall {
- name: name.clone(),
- type_args: type_args.clone(),
- args,
- }
- }
- Expression::MethodCall {
- object,
- name,
- type_args,
- args,
- } => {
+ Expression::FunctionCall { name: name.clone(), type_args: type_args.clone(), args }
+ },
+ Expression::MethodCall { object, name, type_args, args } => {
let object = self.expr(object, None)?;
- let args = args
- .iter()
- .map(|a| self.expr(a, None))
- .collect::>()?;
+ let args = args.iter().map(|a| self.expr(a, None)).collect::>()?;
Expression::MethodCall {
object: Box::new(object),
name: name.clone(),
type_args: type_args.clone(),
args,
}
- }
+ },
Expression::StructLiteral { name, fields } => {
let types = self
.program
@@ -407,25 +362,15 @@ impl Lower<'_> {
})
.collect::>()?,
}
- }
+ },
Expression::ArrayLiteral(values) => {
- let inner = if let WaveType::Array(t, _) = &ty {
- Some(t.as_ref())
- } else {
- None
- };
+ let inner = if let WaveType::Array(t, _) = &ty { Some(t.as_ref()) } else { None };
Expression::ArrayLiteral(
- values
- .iter()
- .map(|v| self.expr(v, inner))
- .collect::>()?,
+ values.iter().map(|v| self.expr(v, inner)).collect::>()?,
)
- }
+ },
Expression::IndexAccess { .. } => self.place(e)?,
- Expression::FieldAccess {
- object,
- field: name,
- } => Expression::FieldAccess {
+ Expression::FieldAccess { object, field: name } => Expression::FieldAccess {
object: Box::new(self.expr(object, None)?),
field: name.clone(),
},
@@ -443,40 +388,33 @@ impl Lower<'_> {
})));
let value = Box::new(self.expr(value, Some(&target_ty))?);
if let Expression::AssignOperation { operator, .. } = e.unspanned() {
- Expression::AssignOperation {
- target,
- operator: operator.clone(),
- value,
- }
+ Expression::AssignOperation { target, operator: operator.clone(), value }
} else {
Expression::Assignment { target, value }
}
- }
- Expression::IncDec { kind, target } => Expression::IncDec {
- kind: kind.clone(),
- target: Box::new(self.place(target)?),
- },
- Expression::AsmBlock {
- instructions,
- inputs,
- outputs,
- clobbers,
- } => Expression::AsmBlock {
- instructions: instructions.clone(),
- inputs: inputs
- .iter()
- .map(|(n, e)| Ok((n.clone(), self.expr(e, None)?)))
- .collect::>()?,
- outputs: outputs
- .iter()
- .map(|(n, e)| Ok((n.clone(), self.place(e)?)))
- .collect::>()?,
- clobbers: clobbers.clone(),
+ },
+ Expression::IncDec { kind, target } => {
+ Expression::IncDec { kind: kind.clone(), target: Box::new(self.place(target)?) }
+ },
+ Expression::AsmBlock { instructions, inputs, outputs, clobbers } => {
+ Expression::AsmBlock {
+ instructions: instructions.clone(),
+ inputs: inputs
+ .iter()
+ .map(|(n, e)| Ok((n.clone(), self.expr(e, None)?)))
+ .collect::>()?,
+ outputs: outputs
+ .iter()
+ .map(|(n, e)| Ok((n.clone(), self.place(e)?)))
+ .collect::>()?,
+ clobbers: clobbers.clone(),
+ }
},
Expression::Located { .. } => unreachable!(),
};
Ok(self.snapshot(value, ty))
}
+
fn block(&mut self, nodes: &[ASTNode]) -> Result<(), AsyncLoweringError> {
let bindings = self.bindings.clone();
for n in nodes {
@@ -485,6 +423,7 @@ impl Lower<'_> {
self.bindings = bindings;
Ok(())
}
+
fn node(&mut self, n: &ASTNode) -> Result<(), AsyncLoweringError> {
match n.unspanned() {
ASTNode::Variable(v) => {
@@ -498,31 +437,23 @@ impl Lower<'_> {
if let Some(init) = init {
self.emit(store(field(slot), init));
}
- }
+ },
ASTNode::Statement(s) => match s {
StatementNode::Return(e) => {
let ty = self.frame.result.clone();
let value = e.as_ref().map(|e| self.expr(e, Some(&ty))).transpose()?;
self.end(Transition::Complete(value));
self.current = self.state();
- }
+ },
StatementNode::Expression(e) => {
self.expr(e, None)?;
- }
+ },
StatementNode::Assign { variable, value } => {
- let target = self
- .bindings
- .get(variable)
- .map_or_else(|| var(variable), field);
+ let target = self.bindings.get(variable).map_or_else(|| var(variable), field);
let value = self.expr(value, None)?;
self.emit(store(target, value));
- }
- StatementNode::If {
- condition,
- body,
- else_if_blocks,
- else_block,
- } => {
+ },
+ StatementNode::If { condition, body, else_if_blocks, else_block } => {
let done = self.state();
let mut branches = vec![(condition, body.as_slice())];
if let Some(others) = else_if_blocks {
@@ -543,16 +474,13 @@ impl Lower<'_> {
}
self.end(Transition::Jump(done));
self.current = done;
- }
+ },
StatementNode::While { condition, body } => {
self.loop_body(None, condition, None, body)?
- }
- StatementNode::For {
- initialization,
- condition,
- increment,
- body,
- } => self.loop_body(Some(initialization), condition, Some(increment), body)?,
+ },
+ StatementNode::For { initialization, condition, increment, body } => {
+ self.loop_body(Some(initialization), condition, Some(increment), body)?
+ },
StatementNode::Break | StatementNode::Continue => {
let (exit, next) = *self.loops.last().expect("validated loop");
self.end(Transition::Jump(if matches!(s, StatementNode::Break) {
@@ -561,7 +489,7 @@ impl Lower<'_> {
next
}));
self.current = self.state();
- }
+ },
StatementNode::Match { value, arms } => {
let value_ty = self.ty(value, None)?;
let value = self.expr(value, None)?;
@@ -585,41 +513,26 @@ impl Lower<'_> {
self.current = start;
self.end(Transition::Match(value, branches));
self.current = done;
- }
+ },
StatementNode::Input { format, args } => {
- let args = args
- .iter()
- .map(|e| self.place(e))
- .collect::>()?;
+ let args = args.iter().map(|e| self.place(e)).collect::>()?;
self.emit(ASTNode::Statement(StatementNode::Input {
format: format.clone(),
args,
}));
- }
+ },
StatementNode::PrintFormat { format, args }
| StatementNode::PrintlnFormat { format, args } => {
- let args = args
- .iter()
- .map(|e| self.expr(e, None))
- .collect::>()?;
+ let args = args.iter().map(|e| self.expr(e, None)).collect::>()?;
let s = match s {
- StatementNode::PrintFormat { .. } => StatementNode::PrintFormat {
- format: format.clone(),
- args,
- },
- _ => StatementNode::PrintlnFormat {
- format: format.clone(),
- args,
+ StatementNode::PrintFormat { .. } => {
+ StatementNode::PrintFormat { format: format.clone(), args }
},
+ _ => StatementNode::PrintlnFormat { format: format.clone(), args },
};
self.emit(ASTNode::Statement(s));
- }
- StatementNode::AsmBlock {
- instructions,
- inputs,
- outputs,
- clobbers,
- } => {
+ },
+ StatementNode::AsmBlock { instructions, inputs, outputs, clobbers } => {
let inputs = inputs
.iter()
.map(|(n, e)| Ok((n.clone(), self.expr(e, None)?)))
@@ -634,42 +547,41 @@ impl Lower<'_> {
outputs,
clobbers: clobbers.clone(),
}));
- }
+ },
_ => self.emit(n.clone()),
},
ASTNode::Expression(e) => {
self.expr(e, None)?;
- }
+ },
_ => {
return Err(AsyncLoweringError {
message: "unsupported declaration inside async function".into(),
span: n.span().cloned(),
})
- }
+ },
}
Ok(())
}
+
fn bind_pattern(&mut self, p: &MatchPattern, ty: &WaveType) -> Result<(), AsyncLoweringError> {
match p.unspanned() {
MatchPattern::Binding(name) => {
let slot = self.new_slot(ty.clone());
self.bindings.insert(name.clone(), slot.clone());
self.emit(store(field(slot), var(name)));
- }
+ },
MatchPattern::Variant { payloads, .. } => {
- let info = self
- .program
- .variant_pattern_of(p)
- .expect("typed variant pattern");
+ let info = self.program.variant_pattern_of(p).expect("typed variant pattern");
let types = info.payload_types.clone();
for (p, t) in payloads.iter().zip(types) {
self.bind_pattern(p, &t)?;
}
- }
- _ => {}
+ },
+ _ => {},
}
Ok(())
}
+
fn loop_body(
&mut self,
init: Option<&ASTNode>,
@@ -708,27 +620,23 @@ impl Lower<'_> {
fn integer(n: usize) -> Expression {
Expression::Literal(Literal::Int(n.to_string()))
}
+
fn call(name: &str, type_args: Vec, args: Vec) -> Expression {
- Expression::FunctionCall {
- name: name.into(),
- type_args,
- args,
- }
+ Expression::FunctionCall { name: name.into(), type_args, args }
}
+
fn statement(e: Expression) -> ASTNode {
ASTNode::Statement(StatementNode::Expression(e))
}
+
fn ret(e: Expression) -> ASTNode {
ASTNode::Statement(StatementNode::Return(Some(e)))
}
+
fn parameter(name: &str, ty: WaveType) -> ParameterNode {
- ParameterNode {
- span: None,
- name: name.into(),
- param_type: ty,
- initial_value: None,
- }
+ ParameterNode { span: None, name: name.into(), param_type: ty, initial_value: None }
}
+
fn function(
name: String,
parameters: Vec,
@@ -748,12 +656,11 @@ fn function(
visibility: Visibility::Private,
}
}
+
fn jump(next: usize) -> Vec {
- vec![
- store(field("state"), integer(next)),
- ASTNode::Statement(StatementNode::Continue),
- ]
+ vec![store(field("state"), integer(next)), ASTNode::Statement(StatementNode::Continue)]
}
+
fn emit_frame(
original: &FunctionNode,
frame: AsyncFrame,
@@ -762,19 +669,11 @@ fn emit_frame(
let frame_name = format!("$async$frame${id}");
let poll_name = format!("$async$poll${id}");
let frame_ty = WaveType::Struct(frame_name.clone());
- let mut fields = frame
- .slots
- .iter()
- .map(|s| (s.name.clone(), s.ty.clone()))
- .collect::>();
+ let mut fields = frame.slots.iter().map(|s| (s.name.clone(), s.ty.clone())).collect::>();
fields.push(("state".into(), WaveType::Int(32)));
fields.push((
"result".into(),
- if frame.result == WaveType::Void {
- WaveType::Byte
- } else {
- frame.result.clone()
- },
+ if frame.result == WaveType::Void { WaveType::Byte } else { frame.result.clone() },
));
let structure = ASTNode::Struct(StructNode {
name: frame_name,
@@ -796,7 +695,7 @@ fn emit_frame(
else_if_blocks: None,
else_block: Some(Box::new(jump(no))),
}))
- }
+ },
Transition::Match(value, branches) => {
body.push(ASTNode::Statement(StatementNode::Match {
value,
@@ -804,21 +703,12 @@ fn emit_frame(
.into_iter()
.map(|(pattern, mut body, next)| {
body.extend(jump(next));
- MatchArm {
- span: None,
- pattern,
- body,
- }
+ MatchArm { span: None, pattern, body }
})
.collect(),
}))
- }
- Transition::Await {
- future,
- output,
- result,
- next,
- } => {
+ },
+ Transition::Await { future, output, result, next } => {
let take = call("__wave_async_take", vec![], vec![future.clone()]);
let mut ready = vec![if result == WaveType::Void {
statement(take)
@@ -838,26 +728,18 @@ fn emit_frame(
vec![var("$async$id"), future],
)));
body.push(ret(Expression::Literal(Literal::Bool(false))));
- }
+ },
Transition::Complete(value) => {
if frame.result != WaveType::Void {
if let Some(value) = value {
body.push(store(field("result"), value));
}
}
- body.push(statement(call(
- "__wave_async_complete",
- vec![],
- vec![var("$async$id")],
- )));
+ body.push(statement(call("__wave_async_complete", vec![], vec![var("$async$id")])));
body.push(ret(Expression::Literal(Literal::Bool(true))));
- }
+ },
}
- arms.push(MatchArm {
- span: None,
- pattern: MatchPattern::Int(index.to_string()),
- body,
- });
+ arms.push(MatchArm { span: None, pattern: MatchPattern::Int(index.to_string()), body });
}
arms.push(MatchArm {
span: None,
@@ -867,19 +749,13 @@ fn emit_frame(
let resume = function(
poll_name.clone(),
vec![
- parameter(
- "$async$frame",
- WaveType::Pointer(Box::new(frame_ty.clone())),
- ),
+ parameter("$async$frame", WaveType::Pointer(Box::new(frame_ty.clone()))),
parameter("$async$id", WaveType::Int(64)),
],
WaveType::Bool,
vec![ASTNode::Statement(StatementNode::While {
condition: Expression::Literal(Literal::Bool(true)),
- body: vec![ASTNode::Statement(StatementNode::Match {
- value: field("state"),
- arms,
- })],
+ body: vec![ASTNode::Statement(StatementNode::Match { value: field("state"), arms })],
})],
);
let mut constructor = original.clone();
@@ -920,7 +796,7 @@ pub fn lower_program(program: &TypedProgram) -> Result, AsyncLoweri
next += 1;
*out = ctor;
generated.extend(extra);
- }
+ },
(ASTNode::Struct(s), ASTNode::Struct(out)) if s.generic_params.is_empty() => {
for (f, target) in s.methods.iter().zip(&mut out.methods) {
if f.is_async && f.generic_params.is_empty() {
@@ -930,7 +806,7 @@ pub fn lower_program(program: &TypedProgram) -> Result, AsyncLoweri
generated.extend(extra);
}
}
- }
+ },
(ASTNode::ProtoImpl(s), ASTNode::ProtoImpl(out)) => {
for (f, target) in s.methods.iter().zip(&mut out.methods) {
if f.is_async && f.generic_params.is_empty() {
@@ -940,8 +816,8 @@ pub fn lower_program(program: &TypedProgram) -> Result, AsyncLoweri
generated.extend(extra);
}
}
- }
- _ => {}
+ },
+ _ => {},
}
}
output.extend(generated);
diff --git a/front/hir/src/conversions.rs b/front/hir/src/conversions.rs
index d44232bb..08d14373 100644
--- a/front/hir/src/conversions.rs
+++ b/front/hir/src/conversions.rs
@@ -79,18 +79,19 @@ pub fn integer_width(ty: &WaveType) -> Option {
_ => None,
}
}
+
pub fn unsigned(ty: &WaveType) -> bool {
- matches!(
- ty,
- WaveType::Uint(_) | WaveType::Bool | WaveType::Byte | WaveType::Char
- )
+ matches!(ty, WaveType::Uint(_) | WaveType::Bool | WaveType::Byte | WaveType::Char)
}
+
pub fn numeric(ty: &WaveType) -> bool {
integer_width(ty).is_some() || matches!(ty, WaveType::Float(_))
}
+
fn pointer(ty: &WaveType) -> bool {
matches!(ty, WaveType::Pointer(_) | WaveType::String)
}
+
pub(super) fn scalar(ty: &WaveType) -> bool {
numeric(ty) || pointer(ty)
}
@@ -123,17 +124,13 @@ pub fn conversion_kind(source: &WaveType, target: &WaveType) -> Option {
Some(if a < b { FloatExtend } else { FloatTruncate })
- }
- (_, WaveType::Float(_)) if integer_width(source).is_some() => Some(if unsigned(source) {
- UnsignedToFloat
- } else {
- SignedToFloat
- }),
- (WaveType::Float(_), _) if integer_width(target).is_some() => Some(if unsigned(target) {
- FloatToUnsigned
- } else {
- FloatToSigned
- }),
+ },
+ (_, WaveType::Float(_)) if integer_width(source).is_some() => {
+ Some(if unsigned(source) { UnsignedToFloat } else { SignedToFloat })
+ },
+ (WaveType::Float(_), _) if integer_width(target).is_some() => {
+ Some(if unsigned(target) { FloatToUnsigned } else { FloatToSigned })
+ },
_ if pointer(source) && integer_width(target).is_some() => Some(PointerToInteger),
_ if integer_width(source).is_some() && pointer(target) => Some(IntegerToPointer),
_ if pointer(source) && pointer(target) => Some(PointerCast),
@@ -152,17 +149,11 @@ impl FloatIntegerRange {
pub fn contains(&self, value: f64) -> bool {
value.is_finite()
&& value < self.upper
- && if self.lower_inclusive {
- value >= self.lower
- } else {
- value > self.lower
- }
+ && if self.lower_inclusive { value >= self.lower } else { value > self.lower }
}
}
pub fn float_integer_range(source: &WaveType, target: &WaveType) -> FloatIntegerRange {
- let WaveType::Float(source_bits) = source else {
- panic!("non-float conversion source")
- };
+ let WaveType::Float(source_bits) = source else { panic!("non-float conversion source") };
let round = |v: f64| {
if *source_bits == 32 {
(v as f32) as f64
@@ -175,11 +166,7 @@ pub fn float_integer_range(source: &WaveType, target: &WaveType) -> FloatInteger
let upper = round(2f64.powi(i32::from(bits - u16::from(signed))));
let minimum = if signed { -upper } else { 0.0 };
let before_minimum = round(minimum - 1.0);
- FloatIntegerRange {
- lower: before_minimum,
- lower_inclusive: before_minimum == minimum,
- upper,
- }
+ FloatIntegerRange { lower: before_minimum, lower_inclusive: before_minimum == minimum, upper }
}
impl NumericExpressionInfo {
@@ -200,11 +187,7 @@ impl NumericExpressionInfo {
if previous != &self.result_type {
return Err("conversion chain does not reach its result type".into());
}
- if self
- .context_type
- .as_ref()
- .is_some_and(|target| target != &self.result_type)
- {
+ if self.context_type.as_ref().is_some_and(|target| target != &self.result_type) {
return Err("conversion chain does not satisfy its contextual destination".into());
}
Ok(())
@@ -222,24 +205,20 @@ fn comparison(op: &Operator) -> bool {
| Operator::GreaterEqual
)
}
+
fn literal(expr: &Expression) -> bool {
match expr {
Expression::Literal(Literal::Int(_) | Literal::Float(_)) => true,
- Expression::Grouped(inner)
- | Expression::Unary {
- operator: Operator::Neg,
- expr: inner,
- } => literal(inner),
+ Expression::Grouped(inner) | Expression::Unary { operator: Operator::Neg, expr: inner } => {
+ literal(inner)
+ },
_ => false,
}
}
pub(super) fn build(program: &TypedProgram) -> Vec