Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ jobs:
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 21
echo "LLVM_SYS_211_PREFIX=/usr/lib/llvm-21" >> "$GITHUB_ENV"
echo "LLVM_CONFIG_PATH=/usr/lib/llvm-21/bin/llvm-config" >> "$GITHUB_ENV"
echo "/usr/lib/llvm-21/bin" >> "$GITHUB_PATH"

- name: Build
run: |
export LLVM_SYS_211_PREFIX=/usr/lib/llvm-21
cargo clean
cargo build --verbose
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose

Expand All @@ -46,6 +46,7 @@ jobs:
run: |
echo "LLVM_SYS_211_PREFIX=$(brew --prefix llvm@21)" >> $GITHUB_ENV
echo "LLVM_CONFIG_PATH=$(brew --prefix llvm@21)/bin/llvm-config" >> $GITHUB_ENV
echo "$(brew --prefix llvm@21)/bin" >> $GITHUB_PATH

- name: Build
run: cargo build --verbose
Expand Down
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,30 +82,36 @@ Wave follows a tiered platform policy to set clear expectations for stability, C
</p>

<details open>
<summary><strong>🥇 Tier 1 · Primary</strong> — <code>Linux</code>, <code>Darwin</code>, <code>WaveOS</code></summary>
<summary><strong>🥇 Tier 1 · Primary</strong> — <code>Linux / ELF</code>, <code>Darwin/macOS</code>, <code>WaveOS / Freestanding</code></summary>
<ul>
<li>Full standard library support</li>
<li>Required CI coverage</li>
<li>ABI stability commitment</li>
<li>Release-blocking platforms</li>
<li>Official release packaging target</li>
</ul>
</details>

<details>
<summary><strong>🥈 Tier 2 · Secondary</strong> — <code>FreeBSD</code>, <code>Redox</code>, <code>Fuchsia</code></summary>
<ul>
<li>Build support maintained</li>
<li>Official support target, but not release-blocking</li>
<li>Object generation support expected</li>
<li>Binary linking supported when a valid sysroot/toolchain is provided</li>
<li>Partial standard library coverage</li>
<li>Clear diagnostics required for missing sysroot, CRT, linker, or libc support</li>
<li>Open to community collaboration</li>
</ul>
</details>

<details>
<summary><strong>🥉 Tier 3 · Experimental</strong> — <code>OpenBSD</code>, <code>Windows (MinGW/GNU)</code></summary>
<summary><strong>🥉 Tier 3 · Experimental</strong> — <code>Windows / PE-COFF</code>, <code>OpenBSD</code>, <code>NetBSD</code>, <code>DragonFlyBSD</code>, <code>Haiku</code></summary>
<ul>
<li>Cross-compilation from Linux supported</li>
<li>Basic standard library coverage (via Wine/MinGW)</li>
<li>Experimental support for native Windows binaries</li>
<li>Experimental target support</li>
<li>Cross-compilation may be available on a best-effort basis</li>
<li>Object generation is prioritized over full hosted binary execution</li>
<li>Standard library coverage may be incomplete</li>
<li>Native execution, packaging, and installer support are not guaranteed</li>
</ul>
</details>

