diff --git a/src/arrange.ml b/src/arrange.ml index d2bc40e16c0..80a48339b47 100644 --- a/src/arrange.ml +++ b/src/arrange.ml @@ -68,6 +68,7 @@ and unop uo = match uo with | PosOp -> Atom "PosOp" | NegOp -> Atom "NegOp" | NotOp -> Atom "NotOp" + | ShowOp -> Atom "ShowOp" and binop bo = match bo with | AddOp -> Atom "AddOp" diff --git a/src/check_ir.ml b/src/check_ir.ml index 46b8b077630..5a5e15a4a7f 100644 --- a/src/check_ir.ml +++ b/src/check_ir.ml @@ -63,10 +63,12 @@ let with_check_typ check_typ env = { env with check_typ } (* More error bookkeeping *) +exception CheckFailed of string + let type_error at text : Diag.message = Diag.{ sev = Diag.Error; at; cat = "IR type"; text } let error env at fmt = - Printf.ksprintf (fun s -> failwith (Diag.string_of_message (type_error at s))) fmt + Printf.ksprintf (fun s -> raise (CheckFailed (Diag.string_of_message (type_error at s)))) fmt let add_lab c x t = {c with labs = T.Env.add x t c.labs} @@ -101,7 +103,7 @@ let check env at p = let check_sub env at t1 t2 = if T.sub t1 t2 then () - else error env at "subtype violation %s %s" (T.string_of_typ t1) (T.string_of_typ t2) + else error env at "subtype violation (%s) (%s)" (T.string_of_typ t1) (T.string_of_typ t2) let make_mut mut : T.typ -> T.typ = match mut.it with @@ -265,7 +267,9 @@ 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; + if op = Syntax.ShowOp + then T.Prim T.Text <: t + else 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; @@ -766,5 +770,9 @@ and gather_dec env scope dec : scope = (* Programs *) let check_prog env prog : unit = - check_block env T.unit prog.it prog.at + try + check_block env T.unit prog.it prog.at + with CheckFailed s -> + Wasm.Sexpr.print 80 (Arrange_ir.prog prog); + failwith s diff --git a/src/lexer.mll b/src/lexer.mll index 60d73a13047..aa4cf4158e2 100644 --- a/src/lexer.mll +++ b/src/lexer.mll @@ -200,6 +200,7 @@ rule token mode = parse | "private" { PRIVATE } | "return" { RETURN } | "shared" { SHARED } + | "show" { SHOW } | "switch" { SWITCH } | "true" { BOOL true } | "type" { TYPE } diff --git a/src/operator.ml b/src/operator.ml index 1bdc2c27e4d..889b934495b 100644 --- a/src/operator.ml +++ b/src/operator.ml @@ -22,15 +22,13 @@ let num_unop fint fword8 fword16 fword32 fword64 ffloat = function | t -> word_unop fword8 fword16 fword32 fword64 t let unop t op = - match t with - | T.Prim p -> - (match op with - | PosOp -> let id v = v in num_unop id id id id id id p - | NegOp -> num_unop Int.neg Word8.neg Word16.neg Word32.neg Word64.neg Float.neg p - | NotOp -> word_unop Word8.not Word16.not Word32.not Word64.not p - ) - | T.Non -> impossible - | _ -> raise (Invalid_argument "unop") + match t, op with + | T.Prim p, PosOp -> let id v = v in num_unop id id id id id id p + | T.Prim p, NegOp -> num_unop Int.neg Word8.neg Word16.neg Word32.neg Word64.neg Float.neg p + | T.Prim p, NotOp -> word_unop Word8.not Word16.not Word32.not Word64.not p + | t, ShowOp when Show.can_show t -> fun v -> Text (Show.show_val t v) + | T.Non, _ -> impossible + | _, _ -> raise (Invalid_argument "unop") (* Binary operators *) diff --git a/src/parser.mly b/src/parser.mly index 3d205e2c8c3..b86b33dc368 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 SHOW %token ASSERT %token ADDOP SUBOP MULOP DIVOP MODOP POWOP %token ANDOP OROP XOROP SHLOP SHROP ROTLOP ROTROP @@ -367,6 +368,8 @@ exp_un : { assign_op e (fun e' -> UnE(ref Type.Pre, op, e') @? at $sloc) (at $sloc) } | NOT e=exp_un { NotE e @? at $sloc } + | SHOW e=exp_un + { UnE (ref Type.Pre, ShowOp, e) @? at $sloc } exp_bin : | e=exp_un diff --git a/src/pipeline.ml b/src/pipeline.ml index f85252b9b39..8d4915bfed2 100644 --- a/src/pipeline.ml +++ b/src/pipeline.ml @@ -136,6 +136,9 @@ let async_lowering = let tailcall_optimization = transform_ir "Tailcall optimization" Tailcall.transform +let show_translation = + transform_ir "Translate show" Show_pass.transform + let check_with parse infer senv name : check_result = match parse name with | Error e -> Error [e] @@ -167,7 +170,8 @@ let interpret_prog (senv,denv) name prog : (Value.value * Interpret.scope) optio then let prog_ir = Desugar.transform senv prog in let prog_ir = await_lowering (!Flags.await_lowering) senv prog_ir name in - let prog_ir = async_lowering (!Flags.await_lowering && !Flags.async_lowering) senv prog_ir name in + let prog_ir = async_lowering (!Flags.await_lowering && !Flags.async_lowering) 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 @@ -293,6 +297,7 @@ let compile_with check mode name : compile_result = let prog = await_lowering true initial_stat_env prog name in let prog = async_lowering true initial_stat_env prog name in let prog = tailcall_optimization true initial_stat_env prog name in + let prog = show_translation true initial_stat_env prog name in phase "Compiling" name; let module_ = Compile.compile mode name prelude [prog] in Ok module_ diff --git a/src/prelude.ml b/src/prelude.ml index 2d01a864e1f..10f4be49db0 100644 --- a/src/prelude.ml +++ b/src/prelude.ml @@ -34,6 +34,94 @@ class revrange(x : Nat, y : Nat) { func printInt(x : Int) { (prim "printInt" : Int -> ()) x }; func print(x : Text) { (prim "print" : Text -> ()) x }; +// 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_option(f : T -> Text, x : ?T) : Text { + switch (x) { + case (?y) {"?(" # f y # ")"}; + case null {"null"}; + } +}; + +func @text_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_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/show.ml b/src/show.ml new file mode 100644 index 00000000000..379ffb58bbc --- /dev/null +++ b/src/show.ml @@ -0,0 +1,65 @@ +(* +Specification for `show`: Type predicate and reference implementation + +The actual show-lowering pass is in in Show_pass, because Show_pass imports +check_ir, but check_ir needs can_show +*) + +module T = Type + +(* 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 + | _ -> 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)) + | _ -> + 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.name 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.name (show_val t' v') diff --git a/src/show.mli b/src/show.mli new file mode 100644 index 00000000000..f733046e776 --- /dev/null +++ b/src/show.mli @@ -0,0 +1,2 @@ +val can_show : Type.typ -> bool +val show_val : Type.typ -> Value.value -> string diff --git a/src/show_pass.ml b/src/show_pass.ml new file mode 100644 index 00000000000..e2d35246b07 --- /dev/null +++ b/src/show_pass.ml @@ -0,0 +1,377 @@ +(* 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]) + +(* 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 + | UnE (ot, Syntax.ShowOp, 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, fields, typ) -> + ActorE (id, t_fields env 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) + | CallE (cc, exp1, typs, exp2) -> + CallE(cc, t_exp env exp1, typs, t_exp env exp2) + | BlockE (decs, ot) -> + BlockE (t_decs env decs, ot) + | 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') + | WhileE (exp1, exp2) -> + WhileE (t_exp env exp1, t_exp env exp2) + | LoopE (exp1, exp2_opt) -> + LoopE (t_exp env exp1, Lib.Option.map (t_exp env) exp2_opt) + | ForE (pat, exp1, exp2) -> + ForE (pat, t_exp env exp1, t_exp env exp2) + | 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) + +and t_dec env dec = { dec with it = t_dec' env dec.it } + +and t_dec' env dec' = + match dec' with + | ExpD exp -> ExpD (t_exp env exp) + | TypD con_id -> TypD con_id + | LetD (pat,exp) -> LetD (pat,t_exp env exp) + | VarD (id,exp) -> VarD (id,t_exp env exp) + | FuncD (cc, id, typbinds, pat, typT, exp) -> + FuncD (cc, id, typbinds, pat, typT, t_exp env exp) + +and t_decs env decs = List.map (t_dec env) decs + +and t_fields env fields = + List.map (fun (field:Ir.exp_field) -> + { field with it = { field.it with exp = t_exp env field.it.exp } }) + fields + +and t_prog env prog:prog = { prog with it = t_decs env prog.it } + +(* Construction helpers *) + +(* Many of these are simply the entry points for helper functions defined in + the prelude. *) + +let arg_id = "x" @@ no_region + +let argE t = + { it = VarE arg_id + ; at = no_region + ; note = { note_typ = t; note_eff = T.Triv } + } + +let define_show : T.typ -> Ir.exp -> Ir.dec = fun t e -> + { it = FuncD ( + Value.local_cc 1 1, + show_name_for t @@ no_region, + [], + { it = VarP arg_id; at = no_region; note = t }, + T.Prim T.Text, + e + ); + at = no_region; + note = { note_typ = show_fun_typ_for t; note_eff = T.Triv } + } + +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 use_generated_show : T.typ -> Ir.exp = fun t -> + idE (show_name_for t @@ no_region) (show_fun_typ_for t) + +let invoke_generated_show : T.typ -> Ir.exp -> Ir.exp = fun t e -> + text_exp (CallE (Value.local_cc 1 1, use_generated_show 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_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_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_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_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_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_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,_) -> + 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_option t' (use_generated_show 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_array_mut t' (use_generated_show t') (argE t)), + [t'] + | _ -> + define_show t (invoke_text_array t' (use_generated_show 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.name ^ " = ")) + (invoke_generated_show t' + { it = DotE (argE t, Syntax.Name f.Type.name @@ 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 + | _ -> 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 the program transformation *) + +let check_prog scope prog = + Check_ir.check_prog (Check_ir.env_of_scope scope) prog + +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' = { prog with it = decls @ prog.it} in + check_prog scope prog'; + prog'; diff --git a/src/show_pass.mli b/src/show_pass.mli new file mode 100644 index 00000000000..60e9a77c4ec --- /dev/null +++ b/src/show_pass.mli @@ -0,0 +1 @@ +val transform : Typing.scope -> Ir.prog -> Ir.prog diff --git a/src/syntax.ml b/src/syntax.ml index a6060832601..08a2b5cf40b 100644 --- a/src/syntax.ml +++ b/src/syntax.ml @@ -69,6 +69,7 @@ type unop = | PosOp (* +x *) | NegOp (* -x *) | NotOp (* bitwise negation *) + | ShowOp (* magic show *) type binop = | AddOp (* x+y *) diff --git a/src/typing.ml b/src/typing.ml index 9d87691084f..1f85965dc13 100644 --- a/src/typing.ml +++ b/src/typing.ml @@ -355,7 +355,9 @@ and infer_exp' env exp : T.typ = (T.string_of_typ_expand t); ot := t; end; - t + if op = ShowOp + then T.Prim T.Text + else t | BinE (ot, exp1, op, exp2) -> let t1 = infer_exp_promote env exp1 in let t2 = infer_exp_promote env exp2 in @@ -610,7 +612,7 @@ and check_exp' env t exp = () | LitE lit, _ -> check_lit env t lit exp.at - | UnE (ot, op, exp1), t' when Operator.has_unop t' op -> + | UnE (ot, op, exp1), t' when op <> ShowOp && Operator.has_unop t' op -> ot := t'; check_exp env t' exp1 | BinE (ot, exp1, op, exp2), t' when Operator.has_binop t' op -> diff --git a/src/value.ml b/src/value.ml index 6bd6e7b060d..149e36dc46c 100644 --- a/src/value.ml +++ b/src/value.ml @@ -360,7 +360,7 @@ let rec string_of_val_nullary d = function (String.concat ", " (List.map (string_of_val' d) vs)) (if List.length vs = 1 then "," else "") | Opt v -> - sprintf "%s?" (string_of_val_nullary d v) + sprintf "?%s" (string_of_val_nullary d v) | Obj ve -> if d = 0 then "{...}" else sprintf "{%s}" (String.concat "; " (List.map (fun (x, v) -> 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.stderr.ok b/test/run-dfinity/ok/counter-class.wasm.stderr.ok index 04999878853..c50b2094c09 100644 --- a/test/run-dfinity/ok/counter-class.wasm.stderr.ok +++ b/test/run-dfinity/ok/counter-class.wasm.stderr.ok @@ -10,7 +10,7 @@ non-closed actor: (ActorE (TupP) () (BlockE - (ExpD (CallE ( 1 -> 0) (VarE show) (VarE c))) + (ExpD (CallE ( 1 -> 0) (VarE showCounter) (VarE c))) (ExpD (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..385fb993b15 --- /dev/null +++ b/test/run/ok/show.run-ir.ok @@ -0,0 +1,10 @@ +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} diff --git a/test/run/ok/show.run-low.ok b/test/run/ok/show.run-low.ok new file mode 100644 index 00000000000..385fb993b15 --- /dev/null +++ b/test/run/ok/show.run-low.ok @@ -0,0 +1,10 @@ +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} diff --git a/test/run/ok/show.run.ok b/test/run/ok/show.run.ok new file mode 100644 index 00000000000..385fb993b15 --- /dev/null +++ b/test/run/ok/show.run.ok @@ -0,0 +1,10 @@ +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} 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..485b1681163 --- /dev/null +++ b/test/run/show.as @@ -0,0 +1,12 @@ +func printLn(x : Text) { print(x # "\n"); }; +printLn(show (true)); +printLn(show (false)); +printLn(show (-42)); +printLn(show (42)); +printLn(show (42,-42,())); +printLn(show ("Foobar", null, null, ?23)); +printLn(show ([1,2,3])); +printLn(show ([var 1,2,3])); +class Foo() { foo : Int = 42; var bar : Bool = true ; private hidden = [1,2] }; +printLn(show (Foo())); +printLn(show (Foo()));