diff --git a/src/arrange.ml b/src/arrange.ml index d91a394d7b7..bc9b77b78f2 100644 --- a/src/arrange.ml +++ b/src/arrange.ml @@ -13,6 +13,7 @@ let rec exp e = match e.it with | UnE (ot, uo, e) -> "UnE" $$ [operator_type !ot; unop uo; exp e] | BinE (ot, e1, bo, e2) -> "BinE" $$ [operator_type !ot; exp e1; binop bo; exp e2] | RelE (ot, e1, ro, e2) -> "RelE" $$ [operator_type !ot; exp e1; relop ro; exp e2] + | ShowE (ot, e) -> "ShowE" $$ [operator_type !ot; exp e] | TupE es -> "TupE" $$ List.map exp es | ProjE (e, i) -> "ProjE" $$ [exp e; Atom (string_of_int i)] | ObjE (s, efs) -> "ObjE" $$ [obj_sort s] @ List.map exp_field efs diff --git a/src/arrange_ir.ml b/src/arrange_ir.ml index 3e883aaecb5..959ba29539c 100644 --- a/src/arrange_ir.ml +++ b/src/arrange_ir.ml @@ -14,6 +14,7 @@ let rec exp e = match e.it with | UnE (t, uo, e) -> "UnE" $$ [typ t; Arrange.unop uo; exp e] | BinE (t, e1, bo, e2)-> "BinE" $$ [typ t; exp e1; Arrange.binop bo; exp e2] | RelE (t, e1, ro, e2)-> "RelE" $$ [typ t; exp e1; Arrange.relop ro; exp e2] + | ShowE (t, e) -> "ShowE" $$ [typ t; exp e] | TupE es -> "TupE" $$ List.map exp es | ProjE (e, i) -> "ProjE" $$ [exp e; Atom (string_of_int i)] | DotE (e, n) -> "DotE" $$ [exp e; Atom (name n)] diff --git a/src/async.ml b/src/async.ml index 0b179293941..37779d247f7 100644 --- a/src/async.ml +++ b/src/async.ml @@ -213,6 +213,8 @@ module Transform() = struct | VarE id -> exp' | UnE (ot, op, exp1) -> UnE (t_operator_type ot, op, t_exp exp1) + | ShowE (ot, exp1) -> + ShowE (t_operator_type ot, t_exp exp1) | BinE (ot, exp1, op, exp2) -> BinE (t_operator_type ot, t_exp exp1, op, t_exp exp2) | RelE (ot, exp1, op, exp2) -> diff --git a/src/await.ml b/src/await.ml index 4246bd4ff6a..13e053b210b 100644 --- a/src/await.ml +++ b/src/await.ml @@ -67,6 +67,8 @@ and t_exp' context exp' = BinE (ot, t_exp context exp1, op, t_exp context exp2) | RelE (ot, exp1, op, exp2) -> RelE (ot, t_exp context exp1, op, t_exp context exp2) + | ShowE (ot, exp1) -> + ShowE (ot, t_exp context exp1) | TupE exps -> TupE (List.map (t_exp context) exps) | OptE exp1 -> @@ -241,6 +243,8 @@ and c_exp' context exp k = binary context k (fun v1 v2 -> e (BinE (ot, v1, op, v2))) exp1 exp2 | RelE (ot, exp1, op, exp2) -> binary context k (fun v1 v2 -> e (RelE (ot, v1, op, v2))) exp1 exp2 + | ShowE (ot, exp1) -> + unary context k (fun v1 -> e (ShowE (ot, v1))) exp1 | TupE exps -> nary context k (fun vs -> e (TupE vs)) exps | OptE exp1 -> diff --git a/src/check_ir.ml b/src/check_ir.ml index 6da79a6eb57..aeeaf73df63 100644 --- a/src/check_ir.ml +++ b/src/check_ir.ml @@ -299,7 +299,7 @@ let rec check_exp env (exp:Ir.exp) : unit = check (Operator.has_unop ot op) "unary operator is not defined for operand type"; check_exp env exp1; typ exp1 <: ot; - ot <: t; + ot <: t | BinE (ot, exp1, op, exp2) -> check (Operator.has_binop ot op) "binary operator is not defined for operand type"; check_exp env exp1; @@ -307,6 +307,12 @@ let rec check_exp env (exp:Ir.exp) : unit = typ exp1 <: ot; typ exp2 <: ot; ot <: t; + | ShowE (ot, exp1) -> + check env.flavor.has_show "show expression in non-show flavor"; + check (Show.can_show ot) "show is not defined for operand type"; + check_exp env exp1; + typ exp1 <: ot; + T.Prim T.Text <: t | RelE (ot, exp1, op, exp2) -> check (Operator.has_relop ot op) "relational operator is not defined for operand type"; check_exp env exp1; diff --git a/src/construct.ml b/src/construct.ml index 232aea8103d..5df03cdc4d4 100644 --- a/src/construct.ml +++ b/src/construct.ml @@ -199,6 +199,25 @@ let switch_optE exp1 exp2 pat exp3 typ1 = } } +let switch_variantE exp1 cases typ1 = + { it = + SwitchE (exp1, + List.map (fun (l,p,e) -> + { it = {pat = {it = VariantP (l, p); + at = no_region; + note = typ exp1}; + exp = e}; + at = no_region; + note = () + }) + cases + ); + at = no_region; + note = { S.note_typ = typ1; + S.note_eff = List.fold_left max_eff (eff exp1) (List.map (fun (l,p,e) -> eff e) cases) + } + } + let tupE exps = let effs = List.map eff exps in let eff = List.fold_left max_eff Type.Triv effs in diff --git a/src/construct.mli b/src/construct.mli index 9c81a16d622..2ecedc6e42f 100644 --- a/src/construct.mli +++ b/src/construct.mli @@ -57,6 +57,7 @@ val callE : exp -> typ list -> exp -> exp val ifE : exp -> exp -> exp -> typ -> exp val dotE : exp -> name -> typ -> exp val switch_optE : exp -> exp -> pat -> exp -> typ -> exp +val switch_variantE : exp -> (id * pat * exp) list -> typ -> exp val tupE : exp list -> exp val breakE: id -> exp -> exp val retE: exp -> exp diff --git a/src/definedness.ml b/src/definedness.ml index a6a91cbfc4c..eefa47fe168 100644 --- a/src/definedness.ml +++ b/src/definedness.ml @@ -92,6 +92,7 @@ let rec exp msgs e : f = match e.it with | UnE (_, uo, e) -> exp msgs e | BinE (_, e1, bo, e2)-> exps msgs [e1; e2] | RelE (_, e1, ro, e2)-> exps msgs [e1; e2] + | ShowE (_, e) -> exp msgs e | TupE es -> exps msgs es | ProjE (e, i) -> exp msgs e | ObjE (s, efs) -> diff --git a/src/desugar.ml b/src/desugar.ml index 3e04fd98494..9b7ff5dcaf2 100644 --- a/src/desugar.ml +++ b/src/desugar.ml @@ -38,6 +38,8 @@ and exp' at note = function I.BinE (!ot, exp e1, o, exp e2) | S.RelE (ot, e1, o, e2) -> I.RelE (!ot, exp e1, o, exp e2) + | S.ShowE (ot, e) -> + I.ShowE (!ot, exp e) | S.TupE es -> I.TupE (exps es) | S.ProjE (e, i) -> I.ProjE (exp e, i) | S.OptE e -> I.OptE (exp e) @@ -283,6 +285,7 @@ and prog (p : Syntax.prog) : Ir.prog = end , { I.has_await = true ; I.has_async_typ = true + ; I.has_show = true ; I.serialized = false } diff --git a/src/effect.ml b/src/effect.ml index 1d20bbbe4d9..d6235a002ca 100644 --- a/src/effect.ml +++ b/src/effect.ml @@ -31,6 +31,7 @@ let rec infer_effect_exp (exp:Syntax.exp) : T.eff = | FuncE _ -> T.Triv | UnE (_, _, exp1) + | ShowE (_, exp1) | ProjE (exp1, _) | OptE exp1 | VariantE (_, exp1) @@ -123,6 +124,7 @@ module Ir = | LitE _ -> T.Triv | UnE (_, _, exp1) + | ShowE (_, exp1) | ProjE (exp1, _) | OptE exp1 | VariantE (_, exp1) diff --git a/src/freevars.ml b/src/freevars.ml index 3eb015bdba6..e273b436d2f 100644 --- a/src/freevars.ml +++ b/src/freevars.ml @@ -65,6 +65,7 @@ let rec exp e : f = match e.it with | UnE (_, uo, e) -> exp e | BinE (_, e1, bo, e2) -> exps [e1; e2] | RelE (_, e1, ro, e2) -> exps [e1; e2] + | ShowE (_, e) -> exp e | TupE es -> exps es | ProjE (e, i) -> exp e | DotE (e, i) -> exp e diff --git a/src/interpret.ml b/src/interpret.ml index 9d55281fb6c..2e694056d89 100644 --- a/src/interpret.ml +++ b/src/interpret.ml @@ -252,6 +252,11 @@ and interpret_exp_mut env exp (k : V.value V.cont) = trap exp.at "arithmetic overflow") ) ) + | ShowE (ot, exp1) -> + interpret_exp env exp1 (fun v -> + if Show.can_show !ot + then k (Value.Text (Show.show_val !ot v)) + else raise (Invalid_argument "debug_show")) | RelE (ot, exp1, op, exp2) -> interpret_exp env exp1 (fun v1 -> interpret_exp env exp2 (fun v2 -> diff --git a/src/interpret_ir.ml b/src/interpret_ir.ml index bb0977391ee..82155579d01 100644 --- a/src/interpret_ir.ml +++ b/src/interpret_ir.ml @@ -278,6 +278,11 @@ and interpret_exp_mut env exp (k : V.value V.cont) = k (interpret_lit env lit) | UnE (ot, op, exp1) -> interpret_exp env exp1 (fun v1 -> k (Operator.unop ot op v1)) + | ShowE (ot, exp1) -> + interpret_exp env exp1 (fun v -> + if Show.can_show ot + then k (Value.Text (Show.show_val ot v)) + else raise (Invalid_argument "debug_show")) | BinE (ot, exp1, op, exp2) -> interpret_exp env exp1 (fun v1 -> interpret_exp env exp2 (fun v2 -> diff --git a/src/ir.ml b/src/ir.ml index 59b92e6637a..e9d4f429bf5 100644 --- a/src/ir.ml +++ b/src/ir.ml @@ -37,6 +37,7 @@ and exp' = | UnE of Type.typ * unop * exp (* unary operator *) | BinE of Type.typ * exp * binop * exp (* binary operator *) | RelE of Type.typ * exp * relop * exp (* relational operator *) + | ShowE of Type.typ * exp (* debug show *) | TupE of exp list (* tuple *) | ProjE of exp * int (* tuple projection *) | OptE of exp (* option injection *) @@ -97,6 +98,7 @@ should hold. type flavor = { has_async_typ : bool; (* AsyncT *) has_await : bool; (* AwaitE and AsyncE *) + has_show : bool; (* ShowE *) serialized : bool; (* Shared function arguments are serialized *) } diff --git a/src/lexer.mll b/src/lexer.mll index fa0d9016bfc..e4274998ec9 100644 --- a/src/lexer.mll +++ b/src/lexer.mll @@ -208,6 +208,7 @@ rule token mode = parse | "private" { PRIVATE } | "return" { RETURN } | "shared" { SHARED } + | "debug_show" { DEBUG_SHOW } | "switch" { SWITCH } | "true" { BOOL true } | "type" { TYPE } diff --git a/src/parser.mly b/src/parser.mly index 21d02937f67..d123bc05c6a 100644 --- a/src/parser.mly +++ b/src/parser.mly @@ -98,6 +98,7 @@ let share_expfield (ef : exp_field) = %token FUNC TYPE OBJECT ACTOR CLASS PRIVATE NEW SHARED %token SEMICOLON SEMICOLON_EOL COMMA COLON SUB DOT QUEST %token AND OR NOT +%token DEBUG_SHOW %token ASSERT %token ADDOP SUBOP MULOP DIVOP MODOP POWOP %token ANDOP OROP XOROP SHLOP SHROP ROTLOP ROTROP @@ -382,6 +383,8 @@ exp_un : { NotE e @? at $sloc } | i=variant_tag e=exp_nullary { VariantE (i, e) @? at $sloc } + | DEBUG_SHOW e=exp_un + { ShowE (ref Type.Pre, e) @? at $sloc } exp_bin : | e=exp_un diff --git a/src/pipeline.ml b/src/pipeline.ml index cd920c83e51..f88f4feeeaf 100644 --- a/src/pipeline.ml +++ b/src/pipeline.ml @@ -141,6 +141,9 @@ let serialization = let tailcall_optimization = transform_if "Tailcall optimization" (fun _ -> Tailcall.transform) +let show_translation = + transform_if "Translate show" Show.transform + let check_with parse infer senv name : check_result = match parse name with | Error e -> Error [e] @@ -175,6 +178,7 @@ let interpret_prog (senv,denv) name prog : (Value.value * Interpret.scope) optio let prog_ir = async_lowering (!Flags.await_lowering && !Flags.async_lowering) senv prog_ir name in let prog_ir = serialization (!Flags.await_lowering && !Flags.async_lowering) senv prog_ir name in let prog_ir = tailcall_optimization true senv prog_ir name in + let prog_ir = show_translation true senv prog_ir name in Interpret_ir.interpret_prog denv prog_ir else Interpret.interpret_prog denv prog in match vo with @@ -301,6 +305,7 @@ let compile_with check mode name : compile_result = let prog_ir = async_lowering true initial_stat_env prog_ir name in let prog_ir = serialization true initial_stat_env prog_ir name in let prog_ir = tailcall_optimization true initial_stat_env prog_ir name in + let prog_ir = show_translation true initial_stat_env prog_ir name in phase "Compiling" name; let module_ = Compile.compile mode name prelude_ir [prog_ir] in Ok module_ diff --git a/src/prelude.ml b/src/prelude.ml index 5a2449c796b..698702f2e3f 100644 --- a/src/prelude.ml +++ b/src/prelude.ml @@ -91,6 +91,98 @@ func clzWord64(w : Word64) : Word64 = (prim "clz64" : Word64 -> Word64) w; func ctzWord64(w : Word64) : Word64 = (prim "ctz64" : Word64 -> Word64) w; func btstWord64(w : Word64, amount : Word64) : Bool = (prim "btst64" : (Word64, Word64) -> Word64) (w, amount) != (0 : Word64); +// Internal helper functions for the show translation + +// The @ in the name ensures that this connot be shadowed by user code, so +// compiler passes can rely on them being in scope +// The text_of functions do not need to be exposed; the user can just use +// the show above. + +func @text_of_Nat(x : Nat) : Text { + var text = ""; + var n = x; + let base = 10; + + while (n > 0) { + let rem = n % base; + text := (switch (rem) { + case (0) { "0" }; + case (1) { "1" }; + case (2) { "2" }; + case (3) { "3" }; + case (4) { "4" }; + case (5) { "5" }; + case (6) { "6" }; + case (7) { "7" }; + case (8) { "8" }; + case (9) { "9" }; + case (_) { assert false; "" }; + }) # text; + n := n / base; + }; + return text; +}; + +func @text_of_Int(x : Int) : Text { + if (x == 0) { + return "0"; + }; + if (x < 0) { + "-" # @text_of_Nat(abs x) + } else { + @text_of_Nat(abs x) + } +}; + +func @text_of_Bool(b : Bool) : Text { + if (b) "true" else "false" +}; + +func @text_of_Text(t : Text) : Text { + // TODO: Escape properly + "\"" # t # "\""; +}; + +func @text_of_option(f : T -> Text, x : ?T) : Text { + switch (x) { + case (?y) {"?(" # f y # ")"}; + case null {"null"}; + } +}; + +func @text_of_variant(l : Text, f : T -> Text, x : T) : Text { + "(#" # l # " " # f x # ")" +}; + +func @text_of_array(f : T -> Text, xs : [T]) : Text { + var text = ""; + for (x in xs.vals()) { + if (text == "") { + text := text # "["; + } else { + text := text # ", "; + }; + text := text # f x; + }; + text := text # "]"; + return text; +}; + +func @text_of_array_mut(f : T -> Text, xs : [var T]) : Text { + var text = ""; + for (x in xs.vals()) { + if (text == "") { + text := text # "[var "; + } else { + text := text # ", "; + }; + text := text # f x; + }; + text := text # "]"; + return text; +}; + +// Array utilities // This would be nicer as a objects, but lets do them as functions // until the compiler has a concept of “static objects” diff --git a/src/rename.ml b/src/rename.ml index 2f5a9152d6c..441ac8a1913 100644 --- a/src/rename.ml +++ b/src/rename.ml @@ -36,6 +36,7 @@ and exp' rho e = match e with | UnE (ot, uo, e) -> UnE (ot, uo, exp rho e) | BinE (ot, e1, bo, e2)-> BinE (ot, exp rho e1, bo, exp rho e2) | RelE (ot, e1, ro, e2)-> RelE (ot, exp rho e1, ro, exp rho e2) + | ShowE (ot, e) -> ShowE (ot, exp rho e) | TupE es -> TupE (List.map (exp rho) es) | ProjE (e, i) -> ProjE (exp rho e, i) | ActorE (i, ds, fs, t)-> let i',rho' = id_bind rho i in diff --git a/src/serialization.ml b/src/serialization.ml index 65454d1fb52..2d17b857712 100644 --- a/src/serialization.ml +++ b/src/serialization.ml @@ -162,6 +162,8 @@ module Transform() = struct BinE (t_typ ot, t_exp exp1, op, t_exp exp2) | RelE (ot, exp1, op, exp2) -> RelE (t_typ ot, t_exp exp1, op, t_exp exp2) + | ShowE (ot, exp1) -> + ShowE (t_typ ot, t_exp exp1) | TupE exps -> TupE (List.map t_exp exps) | OptE exp1 -> diff --git a/src/show.ml b/src/show.ml new file mode 100644 index 00000000000..d3900cf03ed --- /dev/null +++ b/src/show.ml @@ -0,0 +1,446 @@ +(* Translates away calls to `show`. *) +open Source +open Ir +module T = Type +open Construct + +(* A type identifier *) + +(* This needs to map types to some identifier with the following properties: + + - Its domain are normalized types that do not mention any type parameters + - It needs to be injective wrt. type equality + - It needs to terminate, even for recursive types + - It may fail upon type parameters (i.e. no polymorphism) + +We can use string_of_typ here for now, it seems. +*) + +let typ_id : T.typ -> string = + T.string_of_typ + +(* Environmemt *) + +(* We go through the file and collect all type arguments to `show`. + We store them in `params`, indexed by their `type_id` +*) + +module M = Map.Make(String) +type env = + { params : T.typ M.t ref + } + +let empty_env : env = { + params = ref M.empty; + } + +let add_type env t : unit = + env.params := M.add (typ_id t) t !(env.params) + +(* Function names *) + +(* For a concrete type `t` we want to create a function name for `show`. + This name needs to be disjoint from all user-generated names. + Luckily, we are not limited in the characters to use at this point: +*) + +let show_name_for t = + "@show<" ^ typ_id t ^ ">" + +let show_fun_typ_for t = + T.Func (T.Local, T.Returns, [], [t], [T.Prim T.Text]) + +let show_var_for t : Construct.var = + idE (show_name_for t @@ no_region) (show_fun_typ_for t) + +(* The AST traversal *) + +let rec t_exps env decs = List.map (t_exp env) decs + +and t_exp env (e : Ir.exp) = + { e with it = t_exp' env e.it } + +and t_exp' env = function + | PrimE p -> PrimE p + | LitE l -> LitE l + | VarE id -> VarE id + | ShowE (ot, exp1) -> + let t' = T.normalize ot in + add_type env t'; + let f = idE (show_name_for t' @@ no_region) (show_fun_typ_for t') in + CallE(Value.local_cc 1 1, f, [], t_exp env exp1) + | UnE (ot, op, exp1) -> + UnE (ot, op, t_exp env exp1) + | BinE (ot, exp1, op, exp2) -> + BinE (ot, t_exp env exp1, op, t_exp env exp2) + | RelE (ot, exp1, op, exp2) -> + RelE (ot, t_exp env exp1, op, t_exp env exp2) + | TupE exps -> TupE (t_exps env exps) + | OptE exp1 -> + OptE (t_exp env exp1) + | ProjE (exp1, n) -> + ProjE (t_exp env exp1, n) + | ActorE (id, ds, fields, typ) -> + ActorE (id, t_decs env ds, fields, typ) + | DotE (exp1, id) -> + DotE (t_exp env exp1, id) + | ActorDotE (exp1, id) -> + ActorDotE (t_exp env exp1, id) + | AssignE (exp1, exp2) -> + AssignE (t_exp env exp1, t_exp env exp2) + | ArrayE (mut, t, exps) -> + ArrayE (mut, t, t_exps env exps) + | IdxE (exp1, exp2) -> + IdxE (t_exp env exp1, t_exp env exp2) + | FuncE (cc, id, typbinds, pat, typT, exp) -> + FuncE (cc, id, typbinds, pat, typT, t_exp env exp) + | CallE (cc, exp1, typs, exp2) -> + CallE(cc, t_exp env exp1, typs, t_exp env exp2) + | BlockE block -> BlockE (t_block env block) + | IfE (exp1, exp2, exp3) -> + IfE (t_exp env exp1, t_exp env exp2, t_exp env exp3) + | SwitchE (exp1, cases) -> + let cases' = List.map + (fun {it = {pat;exp}; at; note} -> + {it = {pat = pat; exp = t_exp env exp}; at; note}) + cases + in + SwitchE (t_exp env exp1, cases') + | LoopE exp1 -> + LoopE (t_exp env exp1) + | LabelE (id, typ, exp1) -> + LabelE (id, typ, t_exp env exp1) + | BreakE (id, exp1) -> + BreakE (id, t_exp env exp1) + | RetE exp1 -> + RetE (t_exp env exp1) + | AsyncE e -> AsyncE (t_exp env e) + | AwaitE e -> AwaitE (t_exp env e) + | AssertE exp1 -> + AssertE (t_exp env exp1) + | DeclareE (id, typ, exp1) -> + DeclareE (id, typ, t_exp env exp1) + | DefineE (id, mut ,exp1) -> + DefineE (id, mut, t_exp env exp1) + | NewObjE (sort, ids, t) -> + NewObjE (sort, ids, t) + | VariantE (l, exp1) -> + VariantE (l, t_exp env exp1) + +and t_dec env dec = { dec with it = t_dec' env dec.it } + +and t_dec' env dec' = + match dec' with + | TypD con_id -> TypD con_id + | LetD (pat,exp) -> LetD (pat,t_exp env exp) + | VarD (id,exp) -> VarD (id,t_exp env exp) + +and t_decs env decs = List.map (t_dec env) decs + +and t_block env (ds, exp) = (t_decs env ds, t_exp env exp) + +and t_prog env (prog, flavor) = (t_block env prog, flavor) + + +(* Construction helpers *) + +(* Many of these are simply the entry points for helper functions defined in + the prelude. *) + +let argE t = idE ("x" @@ no_region) t + +let define_show : T.typ -> Ir.exp -> Ir.dec = fun t e -> + Construct.funcD (show_var_for t) (argE t) e + +let text_exp : Ir.exp' -> Ir.exp = fun e -> + { it = e; + at = no_region; + note = { note_typ = T.Prim T.Text; note_eff = T.Triv } + } + +let invoke_generated_show : T.typ -> Ir.exp -> Ir.exp = fun t e -> + text_exp (CallE (Value.local_cc 1 1, show_var_for t, [], e)) + +let invoke_prelude_show : string -> T.typ -> Ir.exp -> Ir.exp = fun n t e -> + let fun_typ = T.Func (T.Local, T.Returns, [], [t], [T.Prim T.Text]) in + text_exp (CallE + ( Value.local_cc 1 1 + , { it = VarE (n @@ no_region) + ; at = no_region + ; note = { note_typ = fun_typ; note_eff = T.Triv } + } + , [] + , argE t + ) + ) + +let invoke_text_of_option : T.typ -> Ir.exp -> Ir.exp -> Ir.exp = fun t f e -> + let fun_typ = + T.Func (T.Local, T.Returns, [{T.var="T";T.bound=T.Any}], [show_fun_typ_for (T.Var ("T",0)); T.Opt (T.Var ("T",0))], [T.Prim T.Text]) in + text_exp (CallE + ( Value.local_cc 2 1 + , { it = VarE ("@text_of_option" @@ no_region) + ; at = no_region + ; note = { note_typ = fun_typ; note_eff = T.Triv } + } + , [t] + , { it = TupE [f; e] + ; at = no_region + ; note = { note_typ = T.Tup [show_fun_typ_for t; T.Opt t]; note_eff = T.Triv } + } + ) + ) + +let invoke_text_of_variant : T.typ -> Ir.exp -> T.lab -> Ir.exp -> Ir.exp = fun t f l e -> + let fun_typ = + T.Func (T.Local, T.Returns, [{T.var="T";T.bound=T.Any}], [T.Prim T.Text; show_fun_typ_for (T.Var ("T",0)); T.Var ("T",0)], [T.Prim T.Text]) in + text_exp (CallE + ( Value.local_cc 3 1 + , { it = VarE ("@text_of_variant" @@ no_region) + ; at = no_region + ; note = { note_typ = fun_typ; note_eff = T.Triv } + } + , [t] + , { it = TupE [textE l; f; e] + ; at = no_region + ; note = { note_typ = T.Tup [T.Prim T.Text; show_fun_typ_for t; t]; note_eff = T.Triv } + } + ) + ) + +let invoke_text_of_array : T.typ -> Ir.exp -> Ir.exp -> Ir.exp = fun t f e -> + let fun_typ = + T.Func (T.Local, T.Returns, [{T.var="T";T.bound=T.Any}], [show_fun_typ_for (T.Var ("T",0)); T.Array (T.Var ("T",0))], [T.Prim T.Text]) in + text_exp (CallE + ( Value.local_cc 2 1 + , { it = VarE ("@text_of_array" @@ no_region) + ; at = no_region + ; note = { note_typ = fun_typ; note_eff = T.Triv } + } + , [t] + , { it = TupE [f; e] + ; at = no_region + ; note = { note_typ = T.Tup [show_fun_typ_for t; T.Array t]; note_eff = T.Triv } + } + ) + ) + +let invoke_text_of_array_mut : T.typ -> Ir.exp -> Ir.exp -> Ir.exp = fun t f e -> + let fun_typ = + T.Func (T.Local, T.Returns, [{T.var="T";T.bound=T.Any}], [show_fun_typ_for (T.Var ("T",0)); T.Array (T.Mut (T.Var ("T",0)))], [T.Prim T.Text]) in + text_exp (CallE + ( Value.local_cc 2 1 + , { it = VarE ("@text_of_array_mut" @@ no_region) + ; at = no_region + ; note = { note_typ = fun_typ; note_eff = T.Triv } + } + , [t] + , { it = TupE [f; e] + ; at = no_region + ; note = { note_typ = T.Tup [show_fun_typ_for t; T.Array (T.Mut t)]; note_eff = T.Triv } + } + ) + ) + +let list_build : 'a -> 'a -> 'a -> 'a list -> 'a list = fun pre sep post xs -> + let rec go = function + | [] -> [ post ] + | [x] -> [ x; post ] + | x::xs -> [ x; sep ] @ go xs + in [ pre ] @ go xs + +let catE : Ir.exp -> Ir.exp -> Ir.exp = fun e1 e2 -> + { it = BinE (T.Prim T.Text, e1, Syntax.CatOp, e2) + ; at = no_region + ; note = { note_typ = T.Prim T.Text; note_eff = T.Triv } + } + +let cat_list : Ir.exp list -> Ir.exp = fun es -> + List.fold_right catE es (textE "") + +(* Synthesizing a single show function *) + +(* Returns the new declarations, as well as a list of further types it needs *) + + +let show_for : T.typ -> Ir.dec * T.typ list = fun t -> + match t with + | T.Prim T.Bool -> + define_show t (invoke_prelude_show "@text_of_Bool" t (argE t)), + [] + | T.Prim T.Nat -> + define_show t (invoke_prelude_show "@text_of_Nat" t (argE t)), + [] + | T.Prim T.Int -> + define_show t (invoke_prelude_show "@text_of_Int" t (argE t)), + [] + | T.Prim T.Text -> + define_show t (invoke_prelude_show "@text_of_Text" t (argE t)), + [] + | T.Prim T.Null -> + define_show t (textE ("null")), + [] + | T.Func _ -> + define_show t (textE ("func")), + [] + | T.Con (c,_) -> + (* t is normalized, so this is a type parameter *) + define_show t (textE ("show_for: cannot handle type parameter " ^ T.string_of_typ t)), + [] + | T.Tup [] -> + define_show t (textE ("()")), + [] + | T.Tup ts' -> + let ts' = List.map T.normalize ts' in + define_show t ( + cat_list (list_build + (textE "(") (textE ", ") (textE ")") + (List.mapi (fun i t' -> + invoke_generated_show t' ( + { it = ProjE (argE t, i) + ; at = no_region + ; note = { note_typ = t'; note_eff = T.Triv } + } + ) + ) ts') + ) + ), + ts' + | T.Opt t' -> + let t' = T.normalize t' in + define_show t (invoke_text_of_option t' (show_var_for t') (argE t)), + [t'] + | T.Array t' -> + let t' = T.normalize t' in + begin match t' with + | T.Mut t' -> + define_show t (invoke_text_of_array_mut t' (show_var_for t') (argE t)), + [t'] + | _ -> + define_show t (invoke_text_of_array t' (show_var_for t') (argE t)), + [t'] + end + | T.Obj (T.Object _, fs) -> + define_show t ( + cat_list (list_build + (textE "{") (textE "; ") (textE "}") + (List.map (fun f -> + let t' = T.as_immut (T.normalize f.Type.typ) in + catE + (textE (f.Type.lab ^ " = ")) + (invoke_generated_show t' + { it = DotE (argE t, Ir.Name f.Type.lab @@ no_region ) + ; at = no_region + ; note = { note_typ = t'; note_eff = T.Triv } + } + ) + ) fs + ) + ) + ), + List.map (fun f -> T.as_immut (T.normalize (f.Type.typ))) fs + | T.Variant cts -> + define_show t ( + switch_variantE + (argE t) + (List.map (fun (l, t') -> + let t' = T.normalize t' in + l @@ no_region, + (varP (argE t')), (* Shadowing, but thats fine *) + (invoke_text_of_variant t' (show_var_for t') l (argE t')) + ) cts) + (T.Prim T.Text) + ), + List.map (fun (_l, t') -> T.normalize t') cts + | _ -> assert false (* Should be prevented by can_show *) + +(* Synthesizing the types recursively. Hopefully well-founded. *) + +let show_decls : T.typ M.t -> Ir.dec list = fun roots -> + let seen = ref M.empty in + + let rec go = function + | [] -> [] + | t::todo when M.mem (typ_id t) !seen -> + go todo + | t::todo -> + seen := M.add (typ_id t) () !seen; + let (decl, deps) = show_for t in + decl :: go (deps @ todo) + in go (List.map snd (M.bindings roots)) + +(* Entry point for type checking: *) + +let rec can_show t = + let t = T.normalize t in + match t with + | T.Prim T.Bool + | T.Prim T.Nat + | T.Prim T.Int + | T.Prim T.Text + | T.Prim T.Null -> true + | T.Tup ts' -> List.for_all can_show ts' + | T.Opt t' -> can_show t' + | T.Array t' -> can_show (T.as_immut t') + | T.Obj (T.Object _, fs) -> + List.for_all (fun f -> can_show (T.as_immut f.T.typ)) fs + | T.Variant cts -> + List.for_all (fun (l,t) -> can_show t) cts + | _ -> false + +(* Entry point for the interpreter (reference implementation) *) + +let rec show_val t v = + let t = T.normalize t in + match t, v with + | T.Prim T.Bool, Value.Bool b -> if b then "true" else "false" + | T.Prim T.Nat, Value.Int i -> Value.Int.to_string i + | T.Prim T.Int, Value.Int i -> Value.Int.to_string i + | T.Prim T.Text, Value.Text s -> "\"" ^ s ^ "\"" + | T.Prim T.Null, Value.Null -> "null" + | T.Opt _, Value.Null -> "null" + | T.Opt t', Value.Opt v -> "?(" ^ show_val t' v ^ ")" + | T.Tup ts', Value.Tup vs -> + Printf.sprintf "(%s%s)" + (String.concat ", " (List.map2 show_val ts' vs)) + (if List.length vs = 1 then "," else "") + | T.Array (T.Mut t'), Value.Array a -> + Printf.sprintf "[var %s]" + (String.concat ", " (List.map (fun v -> show_val t' !(Value.as_mut v)) (Array.to_list a))) + | T.Array t', Value.Array a -> + Printf.sprintf "[%s]" + (String.concat ", " (List.map (show_val t') (Array.to_list a))) + | T.Obj (_, fts), Value.Obj fs -> + Printf.sprintf "{%s}" (String.concat "; " (List.map (show_field fs) fts)) + | T.Variant cts, Value.Variant (l, v) -> + begin match List.find_opt (fun (l',t) -> l = l') cts with + | Some (_, t') -> Printf.sprintf "(#%s %s)" l (show_val t' v) + | _ -> assert false + end + | _ -> + Printf.eprintf "show_val: %s : %s\n" (Value.string_of_val v) (T.string_of_typ t); + assert false + +and show_field fs ft = + let v = Value.Env.find ft.T.lab fs in + let m, t', v' = + match ft.T.typ with + | T.Mut t' -> "var ", t', !(Value.as_mut v) + | t' -> "", t', v + in + (* With types: + Printf.sprintf "%s%s : %s = %s" m ft.T.name (T.string_of_typ t') (show_val t' v') + *) + Printf.sprintf "%s = %s" ft.T.lab (show_val t' v') + +(* Entry point for the program transformation *) + +let transform scope prog = + let env = empty_env in + (* Find all parameters to show in the program *) + let prog = t_prog env prog in + (* Create declarations for them *) + let decls = show_decls !(env.params) in + (* Add them to the program *) + let prog' = let ((d,e),f) = prog in ((decls @ d,e), { f with has_show = false }) in + prog'; diff --git a/src/show.mli b/src/show.mli new file mode 100644 index 00000000000..6f740910ece --- /dev/null +++ b/src/show.mli @@ -0,0 +1,5 @@ +val can_show : Type.typ -> bool + +val show_val : Type.typ -> Value.value -> string + +val transform : 'a -> Ir.prog -> Ir.prog diff --git a/src/syntax.ml b/src/syntax.ml index 39d2af3d984..f22210141ee 100644 --- a/src/syntax.ml +++ b/src/syntax.ml @@ -130,6 +130,7 @@ and exp' = | UnE of op_typ * unop * exp (* unary operator *) | BinE of op_typ * exp * binop * exp (* binary operator *) | RelE of op_typ * exp * relop * exp (* relational operator *) + | ShowE of (op_typ * exp) (* debug show operator *) | TupE of exp list (* tuple *) | ProjE of exp * int (* tuple projection *) | OptE of exp (* option injection *) diff --git a/src/tailcall.ml b/src/tailcall.ml index 23be775a084..24bf31329a5 100644 --- a/src/tailcall.ml +++ b/src/tailcall.ml @@ -94,6 +94,7 @@ and exp' env e : exp' = match e.it with | UnE (ot, uo, e) -> UnE (ot, uo, exp env e) | BinE (ot, e1, bo, e2)-> BinE (ot, exp env e1, bo, exp env e2) | RelE (ot, e1, ro, e2)-> RelE (ot, exp env e1, ro, exp env e2) + | ShowE (ot, e) -> ShowE (ot, exp env e) | TupE es -> TupE (List.map (exp env) es) | ProjE (e, i) -> ProjE (exp env e, i) | DotE (e, sn) -> DotE (exp env e, sn) diff --git a/src/typing.ml b/src/typing.ml index 3a243eb3efa..44c046dead6 100644 --- a/src/typing.ml +++ b/src/typing.ml @@ -379,6 +379,15 @@ and infer_exp'' env exp : T.typ = ot := t; end; t + | ShowE (ot, exp1) -> + let t = infer_exp_promote env exp1 in + if not env.pre then begin + if not (Show.can_show t) then + error env exp.at "show is not defined for operand type\n %s" + (T.string_of_typ_expand t); + ot := t + end; + T.Prim T.Text | BinE (ot, exp1, op, exp2) -> let t1 = infer_exp_promote env exp1 in let t2 = infer_exp_promote env exp2 in diff --git a/test/run-dfinity/counter-class.as b/test/run-dfinity/counter-class.as index d6b04c2b19c..5015189f053 100644 --- a/test/run-dfinity/counter-class.as +++ b/test/run-dfinity/counter-class.as @@ -2,14 +2,14 @@ actor class Counter(i : Int) { private var c = i; dec() { - show(c); + showCounter(c); c -= 1; }; read() : async Int { c }; }; -func show(c : Int) {}; +func showCounter(c : Int) {}; let c = Counter(10); diff --git a/test/run-dfinity/ok/counter-class.wasm-run.ok b/test/run-dfinity/ok/counter-class.wasm-run.ok new file mode 100644 index 00000000000..98d3e74e43f --- /dev/null +++ b/test/run-dfinity/ok/counter-class.wasm-run.ok @@ -0,0 +1 @@ +_out/counter-class.wasm:0x___: runtime trap: unreachable executed diff --git a/test/run-dfinity/ok/counter-class.wasm.stderr.ok b/test/run-dfinity/ok/counter-class.wasm.stderr.ok index 024d434dd75..f0ac22d3b03 100644 --- a/test/run-dfinity/ok/counter-class.wasm.stderr.ok +++ b/test/run-dfinity/ok/counter-class.wasm.stderr.ok @@ -8,7 +8,7 @@ non-closed actor: (ActorE (shared 0 -> 0) () (BlockE - (LetD WildP (CallE ( 1 -> 0) (VarE show) (VarE c))) + (LetD WildP (CallE ( 1 -> 0) (VarE showCounter) (VarE c))) (AssignE (VarE c) (BinE Int (VarE c) SubOp (LitE (IntLit 1)))) ) ) diff --git a/test/run/ok/show.run-ir.ok b/test/run/ok/show.run-ir.ok new file mode 100644 index 00000000000..9ebf68ef2fa --- /dev/null +++ b/test/run/ok/show.run-ir.ok @@ -0,0 +1,13 @@ +true +false +-42 +42 +(42, -42, ()) +("Foobar", null, null, ?(23)) +[1, 2, 3] +[var 1, 2, 3] +{bar = true; foo = 42} +{bar = true; foo = 42} +(#foo ()) +(#bar 42) +(#foo 42) diff --git a/test/run/ok/show.run-low.ok b/test/run/ok/show.run-low.ok new file mode 100644 index 00000000000..9ebf68ef2fa --- /dev/null +++ b/test/run/ok/show.run-low.ok @@ -0,0 +1,13 @@ +true +false +-42 +42 +(42, -42, ()) +("Foobar", null, null, ?(23)) +[1, 2, 3] +[var 1, 2, 3] +{bar = true; foo = 42} +{bar = true; foo = 42} +(#foo ()) +(#bar 42) +(#foo 42) diff --git a/test/run/ok/show.run.ok b/test/run/ok/show.run.ok new file mode 100644 index 00000000000..9ebf68ef2fa --- /dev/null +++ b/test/run/ok/show.run.ok @@ -0,0 +1,13 @@ +true +false +-42 +42 +(42, -42, ()) +("Foobar", null, null, ?(23)) +[1, 2, 3] +[var 1, 2, 3] +{bar = true; foo = 42} +{bar = true; foo = 42} +(#foo ()) +(#bar 42) +(#foo 42) diff --git a/test/run/ok/show.wasm-run.ok b/test/run/ok/show.wasm-run.ok new file mode 100644 index 00000000000..40bf7666810 --- /dev/null +++ b/test/run/ok/show.wasm-run.ok @@ -0,0 +1 @@ +_out/show.wasm:0x___: runtime trap: unreachable executed diff --git a/test/run/show.as b/test/run/show.as new file mode 100644 index 00000000000..45cfe6f72a3 --- /dev/null +++ b/test/run/show.as @@ -0,0 +1,15 @@ +func printLn(x : Text) { print(x # "\n"); }; +printLn(debug_show (true)); +printLn(debug_show (false)); +printLn(debug_show (-42)); +printLn(debug_show (42)); +printLn(debug_show (42,-42,())); +printLn(debug_show ("Foobar", null, null, ?23)); +printLn(debug_show ([1,2,3])); +printLn(debug_show ([var 1,2,3])); +class Foo() { let foo : Int = 42; var bar : Bool = true ; private hidden = [1,2] }; +printLn(debug_show (Foo())); +printLn(debug_show (Foo())); +printLn(debug_show (#foo ())); +printLn(debug_show (#bar 42)); +printLn(debug_show ((#foo 42): {#foo : Int; #bar : Text}));