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: 9 additions & 0 deletions NOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

Last updated: 2026-08-08

## parser: braced if-expr arms, paren-less conditions, &-transparent types; fpga specs repaired (Refs #1960)

- If-EXPRESSIONS accept braced arms (`if (c) { 2 } else { 0 }`); if/while STATEMENTS accept paren-less Rust-style conditions with the struct-literal-in-condition rule (a `{` after the cond opens the body)
- Reference types are transparent (`&str`/`&T` parse as the referent)
- mac.t27 pack_trit (braced if-expr), spi.t27 (three `match` constructs -- FSM tick, prescaler, SCK -- silently dropped for ever), fifo.t27 (four literal missing-paren typos) repaired
- fpga-build --smoke: 2 -> 21 of 35 modules generate; remaining tails are the given/then BDD fn form (linker) onward
- tri-net 77-spec icarus gate green, unit suite at the single pre-existing red
- FROZEN_HASH resealed

## gen-verilog: W458 keeps the legacy [N]T binding; unit contracts updated (Refs #1948)

- The W458 array-param exclusion narrows to rust-style [T; N] primitives only; legacy [N]T keeps its module-array ROM binding contract
Expand Down
80 changes: 67 additions & 13 deletions bootstrap/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,9 @@ pub struct Parser {
current: Token,
peek: Token,
pending_pragma: String,
/// Inside a paren-less if/while condition a `{` opens the BODY, never a
/// struct literal (the Rust rule); parse_primary consults this.
no_struct_literal: bool,
}

#[derive(Clone)]
Expand All @@ -853,6 +856,7 @@ impl Parser {
current: first,
peek: second,
pending_pragma: String::new(),
no_struct_literal: false,
}
}