Expand Down
5 changes: 5 additions & 0 deletions front/lexer/src/ident.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ impl<'a> Lexer<'a> {
lexeme: "extern".to_string(),
line: self.line,
},
"export" => Token {
token_type: TokenType::Export,
lexeme: "export".to_string(),
line: self.line,
},
"type" => Token {
token_type: TokenType::Type,
lexeme: "type".to_string(),
Expand Down
1 change: 1 addition & 0 deletions front/lexer/src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ impl fmt::Display for UnsignedIntegerType {
pub enum TokenType {
Fun,
Extern,
Export,
Type,
Enum,
Static,
Expand Down
7 changes: 7 additions & 0 deletions front/parser/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ pub struct FunctionNode {
pub parameters: Vec<ParameterNode>,
pub return_type: Option<WaveType>,
pub body: Vec<ASTNode>,
pub export: Option<ExportAttribute>,
}

#[derive(Debug, Clone)]
pub struct ExportAttribute {
pub abi: String,
pub symbol: Option<String>,
}

#[derive(Debug, Clone)]
Expand Down
28 changes: 20 additions & 8 deletions front/parser/src/parser/decl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,11 +364,18 @@ fn expect(tokens: &mut Peekable<Iter<'_, Token>>, ty: TokenType, msg: &str) -> b
}
}

/// extern(abi) ... / extern(abi, "sym") ...
fn parse_extern_header(tokens: &mut Peekable<Iter<'_, Token>>) -> Option<(String, Option<String>)> {
/// <keyword>(abi) ... / <keyword>(abi, "sym") ...
pub(super) fn parse_ffi_header(
tokens: &mut Peekable<Iter<'_, Token>>,
keyword: &str,
) -> Option<(String, Option<String>)> {
skip_ws(tokens);

if !expect(tokens, TokenType::Lparen, "Expected '(' after 'extern'") {
if !expect(
tokens,
TokenType::Lparen,
&format!("Expected '(' after '{}'", keyword),
) {
return None;
}

Expand All @@ -381,8 +388,8 @@ fn parse_extern_header(tokens: &mut Peekable<Iter<'_, Token>>) -> Option<(String
}) => name.clone(),
other => {
println!(
"Error: Expected ABI identifier in extern(...), found {:?}",
other
"Error: Expected ABI identifier in {}(...), found {:?}",
keyword, other
);
return None;
}
Expand All @@ -403,8 +410,8 @@ fn parse_extern_header(tokens: &mut Peekable<Iter<'_, Token>>) -> Option<(String
}) => Some(s.clone()),
other => {
println!(
"Error: Expected string literal after ',' in extern(...), found {:?}",
other
"Error: Expected string literal after ',' in {}(...), found {:?}",
keyword, other
);
return None;
}
Expand All @@ -416,14 +423,19 @@ fn parse_extern_header(tokens: &mut Peekable<Iter<'_, Token>>) -> Option<(String
if !expect(
tokens,
TokenType::Rparen,
"Expected ')' to close extern(...)",
&format!("Expected ')' to close {}(...)", keyword),
) {
return None;
}

Some((abi, global_symbol))
}

/// extern(abi) ... / extern(abi, "sym") ...
fn parse_extern_header(tokens: &mut Peekable<Iter<'_, Token>>) -> Option<(String, Option<String>)> {
parse_ffi_header(tokens, "extern")
}

fn peek_non_ws_token_type(tokens: &Peekable<Iter<'_, Token>>) -> Option<TokenType> {
let mut it = tokens.clone();
while let Some(t) = it.peek() {
Expand Down
77 changes: 76 additions & 1 deletion front/parser/src/parser/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
// SPDX-License-Identifier: MPL-2.0
// AI TRAINING NOTICE: Prohibited without prior written permission. No use for machine learning or generative AI training, fine-tuning, distillation, embedding, or dataset creation.

use crate::ast::{ASTNode, FunctionNode, ParameterNode, StatementNode, Value};
use crate::ast::{ASTNode, ExportAttribute, FunctionNode, ParameterNode, StatementNode, Value};
use crate::expr::parse_expression;
use crate::parser::asm::*;
use crate::parser::control::*;
Expand Down Expand Up @@ -199,6 +199,13 @@ pub fn parse_parameters(tokens: &mut Peekable<Iter<Token>>) -> Vec<ParameterNode
}

pub fn parse_function(tokens: &mut Peekable<Iter<Token>>) -> Option<ASTNode> {
parse_function_with_export(tokens, None)
}

pub fn parse_function_with_export(
tokens: &mut Peekable<Iter<Token>>,
export: Option<ExportAttribute>,
) -> Option<ASTNode> {
tokens.next();

skip_ws(tokens);
Expand Down Expand Up @@ -252,9 +259,77 @@ pub fn parse_function(tokens: &mut Peekable<Iter<Token>>) -> Option<ASTNode> {
parameters,
body,
return_type,
export,
}))
}

pub fn parse_export(tokens: &mut Peekable<Iter<Token>>) -> Option<Vec<ASTNode>> {
let (abi, global_symbol) = parse_ffi_header(tokens, "export")?;
let export = ExportAttribute {
abi,
symbol: global_symbol,
};

skip_ws(tokens);

if tokens.peek().map(|t| t.token_type.clone()) == Some(TokenType::Lbrace) {
if export.symbol.is_some() {
println!("Error: export block cannot use a single symbol alias");
return None;
}

tokens.next();

let mut nodes = Vec::new();
loop {
skip_ws(tokens);

match tokens.peek().map(|t| t.token_type.clone()) {
Some(TokenType::Rbrace) => {
tokens.next();
break;
}
Some(TokenType::Fun) => {
let node = parse_function_with_export(tokens, Some(export.clone()))?;
if let ASTNode::Function(func) = &node {
if !func.generic_params.is_empty() {
println!("Error: exported functions cannot be generic");
return None;
}
}
nodes.push(node);
}
Some(TokenType::Whitespace) | Some(TokenType::Newline) => {
tokens.next();
}
other => {
println!("Error: Unexpected token in export block: {:?}", other);
return None;
}
}
}

skip_ws(tokens);
if tokens.peek().map(|t| t.token_type.clone()) == Some(TokenType::SemiColon) {
tokens.next();
}

Some(nodes)
} else if tokens.peek().map(|t| t.token_type.clone()) == Some(TokenType::Fun) {
let node = parse_function_with_export(tokens, Some(export))?;
if let ASTNode::Function(func) = &node {
if !func.generic_params.is_empty() {
println!("Error: exported functions cannot be generic");
return None;
}
}
Some(vec![node])
} else {
println!("Error: Expected 'fun' or '{{' after export(...)");
None
}
}

pub fn extract_body(tokens: &mut Peekable<Iter<Token>>) -> Option<Vec<ASTNode>> {
let mut body = vec![];

Expand Down
24 changes: 22 additions & 2 deletions front/parser/src/parser/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

use crate::ast::ASTNode;
use crate::parser::decl::*;
use crate::parser::functions::parse_function;
use crate::parser::functions::{parse_export, parse_function};
use crate::parser::items::*;
use crate::verification::*;
use lexer::token::TokenType;
Expand Down Expand Up @@ -234,6 +234,26 @@ pub fn parse_syntax_only(tokens: &[Token]) -> Result<Vec<ASTNode>, ParseError> {
.with_help("check ABI syntax, function signature, and separators"));
}
}
TokenType::Export => {
let anchor = (*token).clone();
iter.next();
if let Some(export_nodes) = parse_export(&mut iter) {
nodes.extend(export_nodes);
} else {
return Err(ParseError::syntax_at(
Some(&anchor),
"failed to parse export declaration",
)
.with_context("top-level export block/declaration")
.with_expected_many([
"export(c) fun name(...) { ... }",
"export(c, \"symbol\") fun name(...) { ... }",
"export(c) { fun a(...) { ... } fun b(...) { ... } }",
])
.with_found_token(iter.peek().copied())
.with_help("exports require a concrete non-generic function body"));
}
}
TokenType::Const => {
let anchor = (*token).clone();
iter.next();
Expand Down Expand Up @@ -356,7 +376,7 @@ pub fn parse_syntax_only(tokens: &[Token]) -> Result<Vec<ASTNode>, ParseError> {
.with_context("top-level items")
.with_expected_many([
"import", "extern", "const", "static", "type", "enum", "struct",
"proto", "fun",
"proto", "fun", "export",
])
.with_found_token(Some(token))
.with_help("only declarations are allowed at top level"),
Expand Down
38 changes: 16 additions & 22 deletions front/parser/src/parser/stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
// SPDX-License-Identifier: MPL-2.0
// AI TRAINING NOTICE: Prohibited without prior written permission. No use for machine learning or generative AI training, fine-tuning, distillation, embedding, or dataset creation.

use crate::ast::{ASTNode, AssignOperator, Expression, Operator, StatementNode};
use crate::ast::{ASTNode, AssignOperator, Expression, StatementNode};
use crate::expr::{is_assignable, parse_expression, parse_expression_from_token};
use crate::parser::control::{parse_for, parse_if, parse_match, parse_while};
use crate::parser::decl::{parse_let, parse_var};
Expand Down Expand Up @@ -93,28 +93,22 @@ pub fn parse_assignment(
)))
}

None => match left_expr {
Expression::Variable(name) => Some(ASTNode::Statement(StatementNode::Assign {
variable: name,
value: right_expr,
})),

other => {
if !is_assignable(&other) {
println!("Error: Unsupported assignment left expression: {:?}", other);
return None;
}

Some(ASTNode::Statement(StatementNode::Assign {
variable: "deref".to_string(),
value: Expression::BinaryExpression {
left: Box::new(other),
operator: Operator::Assign,
right: Box::new(right_expr),
},
}))
None => {
if !is_assignable(&left_expr) {
println!(
"Error: Unsupported assignment left expression: {:?}",
left_expr
);
return None;
}
},

Some(ASTNode::Statement(StatementNode::Expression(
Expression::Assignment {
target: Box::new(left_expr),
value: Box::new(right_expr),
},
)))
}
}
}

Expand Down
1 change: 1 addition & 0 deletions llvm/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub struct BackendOptions {
pub linker: Option<String>,
pub link_args: Vec<String>,
pub no_default_libs: bool,
pub freestanding: bool,
}

fn is_windows_gnu_target(target: Option<&str>) -> bool {
Expand Down
Loading
Loading