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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ this project aims to follow [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- feat(res-to-affine): partial-port mode #488 slice 3 — `--partial` now translates array literals (`[a, b]`) and record literals (`{x, y}` → `Rec #{ x: x, y: y }`, with a nominal placeholder type + field-punning expansion) (Refs #488)
- feat(res-to-affine): partial-port mode #488 slice 2 — `--partial` now desugars ReScript pipe-first `->` (`a->f(b)` → `f(a, b)`, chained left-to-right), and translates `if`/`else` and blocks with `let` statements (Refs #488)
- feat(res-to-affine): partial-port mode (#488) — new `--partial` flag renders module-top-level functions as AffineScript `fn` skeletons with `switch`→`match` and best-effort expression translation (literals / idents / calls / binary ops with float-op + identity-equality normalisation / `++` / member + qualified access / ternary / variant + tuple + literal patterns); un-translatable forms become `() /* TODO */` / `_ /* TODO */` holes. Output deliberately does NOT type-check but parses (verified). Distinct model from `--translate` (Refs #488)
- feat(res-to-affine): Phase 3 slice 3 — `--translate` now also lowers module-level `let <id> = <literal>` (int/float/string/bool) to a typed `const name: T = value;`; call / `ref(...)` / destructuring bindings are skipped (not compile-time constants); every emitted form verified compilable via `main.exe check`. `switch`→`match` and qualified-path resolution remain out of the standalone-type-check scope (Refs #57)
Expand Down
18 changes: 11 additions & 7 deletions tools/res-to-affine/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,13 +231,17 @@ It translates literals, identifiers, calls, binary operators (normalising
ReScript's float ops `+.`/`*.` → `+`/`*` and `===`/`!==` → `==`/`!=`), string
concat `++`, member/qualified access, ternaries, **`if`/`else`**, **blocks
with `let` statements**, **pipe-first `->`** (`a->f(b)` → `f(a, b)`, chained
left-to-right), and `switch`→`match` with variant/tuple/literal patterns.
Anything else (records, arrays, objects, …) becomes a `() /* TODO */` hole.
The output is a partial port to finish by hand: it **parses** but is not
expected to type-check (verified — the generated skeletons reach
resolution/type-checking without a parse error). Continuing under #488:
record/array/object literals, labelled args, combining `--partial` with
`--translate`, and module-qualified-reference *resolution*.
left-to-right), **array literals** (`[a, b]`), **record literals** (`{x, y}` →
`Rec #{ x: x, y: y }` — AffineScript records are *nominal*, so an anonymous
ReScript record gets the placeholder type `Rec` for the human to rename;
field punning `{x}` expands to `x: x`), and `switch`→`match` with
variant/tuple/literal patterns. Anything else (JS objects, interpolated
template strings, `try`/`catch`, …) becomes a `() /* TODO */` hole. The output
is a partial port to finish by hand: it **parses** but is not expected to
type-check (verified — the generated skeletons reach resolution/type-checking
without a parse error). Continuing under #488: JS objects / template strings,
labelled-arg refinement, combining `--partial` with `--translate`, and
module-qualified-reference *resolution* (a module-mapping policy decision).

## Corpus run

Expand Down
15 changes: 11 additions & 4 deletions tools/res-to-affine/test/fixtures/partial1.res
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// SPDX-License-Identifier: MIT
// #488 partial-port fixture: module-top-level functions -> `fn` skeletons
// with switch->match, pipe desugaring, if/else, blocks, and best-effort
// expression translation. Output is NOT expected to type-check; it must
// parse, with un-translatable forms (e.g. an array literal) as TODO holes.
// with switch->match, pipe desugaring, if/else, blocks, array/record literals,
// and best-effort expression translation. Output is NOT expected to
// type-check; it must parse, with un-translatable forms (e.g. an
// interpolated template string) as TODO holes.

let classify = x => switch x {
| Some(n) => n + 1
Expand All @@ -27,5 +28,11 @@ let clamp = x => if x > 0 { x } else { 0 }
// block with a let statement
let scaled = x => { let y = x + 1; y * 2 }

// array literal has no handler yet -> must become a TODO hole.
// array literal -> [x, x]
let pair = x => [x, x]

// record literal -> Rec #{ x: x, y: y } (nominal placeholder type)
let mkpt = (x, y) => {x: x, y: y}

// interpolated template string has no handler yet -> must become a TODO hole.
let tmpl = x => `val=${x}`
21 changes: 18 additions & 3 deletions tools/res-to-affine/test/test_walker.ml
Original file line number Diff line number Diff line change
Expand Up @@ -401,8 +401,19 @@ let partial1_blob () =
let test_partial_count () =
skip_unless_ready ();
Alcotest.(check int)
"nine module-top-level functions -> fn skeletons"
9 (List.length (translate_partial1 ()))
"eleven module-top-level functions -> fn skeletons"
11 (List.length (translate_partial1 ()))

let test_partial_array () =
skip_unless_ready ();
Alcotest.(check bool) "array literal translated"
true (contains (partial1_blob ()) "[x, x]")

let test_partial_record () =
skip_unless_ready ();
(* nominal placeholder type `Rec`; field punning {x} -> x: x *)
Alcotest.(check bool) "record literal -> Rec #{ ... }"
true (contains (partial1_blob ()) "Rec #{ x: x, y: y }")

let test_partial_pipe () =
skip_unless_ready ();
Expand Down Expand Up @@ -521,8 +532,12 @@ let () =
] );
( "walker-488-partial",
[
Alcotest.test_case "nine functions -> fn skeletons"
Alcotest.test_case "eleven functions -> fn skeletons"
`Quick test_partial_count;
Alcotest.test_case "array literal translated"
`Quick test_partial_array;
Alcotest.test_case "record literal -> Rec #{ ... }"
`Quick test_partial_record;
Alcotest.test_case "switch -> match + patterns + arm bodies"
`Quick test_partial_switch_to_match;
Alcotest.test_case "float op normalised + multi-param skeleton"
Expand Down
29 changes: 29 additions & 0 deletions tools/res-to-affine/walker.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1044,6 +1044,35 @@ let rec translate_expr ~source n =
Printf.sprintf "%s.%s" (translate_expr ~source r)
(node_text ~source p)
| _ -> todo_expr ~source n)
| "array" ->
Printf.sprintf "[%s]"
(String.concat ", "
(List.filter_map
(fun c ->
if c.ntype = "comment" then None
else Some (translate_expr ~source c))
n.children))
(* AffineScript records are nominal (`Type #{ … }`), so an anonymous ReScript
record gets a placeholder type `Rec` for the human to rename; the field
values are translated. Field punning `{x}` expands to `x: x`. *)
| "record" ->
let fields =
List.filter_map
(fun c ->
if c.ntype <> "record_field" then None
else
match List.filter (fun x -> x.ntype <> "comment") c.children with
| [ name ] ->
let nm = node_text ~source name in
Some (Printf.sprintf "%s: %s" nm nm)
| name :: value :: _ ->
Some
(Printf.sprintf "%s: %s" (node_text ~source name)
(translate_expr ~source value))
| [] -> None)
n.children
in
Printf.sprintf "Rec #{ %s }" (String.concat ", " fields)
| _ -> todo_expr ~source n

and translate_labeled_arg ~source n =
Expand Down
Loading