From f78e47ca6cf629569299acc3bba1d7ba1368956d Mon Sep 17 00:00:00 2001 From: Tom Gasson Date: Sun, 5 Jul 2020 12:13:19 -0700 Subject: [PATCH] Introduce `guard` syntax --- src/napkin_core.ml | 66 ++++++++ src/napkin_diagnostics.ml | 4 +- src/napkin_grammar.ml | 6 +- src/napkin_parsetree_viewer.ml | 21 ++- src/napkin_parsetree_viewer.mli | 1 + src/napkin_printer.ml | 52 ++++++ src/napkin_token.ml | 8 +- .../__snapshots__/parse.spec.js.snap | 35 ++++ tests/parsing/errors/expressions/guard.js | 11 ++ .../__snapshots__/parse.spec.js.snap | 84 ++++++++++ tests/parsing/grammar/expressions/guard.js | 153 ++++++++++++++++++ .../__snapshots__/render.spec.js.snap | 19 +++ tests/printer/comments/guard.res | 9 ++ .../expr/__snapshots__/render.spec.js.snap | 148 +++++++++++++++++ tests/printer/expr/guard.js | 153 ++++++++++++++++++ 15 files changed, 760 insertions(+), 10 deletions(-) create mode 100644 tests/parsing/errors/expressions/guard.js create mode 100644 tests/parsing/grammar/expressions/guard.js create mode 100644 tests/printer/comments/guard.res create mode 100644 tests/printer/expr/guard.js diff --git a/src/napkin_core.ml b/src/napkin_core.ml index 0bc89a29..ea0003c4 100644 --- a/src/napkin_core.ml +++ b/src/napkin_core.ml @@ -84,6 +84,7 @@ let jsxAttr = (Location.mknoloc "JSX", Parsetree.PStr []) let uncurryAttr = (Location.mknoloc "bs", Parsetree.PStr []) let ternaryAttr = (Location.mknoloc "ns.ternary", Parsetree.PStr []) let ifLetAttr = (Location.mknoloc "ns.iflet", Parsetree.PStr []) +let guardAttr = (Location.mknoloc "ns.guard", Parsetree.PStr []) let suppressFragileMatchWarningAttr = (Location.mknoloc "warning", Parsetree.PStr [Ast_helper.Str.eval (Ast_helper.Exp.constant (Pconst_string ("-4", None)))]) let makeBracesAttr loc = (Location.mkloc "ns.braces" loc, Parsetree.PStr []) @@ -2003,6 +2004,8 @@ and parseOperandExpr ~context p = parseTryExpression p | If -> parseIfOrIfLetExpression p + | Guard -> + parseGuardExpression p | For -> parseForExpression p | While -> @@ -3092,6 +3095,69 @@ and parseIfOrIfLetExpression p = Parser.eatBreadcrumb p; expr; +and parseGuardLetExpr startPos p = + let pattern = parsePattern p in + Parser.expect Equal p; + let conditionExpr = parseIfCondition p in + let elseExpr = match p.Parser.token with + | Else -> + Parser.endRegion p; + Parser.leaveBreadcrumb p Grammar.ElseBranch; + Parser.next p; + Parser.beginRegion p; + let elseExpr = parseElseBranch p in + Parser.eatBreadcrumb p; + Parser.endRegion p; + elseExpr + | _ -> + Parser.endRegion p; + let startPos = p.Parser.startPos in + let loc = mkLoc startPos p.prevEndPos in + Ast_helper.Exp.construct ~loc (Location.mkloc (Longident.Lident "()") loc) None + in + let thenExpr = parseExprBlock p in + let loc = mkLoc startPos p.prevEndPos in + Ast_helper.Exp.match_ ~attrs:[guardAttr] ~loc conditionExpr [ + Ast_helper.Exp.case pattern thenExpr; + Ast_helper.Exp.case (Ast_helper.Pat.any ()) elseExpr; + ] + +and parseGuardExpr startPos p = + let conditionExpr = parseIfCondition p in + let elseExpr = match p.Parser.token with + | Else -> + Parser.endRegion p; + Parser.leaveBreadcrumb p Grammar.ElseBranch; + Parser.next p; + Parser.beginRegion p; + let elseExpr = parseElseBranch p in + Parser.eatBreadcrumb p; + Parser.endRegion p; + Some elseExpr + | _ -> + Parser.endRegion p; + None + in + let thenExpr = parseExprBlock p in + let loc = mkLoc startPos p.prevEndPos in + Ast_helper.Exp.ifthenelse ~loc ~attrs:[guardAttr] conditionExpr thenExpr elseExpr + +and parseGuardExpression p = + Parser.beginRegion p; + Parser.leaveBreadcrumb p Grammar.ExprGuard; + let startPos = p.Parser.startPos in + Parser.expect Guard p; + let expr = match p.Parser.token with + | Let -> + Parser.next p; + parseGuardLetExpr startPos p + | _ -> + parseGuardExpr startPos p + in + Parser.eatBreadcrumb p; + expr; + + and parseForRest hasOpeningParen pattern startPos p = Parser.expect In p; let e1 = parseExpr p in diff --git a/src/napkin_diagnostics.ml b/src/napkin_diagnostics.ml index c133c116..572842c6 100644 --- a/src/napkin_diagnostics.ml +++ b/src/napkin_diagnostics.ml @@ -95,6 +95,8 @@ let explain t = begin match breadcrumbs, t with | (ExprBlock, _) :: _, Rbrace -> "It seems that this expression block is empty" + | (ExprBlock, _) :: _, Eof -> + "It seems that this expression block is incomplete" | (ExprBlock, _) :: _, Bar -> (* Pattern matching *) "Looks like there might be an expression missing here" | (ExprSetField, _) :: _, _ -> @@ -191,4 +193,4 @@ let unclosedString = UnclosedString let unclosedComment = UnclosedComment let unclosedTemplate = UnclosedTemplate let unknownUchar code = UnknownUchar code -let message txt = Message txt \ No newline at end of file +let message txt = Message txt diff --git a/src/napkin_grammar.ml b/src/napkin_grammar.ml index e07a6750..e3e2fd53 100644 --- a/src/napkin_grammar.ml +++ b/src/napkin_grammar.ml @@ -17,6 +17,7 @@ type t = | ExprArrayAccess | ExprArrayMutation | ExprIf + | ExprGuard | IfCondition | IfBranch | ElseBranch | TypeExpression | External @@ -69,6 +70,7 @@ let toString = function | ExprUnary -> "a unary expression" | ExprBinaryAfterOp op -> "an expression after the operator \"" ^ Token.toString op ^ "\"" | ExprIf -> "an if expression" + | ExprGuard -> "a guard expression" | IfCondition -> "the condition of an if expression" | IfBranch -> "the true-branch of an if expression" | ElseBranch -> "the else-branch of an if expression" @@ -169,7 +171,7 @@ let isExprStart = function | LessThan | Minus | MinusDot | Plus | PlusDot | Bang | Percent | At - | If | Switch | While | For | Assert | Lazy | Try -> true + | If | Guard | Switch | While | For | Assert | Lazy | Try -> true | _ -> false let isJsxAttributeStart = function @@ -300,7 +302,7 @@ let isBlockExprStart = function | Token.At | Hash | Percent | Minus | MinusDot | Plus | PlusDot | Bang | True | False | Float _ | Int _ | String _ | Character _ | Lident _ | Uident _ | Lparen | List | Lbracket | Lbrace | Forwardslash | Assert - | Lazy | If | For | While | Switch | Open | Module | Exception | Let + | Lazy | If | Guard | For | While | Switch | Open | Module | Exception | Let | LessThan | Backtick | Try | Underscore -> true | _ -> false diff --git a/src/napkin_parsetree_viewer.ml b/src/napkin_parsetree_viewer.ml index 216f8d1d..f7aaaa21 100644 --- a/src/napkin_parsetree_viewer.ml +++ b/src/napkin_parsetree_viewer.ml @@ -153,7 +153,7 @@ let processBracesAttr expr = let filterParsingAttrs attrs = List.filter (fun attr -> match attr with - | ({Location.txt = ("ns.ternary" | "ns.braces" | "bs" | "ns.iflet" | "ns.namedArgLoc")}, _) -> false + | ({Location.txt = ("ns.ternary" | "ns.braces" | "bs" | "ns.iflet" | "ns.guard" | "ns.namedArgLoc")}, _) -> false | _ -> true ) attrs @@ -268,9 +268,22 @@ let isIfLetExpr expr = match expr with } when hasIfLetAttribute attrs -> true | _ -> false +let rec hasGuardAttribute attrs = + match attrs with + | [] -> false + | ({Location.txt="ns.guard"},_)::_ -> true + | _::attrs -> hasGuardAttribute attrs + +let isGuardExpr expr = match expr with + | { + pexp_attributes = attrs; + pexp_desc = Pexp_match _ | Pexp_ifthenelse _ + } when hasGuardAttribute attrs -> true + | _ -> false + let hasAttributes attrs = List.exists (fun attr -> match attr with - | ({Location.txt = "bs" | "ns.ternary" | "ns.braces" | "ns.iflet"}, _) -> false + | ({Location.txt = "bs" | "ns.ternary" | "ns.braces" | "ns.iflet" | "ns.guard"}, _) -> false (* Remove the fragile pattern warning for iflet expressions *) | ({Location.txt="warning"}, PStr [{ pstr_desc = Pstr_eval ({ @@ -426,13 +439,13 @@ let shouldInlineRhsBinaryExpr rhs = match rhs.pexp_desc with let filterPrinteableAttributes attrs = List.filter (fun attr -> match attr with - | ({Location.txt="bs" | "ns.ternary" | "ns.iflet"}, _) -> false + | ({Location.txt="bs" | "ns.ternary" | "ns.iflet" | "ns.guard"}, _) -> false | _ -> true ) attrs let partitionPrinteableAttributes attrs = List.partition (fun attr -> match attr with - | ({Location.txt="bs" | "ns.ternary" | "ns.iflet"}, _) -> false + | ({Location.txt="bs" | "ns.ternary" | "ns.iflet" | "ns.guard"}, _) -> false | _ -> true ) attrs diff --git a/src/napkin_parsetree_viewer.mli b/src/napkin_parsetree_viewer.mli index 2be36175..b059d46f 100644 --- a/src/napkin_parsetree_viewer.mli +++ b/src/napkin_parsetree_viewer.mli @@ -67,6 +67,7 @@ val hasAttributes: Parsetree.attributes -> bool val isArrayAccess: Parsetree.expression -> bool val isTernaryExpr: Parsetree.expression -> bool val isIfLetExpr: Parsetree.expression -> bool +val isGuardExpr: Parsetree.expression -> bool val collectTernaryParts: Parsetree.expression -> ((Parsetree.expression * Parsetree.expression) list * Parsetree.expression) diff --git a/src/napkin_printer.ml b/src/napkin_printer.ml index 55fec9e9..20034834 100644 --- a/src/napkin_printer.ml +++ b/src/napkin_printer.ml @@ -2759,6 +2759,32 @@ and printExpression (e : Parsetree.expression) cmtTbl = ] | Pexp_setfield (expr1, longidentLoc, expr2) -> printSetFieldExpr e.pexp_attributes expr1 longidentLoc expr2 e.pexp_loc cmtTbl + | Pexp_ifthenelse (ifExpr, thenExpr, elseExpr) when ParsetreeViewer.isGuardExpr e -> + let condition = if ParsetreeViewer.isBlockExpr ifExpr then + printExpressionBlock ~braces:true ifExpr cmtTbl + else + let doc = printExpressionWithComments ifExpr cmtTbl in + match Parens.expr ifExpr with + | Parens.Parenthesized -> addParens doc + | Braced braces -> printBraces doc ifExpr braces + | Nothing -> doc + in + let elseDoc = match elseExpr with + | None -> Doc.nil + | Some expr -> Doc.concat [ + Doc.text "else "; + printExpressionBlock ~braces:true expr cmtTbl; + ] + in + Doc.concat [ + printAttributes e.pexp_attributes cmtTbl; + Doc.text "guard "; + condition; + Doc.space; + elseDoc; + Doc.line; + printExpressionBlock ~braces:false thenExpr cmtTbl; + ] | Pexp_ifthenelse (_ifExpr, _thenExpr, _elseExpr) when ParsetreeViewer.isTernaryExpr e -> let (parts, alternate) = ParsetreeViewer.collectTernaryParts e in let ternaryDoc = match parts with @@ -3030,6 +3056,32 @@ and printExpression (e : Parsetree.expression) cmtTbl = Doc.text " catch "; printCases cases cmtTbl; ] + | Pexp_match (conditionExpr, [{ + pc_lhs = pattern; + pc_guard = None; + pc_rhs = thenExpr; + }; { + pc_rhs = elseExpr + }]) when ParsetreeViewer.isGuardExpr e -> + let patternDoc = printPattern pattern cmtTbl in + let conditionDoc = printExpressionWithComments conditionExpr cmtTbl in + let elseDocs = match elseExpr with + | {pexp_desc = Pexp_construct ({txt = Longident.Lident "()"}, _)} -> Doc.nil + | _ -> Doc.concat [ + Doc.text " else "; + printExpressionBlock ~braces:true elseExpr cmtTbl + ] + in + Doc.concat [ + printAttributes e.pexp_attributes cmtTbl; + Doc.text "guard let "; + patternDoc; + Doc.text " = "; + conditionDoc; + elseDocs; + Doc.line; + printExpressionBlock ~braces:false thenExpr cmtTbl; + ] | Pexp_match (_, [_;_]) when ParsetreeViewer.isIfLetExpr e -> let (ifs, elseExpr) = ParsetreeViewer.collectIfExpressions e in printIfChain e.pexp_attributes ifs elseExpr cmtTbl diff --git a/src/napkin_token.ml b/src/napkin_token.ml index 139ff1bd..9eefc8b8 100644 --- a/src/napkin_token.ml +++ b/src/napkin_token.ml @@ -46,7 +46,7 @@ type t = | Lazy | Tilde | Question - | If | Else | For | In | To | Downto | While | Switch + | If | Else | For | In | To | Downto | While | Switch | Guard | When | EqualGreater | MinusGreater | External @@ -130,6 +130,7 @@ let toString = function | Tilde -> "tilde" | Question -> "?" | If -> "if" + | Guard -> "guard" | Else -> "else" | For -> "for" | In -> "in" @@ -177,6 +178,7 @@ let keywordTable = function | "assert" -> Assert | "lazy" -> Lazy | "if" -> If +| "guard" -> Guard | "else" -> Else | "for" -> For | "in" -> In @@ -204,7 +206,7 @@ let keywordTable = function let isKeyword = function | True | False | Open | Let | Rec | And | As - | Exception | Assert | Lazy | If | Else | For | In | To + | Exception | Assert | Lazy | If | Guard | Else | For | In | To | Downto | While | Switch | When | External | Typ | Private | Mutable | Constraint | Include | Module | Of | Land | Lor | List | With @@ -220,4 +222,4 @@ let lookupKeyword str = let isKeywordTxt str = try let _ = keywordTable str in true with - | Not_found -> false \ No newline at end of file + | Not_found -> false diff --git a/tests/parsing/errors/expressions/__snapshots__/parse.spec.js.snap b/tests/parsing/errors/expressions/__snapshots__/parse.spec.js.snap index b899031f..c3f4fc26 100644 --- a/tests/parsing/errors/expressions/__snapshots__/parse.spec.js.snap +++ b/tests/parsing/errors/expressions/__snapshots__/parse.spec.js.snap @@ -258,6 +258,41 @@ Missing expression +========================================================" +`; + +exports[`guard.js 1`] = ` +"=====Parsetree========================================== +;;((if a + then (((if c then [%napkinscript.exprhole ] else ()))[@ns.guard ]) + else ((if b then [%napkinscript.exprhole ] else ())[@ns.guard ])) + [@ns.guard ]) +=====Errors============================================= + +File \\"/syntax/tests/parsing/errors/expressions/guard.js\\", line 4, characters 5-32: + + +2 │ guard b else { +3 │ () +4 │ } +5 │ // }, missing a body +6 │ } + +It seems that this expression block is empty + + +File \\"/syntax/tests/parsing/errors/expressions/guard.js\\", line 10, characters 1-25: + + +8 │ guard c else { +9 │ () +10 │ } +11 │ // eof, missing a body + +It seems that this expression block is incomplete + + + ========================================================" `; diff --git a/tests/parsing/errors/expressions/guard.js b/tests/parsing/errors/expressions/guard.js new file mode 100644 index 00000000..24b84dcd --- /dev/null +++ b/tests/parsing/errors/expressions/guard.js @@ -0,0 +1,11 @@ +guard a else { + guard b else { + () + } + // }, missing a body +} + +guard c else { + () +} +// eof, missing a body diff --git a/tests/parsing/grammar/expressions/__snapshots__/parse.spec.js.snap b/tests/parsing/grammar/expressions/__snapshots__/parse.spec.js.snap index 4df7d44e..62fce961 100644 --- a/tests/parsing/grammar/expressions/__snapshots__/parse.spec.js.snap +++ b/tests/parsing/grammar/expressions/__snapshots__/parse.spec.js.snap @@ -486,6 +486,90 @@ exports[`for.js 1`] = ` ;;for p = 0 to x + 1 do () done" `; +exports[`guard.js 1`] = ` +"let safeDivideZero num denom = ((if denom != 0 then num / denom else 0) + [@ns.braces ][@ns.guard ]) +let safeDivideNone num denom = + ((if denom != 0 then Some (num / denom) else None) + [@ns.braces ][@ns.guard ]) +let safeDivideExn num denom = + ((if denom != 0 then num / denom else raise DivisionByZero) + [@ns.braces ][@ns.guard ]) +let nested x y z = + ((if x + then + (((if y + then (((if z then Js.log \\"Hello\\" else raise BadZ))[@ns.guard ]) + else raise BadY)) + [@ns.guard ]) + else raise BadX) + [@ns.braces ][@ns.guard ]) +let returningUnit shouldLog = ((if shouldLog then Js.log \\"Hello\\") + [@ns.braces ][@ns.guard ]) +let nested x y z = + ((if x + then Js.log \\"Hello\\" + else + ((if y + then raise BadX + else ((if z then raise BadY else raise BadZ)[@ns.guard ])) + [@ns.guard ])) + [@ns.braces ][@ns.guard ]) +let isValid width height color status = + ((if ((let size = width * height in size < 100)[@ns.braces ]) + then (((if color != Red then true else false))[@ns.guard ]) + else false) + [@ns.braces ][@ns.guard ]) +let guardlet () = + ((match foo () with + | Some x -> + (doSomethingWithX x; + (((match foo () with + | Some x -> + (((match foo () with + | Some x -> doSomethingWithX x + | _ -> raise FailedToUnwrap)) + [@ns.guard ]) + | _ -> raise FailedToUnwrap)) + [@ns.guard ])) + | _ -> \\"return\\") + [@ns.braces ][@ns.guard ]) +;;((if + ((let one = (x > 10) && (n > 2) in let two = \\"test\\" in (one + 2) + two) + [@ns.braces ]) + then + (\\"hello\\"; + ((if x > 10 + then + (\\"hello\\"; + (let foo x = ((if x > 10 then Js.log x else \\"\\") + [@ns.braces ][@ns.guard ]) in + ((if x > 10 + then + (Js.log x; + (let bar a b c = + ((if a > 10 + then (((if c > 2 then \\"hello\\"))[@ns.guard ]) + else + (\\"test\\"; ((if b > 20 then \\"ok\\" else c)[@ns.guard ]))) + [@ns.braces ][@ns.guard ]) in + let bar ((a)[@b ]) b c = + ((if ((a)[@d ]) > 10 + then (((if c > 2 then ((\\"ok\\")[@g \\"hello\\"]))) + [@f ][@ns.guard ]) + else + (((\\"test\\") + [@e ]); + ((if b > 20 then \\"ok\\" else c) + [@ns.guard ]))) + [@ns.braces ][@ac ][@ns.guard ]) in + ((if test then \\"hello\\" else ())[@ns.guard ])))) + [@ns.guard ]))) + else \\"fail\\") + [@ns.guard ])) + else \\"fail\\")[@ns.guard ])" +`; + exports[`ident.js 1`] = ` "let x = foo let y = Foo.Bar.x diff --git a/tests/parsing/grammar/expressions/guard.js b/tests/parsing/grammar/expressions/guard.js new file mode 100644 index 00000000..7caa4038 --- /dev/null +++ b/tests/parsing/grammar/expressions/guard.js @@ -0,0 +1,153 @@ +let safeDivideZero = (num, denom) => { + guard denom !== 0 else { + 0 + } + num / denom +} +let safeDivideNone = (num, denom) => { + guard denom !== 0 else { + None + } + Some(num / denom) +} + +let safeDivideExn = (num, denom) => { + guard denom !== 0 else { + raise(DivisionByZero) + } + num / denom +} + +let nested = (x,y,z) => { + guard x else { + raise(BadX) + } + guard y else { + raise(BadY) + } + guard z else { + raise(BadZ) + } + + Js.log("Hello") +} + +let returningUnit = shouldLog => { + guard shouldLog + Js.log("Hello") +} + +// NOT advised for use, since this syntax allows you to be flat +// This is really running preconditions before throwing, and handling errors +// on the error cases +// Just here to show if doesn't fail +let nested = (x,y,z) => { + guard x else { + guard y else { + guard z else { + raise(BadZ) + } + raise(BadY) + } + raise(BadX) + } + + Js.log("Hello") +} + +// Not recommended, but blocks are still valid +let isValid = (width, height, color, status) => { + guard { + let size = width * height; + size < 100 + } else { + false + } + + guard color !== Red else { + false + } + + true +} + +let guardlet = () => { + guard let Some(x) = foo() else { + "return" + } + doSomethingWithX(x) + + guard let Some(x) = foo() else { + raise(FailedToUnwrap) + } + + guard let Some(x) = foo() else { + raise(FailedToUnwrap) + } + doSomethingWithX(x) +} + +// Here down is module-level application +// You'll see that these continually nest - so each guard statement puts the remaining body in the +// then branch +guard { + let one = x > 10 && n > 2 + let two = "test" + one + 2 + two +} else { + "fail" +} +"hello" + +guard x > 10 else { + "fail" +} +"hello" + +let foo = x => { + guard x > 10 else { + "" + } + Js.log(x) +} + +guard x > 10 +Js.log(x) + +// nested +let bar = (a,b,c) => { + guard a > 10 else { + "test" + guard b > 20 else { + c + } + "ok" + } + guard c > 2 + + "hello" +} + +// Maintains attributes +let bar = (@b a,b,c) => { + @ac + guard @d a > 10 else { + @e "test" + guard b > 20 else { + c + } + "ok" + } + @f + guard c > 2 + + @g("hello") + "ok" +} + +// top-level +guard test else { + () +} + +"hello" diff --git a/tests/printer/comments/__snapshots__/render.spec.js.snap b/tests/printer/comments/__snapshots__/render.spec.js.snap index 07ecc619..7c9fe5dc 100644 --- a/tests/printer/comments/__snapshots__/render.spec.js.snap +++ b/tests/printer/comments/__snapshots__/render.spec.js.snap @@ -913,6 +913,25 @@ comment " `; +exports[`guard.res 1`] = ` +"let safeDivideZero = (num, denom) => { + guard /* i1 */ denom /* i2 */ !== /* i3 */ 0 /* i4 */ else { + 0 + } + // t5 + + guard let Some( + x, + ) = /* i6 */ /* i7 */ /* i8 */ /* i9 */ /* i10 */ foo() /* i12 */ else { + \\"return\\" + } + // t13 + + num /* i14 */ / /* i15 */ denom // t16 +} +" +`; + exports[`ifLet.res 1`] = ` "if let /* c0 */ /* c1 */ Some( /* c2 */ x /* c3 */, diff --git a/tests/printer/comments/guard.res b/tests/printer/comments/guard.res new file mode 100644 index 00000000..54c53483 --- /dev/null +++ b/tests/printer/comments/guard.res @@ -0,0 +1,9 @@ +let safeDivideZero = (num, denom) => { + guard /* i1 */ denom /* i2 */ !== /* i3 */ 0 /* i4 */ else { + 0 // t5 + } + guard let /* i6 */ Some(/* i7 */ x/* i8 */ ) /* i9 */ = /* i10 */ foo(/* i11 */ ) /* i12 */ else { + "return" //t13 + } + num /* i14 */ / /* i15 */ denom // t16 +} diff --git a/tests/printer/expr/__snapshots__/render.spec.js.snap b/tests/printer/expr/__snapshots__/render.spec.js.snap index 67599957..7fc19455 100644 --- a/tests/printer/expr/__snapshots__/render.spec.js.snap +++ b/tests/printer/expr/__snapshots__/render.spec.js.snap @@ -2143,6 +2143,154 @@ let f = (type \\\\\\"😎\\", x) => Js.log(x) " `; +exports[`guard.js 1`] = ` +"let safeDivideZero = (num, denom) => { + guard denom !== 0 else { + 0 + } + num / denom +} +let safeDivideNone = (num, denom) => { + guard denom !== 0 else { + None + } + Some(num / denom) +} + +let safeDivideExn = (num, denom) => { + guard denom !== 0 else { + raise(DivisionByZero) + } + num / denom +} + +let nested = (x, y, z) => { + guard x else { + raise(BadX) + } + guard y else { + raise(BadY) + } + guard z else { + raise(BadZ) + } + Js.log(\\"Hello\\") +} + +let returningUnit = shouldLog => { + guard shouldLog + Js.log(\\"Hello\\") +} + +// NOT advised for use, since this syntax allows you to be flat +// This is really running preconditions before throwing, and handling errors +// on the error cases +// Just here to show if doesn't fail +let nested = (x, y, z) => { + guard x else { + guard y else { + guard z else { + raise(BadZ) + } + raise(BadY) + } + raise(BadX) + } + Js.log(\\"Hello\\") +} + +// Not recommended, but blocks are still valid +let isValid = (width, height, color, status) => { + guard { + let size = width * height + size < 100 + } else { + false + } + guard color !== Red else { + false + } + true +} + +let guardlet = () => { + guard let Some(x) = foo() else { + \\"return\\" + } + doSomethingWithX(x) + + guard let Some(x) = foo() else { + raise(FailedToUnwrap) + } + guard let Some(x) = foo() else { + raise(FailedToUnwrap) + } + doSomethingWithX(x) +} + +// Here down is module-level application +// You'll see that these continually nest - so each guard statement puts the remaining body in the +// then branch +guard { + let one = x > 10 && n > 2 + let two = \\"test\\" + one + 2 + two +} else { + \\"fail\\" +} +\\"hello\\" + +guard x > 10 else { + \\"fail\\" +} +\\"hello\\" + +let foo = x => { + guard x > 10 else { + \\"\\" + } + Js.log(x) +} + +guard x > 10 +Js.log(x) + +// nested +let bar = (a, b, c) => { + guard a > 10 else { + \\"test\\" + guard b > 20 else { + c + } + \\"ok\\" + } + guard c > 2 + \\"hello\\" +} + +// Maintains attributes +let bar = (@b a, b, c) => { + @ac + guard @d a > 10 else { + @e \\"test\\" + guard b > 20 else { + c + } + \\"ok\\" + } + @f + guard c > 2 + @g(\\"hello\\") \\"ok\\" +} + +// top-level +guard test else { + () +} +\\"hello\\" +" +`; + exports[`ident.js 1`] = ` "let x = a diff --git a/tests/printer/expr/guard.js b/tests/printer/expr/guard.js new file mode 100644 index 00000000..7caa4038 --- /dev/null +++ b/tests/printer/expr/guard.js @@ -0,0 +1,153 @@ +let safeDivideZero = (num, denom) => { + guard denom !== 0 else { + 0 + } + num / denom +} +let safeDivideNone = (num, denom) => { + guard denom !== 0 else { + None + } + Some(num / denom) +} + +let safeDivideExn = (num, denom) => { + guard denom !== 0 else { + raise(DivisionByZero) + } + num / denom +} + +let nested = (x,y,z) => { + guard x else { + raise(BadX) + } + guard y else { + raise(BadY) + } + guard z else { + raise(BadZ) + } + + Js.log("Hello") +} + +let returningUnit = shouldLog => { + guard shouldLog + Js.log("Hello") +} + +// NOT advised for use, since this syntax allows you to be flat +// This is really running preconditions before throwing, and handling errors +// on the error cases +// Just here to show if doesn't fail +let nested = (x,y,z) => { + guard x else { + guard y else { + guard z else { + raise(BadZ) + } + raise(BadY) + } + raise(BadX) + } + + Js.log("Hello") +} + +// Not recommended, but blocks are still valid +let isValid = (width, height, color, status) => { + guard { + let size = width * height; + size < 100 + } else { + false + } + + guard color !== Red else { + false + } + + true +} + +let guardlet = () => { + guard let Some(x) = foo() else { + "return" + } + doSomethingWithX(x) + + guard let Some(x) = foo() else { + raise(FailedToUnwrap) + } + + guard let Some(x) = foo() else { + raise(FailedToUnwrap) + } + doSomethingWithX(x) +} + +// Here down is module-level application +// You'll see that these continually nest - so each guard statement puts the remaining body in the +// then branch +guard { + let one = x > 10 && n > 2 + let two = "test" + one + 2 + two +} else { + "fail" +} +"hello" + +guard x > 10 else { + "fail" +} +"hello" + +let foo = x => { + guard x > 10 else { + "" + } + Js.log(x) +} + +guard x > 10 +Js.log(x) + +// nested +let bar = (a,b,c) => { + guard a > 10 else { + "test" + guard b > 20 else { + c + } + "ok" + } + guard c > 2 + + "hello" +} + +// Maintains attributes +let bar = (@b a,b,c) => { + @ac + guard @d a > 10 else { + @e "test" + guard b > 20 else { + c + } + "ok" + } + @f + guard c > 2 + + @g("hello") + "ok" +} + +// top-level +guard test else { + () +} + +"hello"