From e6e82c0866db4eb08f956b2582e5c2ed5399e986 Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Tue, 18 Sep 2018 14:43:58 -0400 Subject: [PATCH] Detect cycles in type declarations --- README.md | 6 ++- compiler/src/lib/tiger/tiger_dag.ml | 39 +++++++++++++++ compiler/src/lib/tiger/tiger_dag.mli | 3 ++ compiler/src/lib/tiger/tiger_error.ml | 46 ++++++++++++++++++ compiler/src/lib/tiger/tiger_error.mli | 7 +++ compiler/src/lib/tiger/tiger_map_red_black.ml | 6 +++ compiler/src/lib/tiger/tiger_map_sig.ml | 3 +- compiler/src/lib/tiger/tiger_semant.ml | 35 +++++++++++++ compiler/src/lib/tiger/tiger_test_cases.ml | 24 +++++++++ .../src/lib/tiger/tiger_test_cases_book.ml | 3 ++ ...ts-semant-done-head.jpg => tests-head.jpg} | Bin screenshots/tests-semant-done-tail.jpg | Bin 3677 -> 0 bytes screenshots/tests-tail.jpg | Bin 0 -> 6236 bytes 13 files changed, 169 insertions(+), 3 deletions(-) create mode 100644 compiler/src/lib/tiger/tiger_dag.ml create mode 100644 compiler/src/lib/tiger/tiger_dag.mli rename screenshots/{tests-semant-done-head.jpg => tests-head.jpg} (100%) delete mode 100644 screenshots/tests-semant-done-tail.jpg create mode 100644 screenshots/tests-tail.jpg diff --git a/README.md b/README.md index 571377a..cae72e2 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,9 @@ A Tiger-compiler implementation in (OCa)ML Status ------ -![screenshot-tests-semant-done-head](screenshots/tests-semant-done-head.jpg) +![screenshot-tests-head](screenshots/tests-head.jpg) ... -![screenshot-tests-semant-done-tail](screenshots/tests-semant-done-tail.jpg) +![screenshot-tests-tail](screenshots/tests-tail.jpg) ### Features #### Done @@ -49,6 +49,8 @@ Status - [-] grid view (cols: lex, pars, semant, etc.; rows: test cases.) - [x] implementation - [ ] refactoring + - [ ] test time-outs (motive: cycle non-detection caused an infinite loop) + - [ ] parallel test execution - [ ] Travis CI Implementation Notes diff --git a/compiler/src/lib/tiger/tiger_dag.ml b/compiler/src/lib/tiger/tiger_dag.ml new file mode 100644 index 0000000..f843506 --- /dev/null +++ b/compiler/src/lib/tiger/tiger_dag.ml @@ -0,0 +1,39 @@ +module List = ListLabels + +module Map = Tiger_map +module Opt = Tiger_opt + +type t = + unit + +type count = + { parents : int + ; children : int + } + +let of_list pairs = + let incr counts k incr = + let prev = Opt.get (Map.get counts ~k) ~default:{parents=0; children=0} in + Map.set counts ~k ~v:(incr prev) + in + let incr_parents count = {count with parents = succ count.parents} in + let incr_children count = {count with children = succ count.children} in + let zero_children counts = + List.filter (Map.to_list counts) ~f:(fun (_, {children=c; _}) -> c = 0 ) + in + let zero_parents counts = + List.filter (Map.to_list counts) ~f:(fun (_, {parents=p; _}) -> p = 0 ) + in + let counts = + List.fold_left pairs ~init:Map.empty ~f:( + fun counts (p, c) -> + let counts = incr counts p incr_children in + let counts = incr counts c incr_parents in + counts + ) + in + (* At least one node with no in-coming links and + * at least one node with no out-going links. *) + match (zero_parents counts, zero_children counts) with + | _ :: _, _ :: _ -> Ok () + | _, _ -> Error `Cycle diff --git a/compiler/src/lib/tiger/tiger_dag.mli b/compiler/src/lib/tiger/tiger_dag.mli new file mode 100644 index 0000000..0968482 --- /dev/null +++ b/compiler/src/lib/tiger/tiger_dag.mli @@ -0,0 +1,3 @@ +type t + +val of_list : ('a * 'a) list -> (t, [`Cycle]) result diff --git a/compiler/src/lib/tiger/tiger_error.ml b/compiler/src/lib/tiger/tiger_error.ml index 88096a2..11b4b95 100644 --- a/compiler/src/lib/tiger/tiger_error.ml +++ b/compiler/src/lib/tiger/tiger_error.ml @@ -4,6 +4,12 @@ module Sym = Tiger_symbol module Typ = Tiger_env_type type t = + | Cycle_in_type_decs of + { from_id : Sym.t + ; from_pos : Pos.t + ; to_id : Sym.t + ; to_pos : Pos.t + } | Invalid_syntax of Pos.t | Unknown_id of {id : Sym.t; pos : Pos.t} | Unknown_type of {ty_id : Sym.t; pos : Pos.t} @@ -74,6 +80,15 @@ let to_string = function | Invalid_syntax pos -> s "Invalid syntax in %s" (Pos.to_string pos) + | Cycle_in_type_decs {from_id; from_pos; to_id; to_pos} -> + s ( "Circular type declaration between %S and %S." + ^^"Locations: %S (in %s), %S (in %s).") + (Sym.to_string from_id) + (Sym.to_string to_id) + (Sym.to_string from_id) + (Pos.to_string from_pos) + (Sym.to_string to_id) + (Pos.to_string to_pos) | Unknown_id {id; pos} -> s "Unknown identifier %S in %s" (Sym.to_string id) (Pos.to_string pos) | Unknown_type {ty_id; pos} -> @@ -152,6 +167,7 @@ let is_unknown_id t = match t with | Unknown_id _ -> true + | Cycle_in_type_decs _ | Invalid_syntax _ | Unknown_type _ | Id_is_a_function _ @@ -174,6 +190,7 @@ let is_unknown_type t = match t with | Unknown_type _ -> true + | Cycle_in_type_decs _ | Unknown_id _ | Invalid_syntax _ | Id_is_a_function _ @@ -196,6 +213,7 @@ let is_wrong_type t = match t with | Wrong_type _ -> true + | Cycle_in_type_decs _ | Unknown_type _ | Unknown_id _ | Invalid_syntax _ @@ -218,6 +236,7 @@ let is_wrong_number_of_args t = match t with | Wrong_number_of_args _ -> true + | Cycle_in_type_decs _ | Wrong_type _ | Unknown_type _ | Unknown_id _ @@ -240,6 +259,7 @@ let is_invalid_syntax t = match t with | Invalid_syntax _ -> true + | Cycle_in_type_decs _ | Wrong_type _ | Unknown_type _ | Unknown_id _ @@ -262,6 +282,7 @@ let is_not_a_record t = match t with | Exp_not_a_record _ -> true + | Cycle_in_type_decs _ | Invalid_syntax _ | Wrong_type _ | Unknown_type _ @@ -284,6 +305,7 @@ let is_not_an_array t = match t with | Exp_not_an_array _ -> true + | Cycle_in_type_decs _ | Exp_not_a_record _ | Invalid_syntax _ | Wrong_type _ @@ -306,6 +328,30 @@ let is_no_such_field_in_record t = match t with | No_such_field_in_record _ -> true + | Cycle_in_type_decs _ + | Exp_not_an_array _ + | Exp_not_a_record _ + | Invalid_syntax _ + | Wrong_type _ + | Unknown_type _ + | Unknown_id _ + | Id_is_a_function _ + | Id_not_a_function _ + | Wrong_type_of_expression_in_var_dec _ + | Wrong_type_used_as_array _ + | Wrong_type_used_as_record _ + | Wrong_type_of_field_value _ + | Wrong_type_of_arg _ + | Wrong_number_of_args _ + | Invalid_operand_type _ + | Different_operand_types _ -> + false + +let is_cycle_in_type_dec t = + match t with + | Cycle_in_type_decs _ -> + true + | No_such_field_in_record _ | Exp_not_an_array _ | Exp_not_a_record _ | Invalid_syntax _ diff --git a/compiler/src/lib/tiger/tiger_error.mli b/compiler/src/lib/tiger/tiger_error.mli index 9090ad4..73505b8 100644 --- a/compiler/src/lib/tiger/tiger_error.mli +++ b/compiler/src/lib/tiger/tiger_error.mli @@ -4,6 +4,12 @@ module Sym = Tiger_symbol module Typ = Tiger_env_type type t = + | Cycle_in_type_decs of + { from_id : Sym.t + ; from_pos : Pos.t + ; to_id : Sym.t + ; to_pos : Pos.t + } | Invalid_syntax of Pos.t | Unknown_id of {id : Sym.t; pos : Pos.t} | Unknown_type of {ty_id : Sym.t; pos : Pos.t} @@ -78,3 +84,4 @@ val is_invalid_syntax : t -> bool val is_not_a_record : t -> bool val is_not_an_array : t -> bool val is_no_such_field_in_record : t -> bool +val is_cycle_in_type_dec : t -> bool diff --git a/compiler/src/lib/tiger/tiger_map_red_black.ml b/compiler/src/lib/tiger/tiger_map_red_black.ml index 30ba479..4713943 100644 --- a/compiler/src/lib/tiger/tiger_map_red_black.ml +++ b/compiler/src/lib/tiger/tiger_map_red_black.ml @@ -102,3 +102,9 @@ let to_dot t ~k_to_string = let of_list pairs = List.fold_left pairs ~init:empty ~f:(fun t (k, v) -> set t ~k ~v) + +let rec to_list = function + | Leaf -> + [] + | Node (_, pair, left, right) -> + pair :: ((to_list left) @ (to_list right)) diff --git a/compiler/src/lib/tiger/tiger_map_sig.ml b/compiler/src/lib/tiger/tiger_map_sig.ml index ed21d33..6356ed3 100644 --- a/compiler/src/lib/tiger/tiger_map_sig.ml +++ b/compiler/src/lib/tiger/tiger_map_sig.ml @@ -11,5 +11,6 @@ module type S = sig val to_dot : ('k, 'v) t -> k_to_string:('k -> string) -> string - val of_list : ('k * 'v) list -> ('k, 'v) t + val of_list : ('k * 'v) list -> ('k , 'v) t + val to_list : ('k , 'v) t -> ('k * 'v) list end diff --git a/compiler/src/lib/tiger/tiger_semant.ml b/compiler/src/lib/tiger/tiger_semant.ml index b19c6fc..6bb45fd 100644 --- a/compiler/src/lib/tiger/tiger_semant.ml +++ b/compiler/src/lib/tiger/tiger_semant.ml @@ -1,8 +1,11 @@ module List = ListLabels module A = Tiger_absyn +module Dag = Tiger_dag module Env = Tiger_env module E = Tiger_error +module Pos = Tiger_position +module Sym = Tiger_symbol module Translate = Tiger_translate module Type = Tiger_env_type module Value = Tiger_env_value @@ -77,6 +80,37 @@ end = struct let check_int expty ~pos : unit = check_same return_int expty ~pos + let paths_of_typedecs typedecs : (Sym.t * Sym.t * Pos.t) list list = + let (path, paths) = + List.fold_left typedecs ~init:([], []) ~f:( + fun (path, paths) (A.TypeDec {name=child; ty; pos}) -> + match ty with + | A.NameTy {symbol=parent; _} -> + (((parent, child, pos) :: path), paths) + | A.RecordTy _ + | A.ArrayTy _ -> + ([], path :: paths) + ) + in + List.map (path :: paths) ~f:List.rev + + let check_cycles (typedecs : A.typedec list) : unit = + let non_empty_paths = + List.filter + (paths_of_typedecs typedecs) + ~f:(function [] -> false | _ -> true) + in + List.iter non_empty_paths ~f:( + fun path -> + match Dag.of_list (List.map path ~f:(fun (p, c, _) -> (p, c))) with + | Ok _ -> + () + | Error `Cycle -> + let (_, from_id, from_pos) = List.hd path in + let (_, to_id, to_pos) = List.hd (List.rev path) in + E.raise (E.Cycle_in_type_decs {from_id; from_pos; to_id; to_pos}) + ) + let rec transExp ~env exp = let rec trexp exp = (match exp with @@ -285,6 +319,7 @@ end = struct in Env.set_val env name (Value.Var {ty}) | A.TypeDecs typedecs -> + check_cycles typedecs; let env = List.fold_left typedecs ~init:env ~f:( fun env (A.TypeDec {name; ty=_; pos=_}) -> diff --git a/compiler/src/lib/tiger/tiger_test_cases.ml b/compiler/src/lib/tiger/tiger_test_cases.ml index 73b1a26..082e02f 100644 --- a/compiler/src/lib/tiger/tiger_test_cases.ml +++ b/compiler/src/lib/tiger/tiger_test_cases.ml @@ -96,6 +96,30 @@ let micro = lst \n\ end" ) + ; ( Test.case + "Cycle in type dec" + ~code:"\ + let \n\ + type a = b \n\ + type b = a \n\ + in \n\ + end \ + " + ~is_error_expected_semant:(Some Error.is_cycle_in_type_dec) + ) + ; ( Test.case + "Cycle in type dec" + ~code:"\ + let \n\ + type a = b \n\ + type b = c \n\ + type c = a \n\ + var x : a := 1 \n\ + in \n\ + end \ + " + ~is_error_expected_semant:(Some Error.is_cycle_in_type_dec) + ) ] let book ~dir = diff --git a/compiler/src/lib/tiger/tiger_test_cases_book.ml b/compiler/src/lib/tiger/tiger_test_cases_book.ml index 928373b..215fa62 100644 --- a/compiler/src/lib/tiger/tiger_test_cases_book.ml +++ b/compiler/src/lib/tiger/tiger_test_cases_book.ml @@ -96,6 +96,9 @@ let is_error_expected_parsing_of_filename = let is_error_expected_semant_of_filename = let module E = Tiger_error in function + | "test16.tig" -> + Some Error.is_cycle_in_type_dec + (* TODO: Be more specific - between which decs? *) | "test17.tig" | "test33.tig" -> Some Error.is_unknown_type diff --git a/screenshots/tests-semant-done-head.jpg b/screenshots/tests-head.jpg similarity index 100% rename from screenshots/tests-semant-done-head.jpg rename to screenshots/tests-head.jpg diff --git a/screenshots/tests-semant-done-tail.jpg b/screenshots/tests-semant-done-tail.jpg deleted file mode 100644 index cdc20606cf2669e90dffd4423beeb6c245891d44..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3677 zcmeH}X;4#H7RO(9h-`rn!Wwo10wRe)u~|e32Bg`etgXlbv->*x|Ul8iTP zHrck_ifp}uV&m-MO5L^F&D}p>@4mpG{lN#LX@_EB4Y)UjG9~lwI&*a0VnQKx-elp3Pl`SmLz5Vn#!*9E10_%{POF#mXOo5bv^Xt;`^LBU^ z#10hT$GKTi%{r>5BXcB4W_fCnUL54KQ^{?BUEmnj|Y|wq{%(tsZ_Rmn9%!?g6DU)lkAipcBNi|SY zXVVy$9iA3f+St=HA2cprK{-g1t-lF~A_MjgTWF{TDyBp+vXH*tcu4vSt7psC-b$I- z?HqlmGOc(sBv{VnvFY(ZM@~7Q`b|X+-4@?pFtMiZ%y2VJPp5v%?v@^rKkru7GX-NEc-JfH+t%p*S_Cp+_U?=oh+}@8TsQV3s=Vv0J3Y2vzTc-`c85Q z6oO<9N)_zitXz{EqCcG!CUK$IbB32(zSL2Ehcp$CaWoRfbd_&^Nb1!Hkvg!$sXTeY zt*~y6)nIxjP0wi0qnGN2duc`+peQbCq_VWI^}?Q{kG2{5k}YLoheFE&ZX|8YB`D?O z(3%V`o^AS4a*}_NPhCWX&b@Sc%6sw2WcbM=KjsLJC(j;qbh%P{9$F{iMQ_~mG4O4? zq>L2?QlDcz7&9l)W8ZUUXp8uWv&c_$kD)n=a@`X)Tu97Bl+ zBUW-swC6L|zyts#;g&|p!&Ar8IhEJK)AbOP{YAqwBj}r7IX%Saa^#g$VF~;h ze1%HV_!e42*Yn#o)$C(79)owgYTVHZDed!Z{=bD5rQ}|p2GiP<0ZMB z+nVG3(3h<*ZR-5V8m8{6(-NA*bYTuHd?_+JW`zNdT+75RnIJbv?6-3ayl{NJUJOo`_a8-_QYINmgwdoj3kzhB+9MkJ^aZ) zd*=bPqmL=#{t-7XiLJ_KCuS-~I>osTFQS5-$mx}@C76uKqiqg`W`0;)onh>8Y=n8$ zrLGTlJKd^Z7t0rRYxJ5&M>=Us=aSnuYf<#?{aR0$<~sUpxh~-~+GL&{-J#jD{;dk> zJ$0l_w6!I1N_oU#AV#}2Mg-m^7YUJ9vTHFKdaNmjy5mf4RWWwDdQw6}^@Hks>5IZf z+E-l7y$JDE;}ca?+z8&AfZGsz+rCqV4D5}onHAFent<@8SG5^m<*H+S?%t~I8r{XV ztxd7DGsA~dyB)wK(G2^uey`Dqgh`b>wGg3QJ^EDz5tm=hv%ND*v0>G=snlnqR(6gy z>wNu|UM?VkpSNn&@;{ONyQ(Hb6x(V}&+?cv#;9RRaZpxp)H$y8WCc}M-LlQ=l7$#y zsS%OWZAJfplWZ2Bp3kmVNh`}BHDosv-8GMtyg7y3e&0ED5TRN2N5KF50jxcF%A6(qWB|e7-vNNn BR#pH2 diff --git a/screenshots/tests-tail.jpg b/screenshots/tests-tail.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b0c72d7a268444762e9532bd72484dd7e00ffe80 GIT binary patch literal 6236 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 literal 0 HcmV?d00001 -- 2.20.1