Expand Down Expand Up @@ -1590,6 +1594,12 @@ impl Parser {
fn parse_type_annotation(&mut self) -> String {
let mut ty = String::new();

// Reference types are transparent at the spec level: `&str` / `&T`
// parse as their referent (t27#1960).
if self.current.kind == TokenKind::Amp {
self.advance(); // consume &
}

// Handle tuple type: (T1, T2, ...). Keep the raw textual form so that
// named tuples like (a: T, b: T) do not hang the parser and are stored
// as-is for backends that do not need to unpack them.
Expand Down Expand Up @@ -1772,6 +1782,13 @@ impl Parser {
self.advance(); // consume !
}

// Reference types are transparent at the spec level: `&str` / `&T`
// parse as their referent (t27#1960 fallout -- the old recovery
// silently dropped whole fns over the `&`).
if self.current.kind == TokenKind::Amp {
self.advance(); // consume &
}

// Return type (identifier, tuple, or []T / [N]T / [][]const u8 slice/array types, or void)
if self.current.kind == TokenKind::LParen {
// Tuple return type: (u32, u32)
Expand Down Expand Up @@ -2161,10 +2178,21 @@ impl Parser {
let mut if_node = Node::new(NodeKind::StmtIf);
self.advance(); // consume 'if'

// Condition in parentheses
self.expect(TokenKind::LParen)?;
let cond = self.parse_expr()?;
self.expect(TokenKind::RParen)?;
// Condition: `if (cond) { ... }` or the paren-less Rust-style
// `if cond { ... }` -- both appear across the spec corpus, and the
// paren-less form was silently DROPPED by the old statement recovery
// (t27#1960 fallout of the #1941 hardening).
let cond = if self.current.kind == TokenKind::LParen {
self.advance(); // consume (
let c = self.parse_expr()?;
self.expect(TokenKind::RParen)?;
c
} else {
self.no_struct_literal = true;
let c = self.parse_expr();
self.no_struct_literal = false;
c?
};
if_node.children.push(cond);

// Then branch: { ... }
Expand Down Expand Up @@ -2233,10 +2261,18 @@ impl Parser {
let mut while_node = Node::new(NodeKind::StmtWhile);
self.advance(); // consume 'while'

// Condition in parentheses
self.expect(TokenKind::LParen)?;
let cond = self.parse_expr()?;
self.expect(TokenKind::RParen)?;
// Condition: parenthesized or paren-less (see parse_if_stmt).
let cond = if self.current.kind == TokenKind::LParen {
self.advance(); // consume (
let c = self.parse_expr()?;
self.expect(TokenKind::RParen)?;
c
} else {
self.no_struct_literal = true;
let c = self.parse_expr();
self.no_struct_literal = false;
c?
};
while_node.children.push(cond);

// Body: { ... }
Expand Down Expand Up @@ -2836,8 +2872,9 @@ impl Parser {
}
}

// Check for struct literal: Name{ .field = expr, ... }
if self.current.kind == TokenKind::LBrace {
// Check for struct literal: Name{ .field = expr, ... }. In a
// paren-less condition the `{` opens the statement body.
if self.current.kind == TokenKind::LBrace && !self.no_struct_literal {
return self.parse_struct_literal(name);
}

Expand Down Expand Up @@ -3061,8 +3098,18 @@ impl Parser {
let cond = self.parse_expr()?;
self.expect(TokenKind::RParen)?;

// Then expression
let then_expr = self.parse_expr()?;
// Then expression. Braced arms (`if (c) { 2 } else { 0 }`) are the
// common spec spelling; the bare-expression form stays supported.
// Before #1941 the brace was silently swallowed by statement
// recovery -- now it must parse (t27#1960, mac.t27 pack_trit).
let then_expr = if self.current.kind == TokenKind::LBrace {
self.advance(); // consume {
let e = self.parse_expr()?;
self.expect(TokenKind::RBrace)?;
e
} else {
self.parse_expr()?
};

// else expression
let mut if_node = Node::new(NodeKind::ExprIf);
Expand All @@ -3071,7 +3118,14 @@ impl Parser {

if self.current.kind == TokenKind::KwElse {
self.advance(); // consume 'else'
let else_expr = self.parse_expr()?;
let else_expr = if self.current.kind == TokenKind::LBrace {
self.advance(); // consume {
let e = self.parse_expr()?;
self.expect(TokenKind::RBrace)?;
e
} else {
self.parse_expr()?
};
if_node.children.push(else_expr);
}

Expand Down
2 changes: 1 addition & 1 deletion bootstrap/stage0/FROZEN_HASH
Original file line number Diff line number Diff line change
@@ -1 +1 @@
c808d130f5af01ba082ba255737cacda0f7a2fec507949ba07fdf6fa13da7f06
ac0310ff69d2c211d638757eaa8704e80a968918ccb93f5fe4ebb353e99a3788
9 changes: 9 additions & 0 deletions docs/NOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

Last updated: 2026-08-08

## parser: braced if-expr arms, paren-less conditions, &-transparent types; fpga specs repaired (Refs #1960)

- If-EXPRESSIONS accept braced arms (`if (c) { 2 } else { 0 }`); if/while STATEMENTS accept paren-less Rust-style conditions with the struct-literal-in-condition rule (a `{` after the cond opens the body)
- Reference types are transparent (`&str`/`&T` parse as the referent)
- mac.t27 pack_trit (braced if-expr), spi.t27 (three `match` constructs -- FSM tick, prescaler, SCK -- silently dropped for ever), fifo.t27 (four literal missing-paren typos) repaired
- fpga-build --smoke: 2 -> 21 of 35 modules generate; remaining tails are the given/then BDD fn form (linker) onward
- tri-net 77-spec icarus gate green, unit suite at the single pre-existing red
- FROZEN_HASH resealed

## gen-verilog: W458 keeps the legacy [N]T binding; unit contracts updated (Refs #1948)

- The W458 array-param exclusion narrows to rust-style [T; N] primitives only; legacy [N]T keeps its module-array ROM binding contract
Expand Down
8 changes: 4 additions & 4 deletions specs/fpga/fifo.t27
Original file line number Diff line number Diff line change
Expand Up @@ -200,16 +200,16 @@ module Fifo {
if (cfg.name == "") {
errors = errors + 1;
}
if (cfg.depth == 0 {
if (cfg.depth == 0) {
errors = errors + 1;
}
if (cfg.data_width == 0 {
if (cfg.data_width == 0) {
errors = errors + 1;
}
if (cfg.has_almost_empty and cfg.almost_empty_threshold >= cfg.depth {
if (cfg.has_almost_empty and cfg.almost_empty_threshold >= cfg.depth) {
errors = errors + 1;
}
if (cfg.has_almost_full and cfg.almost_full_threshold >= cfg.depth {
if (cfg.has_almost_full and cfg.almost_full_threshold >= cfg.depth) {
errors = errors + 1;
}

Expand Down
120 changes: 56 additions & 64 deletions specs/fpga/spi.t27
Original file line number Diff line number Diff line change
Expand Up @@ -111,17 +111,18 @@ module SPI_Master;
// spi_get_prescaler_div() 413 u32
// Get actual prescaler divider value
fn spi_get_prescaler_div() -> u32 {
match spi.prescaler {
PRESCALER_2 => 2u32,
PRESCALER_4 => 4u32,
PRESCALER_8 => 8u32,
PRESCALER_16 => 16u32,
PRESCALER_32 => 32u32,
PRESCALER_64 => 64u32,
PRESCALER_128 => 128u32,
PRESCALER_256 => 256u32,
_ => 16u32,
}
// Was a `match` expression, which t27 has never parsed -- the whole
// dispatch was silently DROPPED before the #1941 hardening (the fn
// was an unimplemented stub). If-chain now.
if (spi.prescaler == PRESCALER_2) { return 2; }
if (spi.prescaler == PRESCALER_4) { return 4; }
if (spi.prescaler == PRESCALER_8) { return 8; }
if (spi.prescaler == PRESCALER_16) { return 16; }
if (spi.prescaler == PRESCALER_32) { return 32; }
if (spi.prescaler == PRESCALER_64) { return 64; }
if (spi.prescaler == PRESCALER_128) { return 128; }
if (spi.prescaler == PRESCALER_256) { return 256; }
return 16;
}

// spi_get_sck_freq() 414 u32
Expand Down Expand Up @@ -178,11 +179,10 @@ module SPI_Master;
fn spi_get_sck() -> bool {
// In Mode 0: SCK is low in idle
// Alternates during transfer
match spi.tx_state {
TX_BIT => false, // SCK low (setup)
RX_BIT => true, // SCK high (sample)
_ => SPI_CPOL == 0,
}
// Was a `match` expression (never parsed; silently dropped pre-#1941).
if (spi.tx_state == TX_BIT) { return false; } // SCK low (setup)
if (spi.tx_state == RX_BIT) { return true; } // SCK high (sample)
return SPI_CPOL == 0;
}

// spi_get_mosi() 421 bool
Expand All @@ -197,30 +197,25 @@ module SPI_Master;
// spi_tick() 422 void
// Process one system clock cycle
fn spi_tick() -> void {
match spi.state {
SPI_IDLE => {
// Do nothing, waiting for transfer
}
SPI_CS_ASSERT => {
spi.cs_assert_cnt = spi.cs_assert_cnt + 1;
if (spi.cs_assert_cnt >= (CS_ASSERT_DELAY * CLK_FREQ / 1_000_000_000)) {
spi.cs_assert_cnt = 0;
spi.cs_asserted = true;
spi.state = SPI_TRANSFER;
spi.tx_state = TX_BIT;
}
// Was a `match` statement (never parsed; the whole FSM tick was
// silently dropped pre-#1941). If/else-if chain now.
if (spi.state == SPI_CS_ASSERT) {
spi.cs_assert_cnt = spi.cs_assert_cnt + 1;
if (spi.cs_assert_cnt >= (CS_ASSERT_DELAY * CLK_FREQ / 1_000_000_000)) {
spi.cs_assert_cnt = 0;
spi.cs_asserted = true;
spi.state = SPI_TRANSFER;
spi.tx_state = TX_BIT;
}
SPI_TRANSFER => {
spi_transfer_bit();
}
SPI_CS_DEASSERT => {
spi.cs_deassert_cnt = spi.cs_deassert_cnt + 1;
if (spi.cs_deassert_cnt >= (CS_DEASSERT_DELAY * CLK_FREQ / 1_000_000_000)) {
spi.cs_deassert_cnt = 0;
spi.cs_asserted = false;
spi.state = SPI_IDLE;
spi.busy = false;
}
} else if (spi.state == SPI_TRANSFER) {
spi_transfer_bit();
} else if (spi.state == SPI_CS_DEASSERT) {
spi.cs_deassert_cnt = spi.cs_deassert_cnt + 1;
if (spi.cs_deassert_cnt >= (CS_DEASSERT_DELAY * CLK_FREQ / 1_000_000_000)) {
spi.cs_deassert_cnt = 0;
spi.cs_asserted = false;
spi.state = SPI_IDLE;
spi.busy = false;
}
}
}
Expand All @@ -231,34 +226,31 @@ module SPI_Master;
const prescaler_div = spi_get_prescaler_div();
spi.bit_counter = spi.bit_counter + 1;

match spi.tx_state {
TX_BIT => {
if (spi.bit_counter >= prescaler_div / 2) {
spi.bit_counter = 0;
spi.tx_state = RX_BIT;
}
// Was a `match` statement (silently dropped pre-#1941).
if (spi.tx_state == TX_BIT) {
if (spi.bit_counter >= prescaler_div / 2) {
spi.bit_counter = 0;
spi.tx_state = RX_BIT;
}
RX_BIT => {
if (spi.bit_counter >= prescaler_div / 2) {
// Sample MISO 424 in spec-level simulation this is a placeholder;
// Verilog emission reads the actual MISO input pin
const miso_bit = false;
spi.rx_data = (spi.rx_data << 1) | (if miso_bit { 1u32 } else { 0u32 });
spi.bit_count = spi.bit_count + 1;
spi.bit_counter = 0;

if (spi.bit_count >= spi.data_width) {
spi.tx_state = WAIT_EDGE;
} else {
spi.tx_state = TX_BIT;
}
} else if (spi.tx_state == RX_BIT) {
if (spi.bit_counter >= prescaler_div / 2) {
// Sample MISO 424 in spec-level simulation this is a placeholder;
// Verilog emission reads the actual MISO input pin
const miso_bit = false;
spi.rx_data = (spi.rx_data << 1) | (if (miso_bit) { 1 } else { 0 });
spi.bit_count = spi.bit_count + 1;
spi.bit_counter = 0;

if (spi.bit_count >= spi.data_width) {
spi.tx_state = WAIT_EDGE;
} else {
spi.tx_state = TX_BIT;
}
}
WAIT_EDGE => {
if (spi.bit_counter >= prescaler_div / 2) {
spi.bit_counter = 0;
spi.state = SPI_CS_DEASSERT;
}
} else if (spi.tx_state == WAIT_EDGE) {
if (spi.bit_counter >= prescaler_div / 2) {
spi.bit_counter = 0;
spi.state = SPI_CS_DEASSERT;
}
}
}
Expand Down
Loading