Skip to content

Commit

Permalink
Support for structure updates
Browse files Browse the repository at this point in the history
Closes #592

A new node is added in `desugared`, and translated into an exploded structure
literal during translation to `scopelang`. The main reason to put it there is
that it needs to be after disambiguation, since that is used to discover the
type of the structure that is being updated.
  • Loading branch information
AltGr committed Apr 12, 2024
1 parent 2713b73 commit e2c4504
Show file tree
Hide file tree
Showing 17 changed files with 1,675 additions and 1,388 deletions.
12 changes: 12 additions & 0 deletions compiler/desugared/from_surface.ml
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,18 @@ let rec translate_expr
Expr.make_abs (Array.of_list vs) (rec_helper ~local_vars e2) taus pos
in
Expr.eapp ~f ~args:[rec_helper e1] ~tys:[] emark
| StructReplace (e, fields) ->
let fields =
List.fold_left
(fun acc (field_id, field_expr) ->
if Ident.Map.mem (Mark.remove field_id) acc then
Message.error ~pos:(Mark.get field_expr)
"Duplicate redefinition of field@ %a" Ident.format
(Mark.remove field_id);
Ident.Map.add (Mark.remove field_id) (rec_helper field_expr) acc)
Ident.Map.empty fields
in
Expr.edstructamend ~fields ~e:(rec_helper e) ~name_opt:None emark
| StructLit (((path, s_name), _), fields) ->
let ctxt = Name_resolution.module_ctx ctxt path in
let s_uid =
Expand Down
45 changes: 44 additions & 1 deletion compiler/scopelang/from_desugared.ml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,50 @@ let rec translate_expr (ctx : ctx) (e : D.expr) : untyped Ast.expr boxed =
})
m
| ELocation (ToplevelVar v) -> Expr.elocation (ToplevelVar v) m
| EDStructAccess _ ->
| EDStructAmend { name_opt = Some name; e; fields } ->
let str_fields = StructName.Map.find name ctx.decl_ctx.ctx_structs in
let fields =
Ident.Map.fold
(fun id e ->
match
StructName.Map.find name
(Ident.Map.find id ctx.decl_ctx.ctx_struct_fields)
with
| field -> StructField.Map.add field (translate_expr ctx e)
| exception (Ident.Map.Not_found _ | StructName.Map.Not_found _) ->
Message.error ~pos:(Expr.pos e)
~fmt_pos:
[
( (fun ppf ->
Format.fprintf ppf "Declaration of structure %a"
StructName.format name),
Mark.get (StructName.get_info name) );
]
"Field %a@ does@ not@ belong@ to@ structure@ %a" Ident.format id
StructName.format name)
fields StructField.Map.empty
in
if StructField.Map.cardinal fields = StructField.Map.cardinal str_fields
then
Message.warning ~pos:(Expr.mark_pos m)
"All fields of@ %a@ are@ rewritten@ in@ this@ replacement."
StructName.format name;
let orig_var = Var.make "orig" in
let orig_e = Expr.evar orig_var (Mark.get e) in
let fields =
StructField.Map.mapi
(fun field _ty ->
match StructField.Map.find_opt field fields with
| Some e -> e
| None -> Expr.estructaccess ~name ~field ~e:orig_e m)
str_fields
in
Expr.make_let_in orig_var
(TStruct name, Expr.pos e)
(translate_expr ctx e)
(Expr.estruct ~name ~fields m)
(Expr.mark_pos m)
| EDStructAmend { name_opt = None; _ } | EDStructAccess _ ->
assert false (* This shouldn't appear in desugared after disambiguation *)
| EScopeCall { scope; args } ->
Expr.escopecall ~scope
Expand Down
6 changes: 6 additions & 0 deletions compiler/shared_ast/definitions.ml
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,12 @@ and ('a, 'b, 'm) base_gexpr =
args : ('a, 'm) gexpr ScopeVar.Map.t;
}
-> ('a, < explicitScopes : yes ; .. >, 'm) base_gexpr
| EDStructAmend : {
name_opt : StructName.t option;
e : ('a, 'm) gexpr;
fields : ('a, 'm) gexpr Ident.Map.t;
}
-> ('a, < syntacticNames : yes ; .. >, 'm) base_gexpr
| EDStructAccess : {
name_opt : StructName.t option;
e : ('a, 'm) gexpr;
Expand Down
44 changes: 42 additions & 2 deletions compiler/shared_ast/expr.ml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ module Box = struct
let lift : ('a, 't) boxed_gexpr -> ('a, 't) gexpr B.box =
fun em -> B.box_apply (fun e -> Mark.add (Mark.get em) e) (Mark.remove em)

module LiftIdentMap = Bindlib.Lift (Ident.Map)

let lift_identmap = LiftIdentMap.lift_box

module LiftStruct = Bindlib.Lift (StructField.Map)

let lift_struct = LiftStruct.lift_box
Expand Down Expand Up @@ -156,6 +160,14 @@ let estruct ~name ~(fields : ('a, 't) boxed_gexpr StructField.Map.t) mark =
(fun fields -> EStruct { name; fields })
(Box.lift_struct (StructField.Map.map Box.lift fields))

let edstructamend ~name_opt ~e ~(fields : ('a, 't) boxed_gexpr Ident.Map.t) mark
=
Mark.add mark
@@ Bindlib.box_apply2
(fun e fields -> EDStructAmend { name_opt; e; fields })
(Box.lift e)
(Box.lift_identmap (Ident.Map.map Box.lift fields))

let edstructaccess ~name_opt ~field ~e =
Box.app1 e @@ fun e -> EDStructAccess { name_opt; field; e }

Expand Down Expand Up @@ -305,6 +317,9 @@ let map
| EStruct { name; fields } ->
let fields = StructField.Map.map f fields in
estruct ~name ~fields m
| EDStructAmend { name_opt; e; fields } ->
let fields = Ident.Map.map f fields in
edstructamend ~name_opt ~e:(f e) ~fields m
| EDStructAccess { name_opt; field; e } ->
edstructaccess ~name_opt ~field ~e:(f e) m
| EStructAccess { name; field; e } -> estructaccess ~name ~field ~e:(f e) m
Expand Down Expand Up @@ -345,6 +360,8 @@ let shallow_fold
| EErrorOnEmpty e -> acc |> f e
| ECatch { body; handler; _ } -> acc |> f body |> f handler
| EStruct { fields; _ } -> acc |> StructField.Map.fold (fun _ -> f) fields
| EDStructAmend { e; fields; _ } ->
acc |> f e |> Ident.Map.fold (fun _ -> f) fields
| EDStructAccess { e; _ } -> acc |> f e
| EStructAccess { e; _ } -> acc |> f e
| EMatch { e; cases; _ } ->
Expand Down Expand Up @@ -434,6 +451,16 @@ let map_gather
(acc, StructField.Map.empty)
in
acc, estruct ~name ~fields m
| EDStructAmend { name_opt; e; fields } ->
let acc, e = f e in
let acc, fields =
Ident.Map.fold
(fun cons e (acc, fields) ->
let acc1, e = f e in
join acc acc1, Ident.Map.add cons e fields)
fields (acc, Ident.Map.empty)
in
acc, edstructamend ~name_opt ~e ~fields m
| EDStructAccess { name_opt; field; e } ->
let acc, e = f e in
acc, edstructaccess ~name_opt ~field ~e m
Expand Down Expand Up @@ -618,6 +645,11 @@ and equal : type a. (a, 't) gexpr -> (a, 't) gexpr -> bool =
| ( EStruct { name = s1; fields = fields1 },
EStruct { name = s2; fields = fields2 } ) ->
StructName.equal s1 s2 && StructField.Map.equal equal fields1 fields2
| ( EDStructAmend { name_opt = s1; e = e1; fields = fields1 },
EDStructAmend { name_opt = s2; e = e2; fields = fields2 } ) ->
Option.equal StructName.equal s1 s2
&& equal e1 e2
&& Ident.Map.equal equal fields1 fields2
| ( EDStructAccess { e = e1; field = f1; name_opt = s1 },
EDStructAccess { e = e2; field = f2; name_opt = s2 } ) ->
Option.equal StructName.equal s1 s2 && Ident.equal f1 f2 && equal e1 e2
Expand All @@ -641,8 +673,8 @@ and equal : type a. (a, 't) gexpr -> (a, 't) gexpr -> bool =
| ( ( EVar _ | EExternal _ | ETuple _ | ETupleAccess _ | EArray _ | ELit _
| EAbs _ | EApp _ | EAppOp _ | EAssert _ | EDefault _ | EPureDefault _
| EIfThenElse _ | EEmptyError | EErrorOnEmpty _ | ERaise _ | ECatch _
| ELocation _ | EStruct _ | EDStructAccess _ | EStructAccess _ | EInj _
| EMatch _ | EScopeCall _ | ECustom _ ),
| ELocation _ | EStruct _ | EDStructAmend _ | EDStructAccess _
| EStructAccess _ | EInj _ | EMatch _ | EScopeCall _ | ECustom _ ),
_ ) ->
false

Expand Down Expand Up @@ -685,6 +717,11 @@ let rec compare : type a. (a, _) gexpr -> (a, _) gexpr -> int =
EStruct {name=name2; fields=field_map2 } ->
StructName.compare name1 name2 @@< fun () ->
StructField.Map.compare compare field_map1 field_map2
| EDStructAmend {name_opt=n1; e=e1; fields=field_map1},
EDStructAmend {name_opt=n2; e=e2; fields=field_map2} ->
compare e1 e2 @@< fun () ->
Ident.Map.compare compare field_map1 field_map2 @@< fun () ->
Option.compare StructName.compare n1 n2
| EDStructAccess {e=e1; field=field_name1; name_opt=struct_name1},
EDStructAccess {e=e2; field=field_name2; name_opt=struct_name2} ->
compare e1 e2 @@< fun () ->
Expand Down Expand Up @@ -748,6 +785,7 @@ let rec compare : type a. (a, _) gexpr -> (a, _) gexpr -> int =
| EIfThenElse _, _ -> -1 | _, EIfThenElse _ -> 1
| ELocation _, _ -> -1 | _, ELocation _ -> 1
| EStruct _, _ -> -1 | _, EStruct _ -> 1
| EDStructAmend _, _ -> -1 | _, EDStructAmend _ -> 1
| EDStructAccess _, _ -> -1 | _, EDStructAccess _ -> 1
| EStructAccess _, _ -> -1 | _, EStructAccess _ -> 1
| EMatch _, _ -> -1 | _, EMatch _ -> 1
Expand Down Expand Up @@ -895,6 +933,8 @@ let rec size : type a. (a, 't) gexpr -> int =
| ELocation _ -> 1
| EStruct { fields; _ } ->
StructField.Map.fold (fun _ e acc -> acc + 1 + size e) fields 0
| EDStructAmend { e; fields; _ } ->
1 + size e + Ident.Map.fold (fun _ e acc -> acc + 1 + size e) fields 0
| EDStructAccess { e; _ } -> 1 + size e
| EStructAccess { e; _ } -> 1 + size e
| EMatch { e; cases; _ } ->
Expand Down
7 changes: 7 additions & 0 deletions compiler/shared_ast/expr.mli
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,13 @@ val estruct :
'm mark ->
('a any, 'm) boxed_gexpr

val edstructamend :
name_opt:StructName.t option ->
e:('a, 'm) boxed_gexpr ->
fields:('a, 'm) boxed_gexpr Ident.Map.t ->
'm mark ->
((< syntacticNames : yes ; .. > as 'a), 'm) boxed_gexpr

val edstructaccess :
name_opt:StructName.t option ->
field:Ident.t ->
Expand Down
11 changes: 10 additions & 1 deletion compiler/shared_ast/print.ml
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ module Precedence = struct
| ETupleAccess _ -> Dot
| ELocation _ -> Contained
| EScopeCall _ -> App
| EDStructAmend _ -> App
| EDStructAccess _ | EStructAccess _ -> Dot
| EAssert _ -> App
| EDefault _ -> Contained
Expand Down Expand Up @@ -681,6 +682,14 @@ module ExprGen (C : EXPR_PARAM) = struct
| EDStructAccess { e; field; _ } ->
Format.fprintf fmt "@[<hv 2>%a%a@,%a%a%a@]" (lhs exprc) e punctuation
"." punctuation "\"" Ident.format field punctuation "\""
| EDStructAmend { e; fields; _ } ->
Format.fprintf fmt "@[<hv 2>@[<hov>%a %a@ with@]@ %a@;<1 -2>%a@]"
punctuation "{" (lhs exprc) e
(Ident.Map.format_bindings ~pp_sep:Format.pp_print_space
(fun fmt pp_field_name field_expr ->
Format.fprintf fmt "@[<hv 2>%t %a@ %a%a@]" pp_field_name
punctuation "=" (lhs exprc) field_expr punctuation ";"))
fields punctuation "}"
| EStruct { name; fields } ->
if StructField.Map.is_empty fields then (
punctuation fmt "{";
Expand Down Expand Up @@ -1127,7 +1136,7 @@ module UserFacing = struct
| EApp _ | EAppOp _ | EVar _ | EIfThenElse _ | EMatch _ | ETupleAccess _
| EStructAccess _ | EAssert _ | EDefault _ | EPureDefault _
| EErrorOnEmpty _ | ERaise _ | ECatch _ | ELocation _ | EScopeCall _
| EDStructAccess _ | ECustom _ ->
| EDStructAmend _ | EDStructAccess _ | ECustom _ ->
fallback ppf e
let expr :
Expand Down
19 changes: 19 additions & 0 deletions compiler/shared_ast/typing.ml
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,25 @@ and typecheck_expr_top_down :
fields
in
Expr.estruct ~name ~fields mark
| A.EDStructAmend { name_opt = _; e; fields } ->
let e = typecheck_expr_top_down ctx env tau e in
let name =
match UnionFind.get (ty e) with
| TStruct name, _ -> name
| TAny _, _ -> failwith "Disambiguation failure"
| _ ->
Message.error ~pos:(Expr.pos e)
"This expression has type %a, where a structure was expected"
(format_typ ctx) (ty e)
in
let fields = A.Ident.Map.map (typecheck_expr_bottom_up ctx env) fields in
(* Note: here we identify the structure name, and type the fields
individually, but without enforcing any consistency constraint between
the two. This is fine because this construction only appears in
Desugared, where it is used for disambiguation. In later passes this is
rewritten into a struct literal, so no need to anticipate name resolution
and duplicate the checks here. *)
Expr.edstructamend ~name_opt:(Some name) ~e ~fields context_mark
| A.EDStructAccess { e = e_struct; name_opt; field } ->
let t_struct =
match name_opt with
Expand Down
1 change: 1 addition & 0 deletions compiler/surface/ast.ml
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ and naked_expression =
| EnumInject of (path * uident Mark.pos) Mark.pos * expression option
| StructLit of
(path * uident Mark.pos) Mark.pos * (lident Mark.pos * expression) list
| StructReplace of expression * (lident Mark.pos * expression) list
| ArrayLit of expression list
| Tuple of expression list
| Ident of path * lident Mark.pos * lident Mark.pos option
Expand Down
7 changes: 7 additions & 0 deletions compiler/surface/lexer.cppo.ml
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,9 @@ module R = Re.Pcre
#ifndef MR_LIST_EMPTY
#define MR_LIST_EMPTY MS_LIST_EMPTY
#endif
#ifndef MR_BUT_REPLACE
#define MR_BUT_REPLACE MS_BUT_REPLACE
#endif
#ifndef MR_CARDINAL
#define MR_CARDINAL MS_CARDINAL
#endif
Expand Down Expand Up @@ -316,6 +319,7 @@ let token_list : (string * token) list =
(MS_MINIMUM, MINIMUM);
(MS_IS, IS);
(MS_LIST_EMPTY, LIST_EMPTY);
(MS_BUT_REPLACE, BUT_REPLACE);
(MS_CARDINAL, CARDINAL);
(MS_YEAR, YEAR);
(MS_MONTH, MONTH);
Expand Down Expand Up @@ -570,6 +574,9 @@ let rec lex_code (lexbuf : lexbuf) : token =
| MR_LIST_EMPTY ->
L.update_acc lexbuf;
LIST_EMPTY
| MR_BUT_REPLACE ->
L.update_acc lexbuf;
BUT_REPLACE
| MR_CARDINAL ->
L.update_acc lexbuf;
CARDINAL
Expand Down
2 changes: 2 additions & 0 deletions compiler/surface/lexer_en.cppo.ml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@
#define MS_IS "is"
#define MS_LIST_EMPTY "list empty"
#define MR_LIST_EMPTY "list", space_plus, "empty"
#define MS_BUT_REPLACE "but replace"
#define MR_BUT_REPLACE "but", space_plus, "replace"
#define MS_CARDINAL "number"
#define MS_YEAR "year"
#define MS_MONTH "month"
Expand Down
2 changes: 2 additions & 0 deletions compiler/surface/lexer_fr.cppo.ml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@
#define MS_IS "est"
#define MS_LIST_EMPTY "liste vide"
#define MR_LIST_EMPTY "liste", space_plus, "vide"
#define MS_BUT_REPLACE "mais en rempla\195\167ant"
#define MR_BUT_REPLACE "mais", space_plus, "en", space_plus, "rempla", 0xE7, "ant"
#define MS_CARDINAL "nombre"
#define MS_YEAR "an"
#define MS_MONTH "mois"
Expand Down
2 changes: 2 additions & 0 deletions compiler/surface/lexer_pl.cppo.ml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@
#define MS_IS "jest"
#define MS_LIST_EMPTY "lista pusta"
#define MR_LIST_EMPTY "lista", space_plus, "pusta"
#define MS_BUT_REPLACE "ale zastąpić"
#define MR_BUT_REPLACE "ale", space_plus, "zast", 0x0105, "pi", 0x0107
#define MS_CARDINAL "liczba"
#define MS_YEAR "rok"
#define MS_MONTH "miesiąc"
Expand Down
Loading

0 comments on commit e2c4504

Please sign in to comment.