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
2 changes: 1 addition & 1 deletion conformance/valid/011_rows.affine
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ fn get_name[..r](entity: {name: String, ..r}) -> String {
}

fn with_id[..r](record: {..r}, id: Int) -> {id: Int, ..r} {
{ id: id, ..record }
#{ id: id, ..record }
}

type HasPosition[..r] = {x: Int, y: Int, ..r}
Expand Down
4 changes: 2 additions & 2 deletions examples/comprehensive_test.affine
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ enum Shape {
}

fn vec2_add(a: Vec2, b: Vec2) -> Vec2 {
{ x: a.x + b.x, y: a.y + b.y }
#{ x: a.x + b.x, y: a.y + b.y }
}

fn area(s: Shape) -> Int {
Expand All @@ -30,7 +30,7 @@ fn larger_area(s1: Shape, s2: Shape) -> Int = max(area(s1), area(s2));

fn main() -> () {
let circle = Circle(5);
let rect = Rect({ x: 3, y: 4 });
let rect = Rect(#{ x: 3, y: 4 });
let result = larger_area(circle, rect);
();
}
2 changes: 1 addition & 1 deletion examples/rows.affine
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ struct Point3D {
fn getX(p: Point2D) -> Int = p.x;

fn mk_point(x: Int, y: Int) -> Point2D {
{ x: x, y: y }
#{ x: x, y: y }
}
4 changes: 2 additions & 2 deletions examples/typecheck_complete_test.affine
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ enum Color {
}

fn make_point(x: Int, y: Int) -> Point {
{ x: x, y: y }
#{ x: x, y: y }
}

fn origin() -> Point {
{ x: 0, y: 0 }
#{ x: 0, y: 0 }
}

fn mutate_counter() -> Int {
Expand Down
3 changes: 3 additions & 0 deletions lib/lexer.ml
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ let rec token state buf =
| "->" -> ARROW
| "=>" -> FAT_ARROW
| "::" -> COLONCOLON
(* Record-literal opener (affinescript#215): `#{` is the unambiguous
record/struct-literal sigil; bare `{` is always a block. *)
| "#{" -> HASH_LBRACE
(* Row variable "..name" — must come before ".." so sedlex prefers the longer match *)
| "..", lower_ident ->
let s = Sedlexing.Utf8.lexeme buf in
Expand Down
1 change: 1 addition & 0 deletions lib/parse.ml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ let next_token state () =
| Token.LPAREN -> Parser.LPAREN
| Token.RPAREN -> Parser.RPAREN
| Token.LBRACE -> Parser.LBRACE
| Token.HASH_LBRACE -> Parser.HASH_LBRACE
| Token.RBRACE -> Parser.RBRACE
| Token.LBRACKET -> Parser.LBRACKET
| Token.RBRACKET -> Parser.RBRACKET
Expand Down
1 change: 1 addition & 0 deletions lib/parse_driver.ml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ let to_menhir_token (tok : Token.t) : Parser.token =
| Token.LPAREN -> Parser.LPAREN
| Token.RPAREN -> Parser.RPAREN
| Token.LBRACE -> Parser.LBRACE
| Token.HASH_LBRACE -> Parser.HASH_LBRACE
| Token.RBRACE -> Parser.RBRACE
| Token.LBRACKET -> Parser.LBRACKET
| Token.RBRACKET -> Parser.RBRACKET
Expand Down
21 changes: 12 additions & 9 deletions lib/parser.mly
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ let rec effect_union_of_list = function

/* Punctuation */
%token LPAREN RPAREN LBRACE RBRACE LBRACKET RBRACKET
%token HASH_LBRACE /* `#{` record-literal opener (affinescript#215) */
%token COMMA SEMICOLON COLON COLONCOLON DOT DOTDOT
%token ARROW FAT_ARROW PIPE AT UNDERSCORE BACKSLASH QUESTION

Expand Down Expand Up @@ -768,10 +769,11 @@ expr_primary:
ordinary parameter binding named "self". */
| SELF_KW { ExprVar (mk_ident "self" $startpos $endpos) }
| name = lower_ident { ExprVar (mk_ident name $startpos $endpos) }
/* Struct literal: `Point { x: v, y: w }`. Must come before the plain
upper_ident production so Menhir shifts LBRACE rather than reducing
upper_ident to ExprVar when the next token is LBRACE. */
| _ty = upper_ident LBRACE b = expr_record_body RBRACE
/* Struct literal: `Point #{ x: v, y: w }` (affinescript#215). The `#{`
sigil makes this unambiguous against a bare block and removes the
Rust-style struct-literal-in-`if`/`match`-scrutinee hazard entirely;
no production-ordering hack needed any more. */
| _ty = upper_ident HASH_LBRACE b = expr_record_body RBRACE
{ ExprRecord { er_fields = fst b; er_spread = snd b } }
| name = upper_ident { ExprVar (mk_ident name $startpos $endpos) }
| ty = upper_ident COLONCOLON variant = upper_ident
Expand All @@ -787,11 +789,12 @@ expr_primary:
/* Arrays */
| LBRACKET es = separated_list(COMMA, expr) RBRACKET { ExprArray es }

/* Records — use a recursive rule (expr_record_body / expr_record_rest) to
avoid the LALR(1) greedy-separator conflict that arises when a ROW_VAR
spread like `..record` follows a COMMA that `separated_list` has already
consumed expecting another record_field. */
| LBRACE b = expr_record_body RBRACE
/* Anonymous record `#{ f: v, ..spread }` (affinescript#215). The `#{`
sigil removes the entire block-vs-record-literal ambiguity (family
C+D) by construction — bare `{` is now unconditionally a block.
expr_record_body / expr_record_rest stay recursive to avoid the
ROW_VAR greedy-separator conflict on `..spread` after a COMMA. */
| HASH_LBRACE b = expr_record_body RBRACE
{ ExprRecord { er_fields = fst b; er_spread = snd b } }

/* Block */
Expand Down
2 changes: 2 additions & 0 deletions lib/token.ml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ type t =
| RPAREN
| LBRACE
| RBRACE
| HASH_LBRACE (** #{ — record-literal opener (affinescript#215) *)
| LBRACKET
| RBRACKET
| COMMA
Expand Down Expand Up @@ -187,6 +188,7 @@ let to_string = function
| RPAREN -> ")"
| LBRACE -> "{"
| RBRACE -> "}"
| HASH_LBRACE -> "#{"
| LBRACKET -> "["
| RBRACKET -> "]"
| COMMA -> ","
Expand Down
2 changes: 1 addition & 1 deletion stdlib/testing.affine
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ fn bench(f: () -> (), iterations: Int) -> BenchResult {
}

let tot = time_now() - start;
{
#{
iterations: iterations,
total_time: tot,
avg_time: tot / float(iterations)
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/fixtures/counter.affine
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ fn counter_subs(model: Int) -> String {
// Wire up the TEA runtime and hand off to the interpreter loop.

fn main() -> () {
tea_run({
tea_run(#{
init: counter_init,
update: counter_update,
view: counter_view,
Expand Down
4 changes: 2 additions & 2 deletions test/e2e/fixtures/full_pipeline.affine
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ enum Shape {
}

fn vec2_add(a: Vec2, b: Vec2) -> Vec2 {
{ x: a.x + b.x, y: a.y + b.y }
#{ x: a.x + b.x, y: a.y + b.y }
}

fn area(s: Shape) -> Int {
Expand All @@ -31,7 +31,7 @@ fn larger_area(s1: Shape, s2: Shape) -> Int = max(area(s1), area(s2));

fn main() -> () {
let circle = Circle(5);
let rect = Rect({ x: 3, y: 4 });
let rect = Rect(#{ x: 3, y: 4 });
let result = larger_area(circle, rect);
();
}
2 changes: 1 addition & 1 deletion test/e2e/fixtures/row_polymorphism.affine
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ struct Point3D {
fn getX(p: Point2D) -> Int = p.x;

fn mk_point(x: Int, y: Int) -> Point2D {
{ x: x, y: y }
#{ x: x, y: y }
}
12 changes: 6 additions & 6 deletions test/e2e/fixtures/titlescreen.affine
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ struct TitleModel {
// ── init ─────────────────────────────────────────────────────────────────────

fn title_init() -> TitleModel {
{
#{
screen_w: 1280,
screen_h: 720,
bgm_playing: 0,
Expand All @@ -58,10 +58,10 @@ fn title_init() -> TitleModel {

fn title_update(msg: TitleMsg, model: TitleModel) -> TitleModel {
match msg {
NewGame => { screen_w: model.screen_w, screen_h: model.screen_h, bgm_playing: model.bgm_playing, selected: "new_game" },
LoadGame => { screen_w: model.screen_w, screen_h: model.screen_h, bgm_playing: model.bgm_playing, selected: "load_game" },
Settings => { screen_w: model.screen_w, screen_h: model.screen_h, bgm_playing: model.bgm_playing, selected: "settings" },
Credits => { screen_w: model.screen_w, screen_h: model.screen_h, bgm_playing: model.bgm_playing, selected: "credits" }
NewGame => #{ screen_w: model.screen_w, screen_h: model.screen_h, bgm_playing: model.bgm_playing, selected: "new_game" },
LoadGame => #{ screen_w: model.screen_w, screen_h: model.screen_h, bgm_playing: model.bgm_playing, selected: "load_game" },
Settings => #{ screen_w: model.screen_w, screen_h: model.screen_h, bgm_playing: model.bgm_playing, selected: "settings" },
Credits => #{ screen_w: model.screen_w, screen_h: model.screen_h, bgm_playing: model.bgm_playing, selected: "credits" }
}
}

Expand All @@ -84,7 +84,7 @@ fn title_subs(model: TitleModel) -> String {
// ── main ──────────────────────────────────────────────────────────────────────

fn main() -> () {
tea_run({
tea_run(#{
init: title_init,
update: title_update,
view: title_view,
Expand Down
4 changes: 2 additions & 2 deletions test/e2e/fixtures/type_decls.affine
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ enum Result[T, E] {
}

fn make_point(x: Int, y: Int) -> Point {
{ x: x, y: y }
#{ x: x, y: y }
}

fn origin() -> Point {
{ x: 0, y: 0 }
#{ x: 0, y: 0 }
}
2 changes: 1 addition & 1 deletion test/golden/rows.affine
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ fn getX[..r](p: {x: Int, ..r}) -> Int {
}

fn addY[..r](p: {..r}) -> {y: Int, ..r} {
{y: 0, ..p}
#{y: 0, ..p}
}
Loading