diff --git a/mulang.cabal b/mulang.cabal index 3bc2e2c03..12bb0ba94 100644 --- a/mulang.cabal +++ b/mulang.cabal @@ -36,6 +36,7 @@ library Language.Mulang.Ast Language.Mulang.Identifier Language.Mulang.Builder + Language.Mulang.Unbuilder Language.Mulang.Generator Language.Mulang.Signature Language.Mulang.DomainLanguage @@ -58,13 +59,23 @@ library Language.Mulang.Inspector.Typed Language.Mulang.Parsers Language.Mulang.Parsers.Haskell - Language.Mulang.Parsers.Prolog Language.Mulang.Parsers.Java Language.Mulang.Parsers.JavaScript + Language.Mulang.Parsers.Prolog Language.Mulang.Parsers.Python + Language.Mulang.Operators + Language.Mulang.Operators.Haskell + Language.Mulang.Operators.Java + Language.Mulang.Operators.Python + Language.Mulang.Operators.Ruby + Language.Mulang.Unparsers + Language.Mulang.Unparsers.Java + Language.Mulang.Unparsers.Python + Language.Mulang.Unparsers.Ruby Language.Mulang.Analyzer Language.Mulang.Analyzer.Analysis Language.Mulang.Analyzer.Analysis.Json + Language.Mulang.Analyzer.Autocorrector Language.Mulang.Analyzer.DomainLanguageCompiler Language.Mulang.Analyzer.ExpectationsAnalyzer Language.Mulang.Analyzer.ExpectationsCompiler @@ -73,6 +84,7 @@ library Language.Mulang.Analyzer.SignatureStyleCompiler Language.Mulang.Analyzer.SmellsAnalyzer Language.Mulang.Analyzer.TestsAnalyzer + Language.Mulang.Analyzer.Synthesizer Language.Mulang.Interpreter Language.Mulang.Interpreter.Runner diff --git a/spec/AutocorrectorSpec.hs b/spec/AutocorrectorSpec.hs new file mode 100644 index 000000000..27b85907a --- /dev/null +++ b/spec/AutocorrectorSpec.hs @@ -0,0 +1,136 @@ +module AutocorrectorSpec (spec) where + +import Test.Hspec +import Language.Mulang.Analyzer.Autocorrector +import Language.Mulang.Analyzer.Analysis hiding (spec) +import qualified Language.Mulang.Analyzer.Analysis as A +import Data.Maybe (fromJust) + +run language = head . fromJust . expectations . A.spec . autocorrect . expectationsAnalysis (CodeSample language "foo") . (:[]) + +spec :: Spec +spec = do + describe "correct primitive usages" $ do + it "corrects haskell otherwise negated" $ do + run Haskell (Expectation "*" "Not:Uses:otherwise") `shouldBe` (Expectation "*" "Not:UsesOtherwise") + + it "corrects haskell otherwise" $ do + run Haskell (Expectation "*" "Uses:otherwise") `shouldBe` (Expectation "*" "UsesOtherwise") + + it "corrects haskell and" $ do + run Haskell (Expectation "*" "Uses:and") `shouldBe` (Expectation "*" "Uses:and") + run Haskell (Expectation "*" "Uses:&&") `shouldBe` (Expectation "*" "UsesAnd") + + it "corrects haskell or" $ do + run Haskell (Expectation "*" "Uses:or") `shouldBe` (Expectation "*" "Uses:or") + run Haskell (Expectation "*" "Uses:||")`shouldBe` (Expectation "*" "UsesOr") + + it "corrects haskell not" $ do + run Haskell (Expectation "*" "Uses:not") `shouldBe` (Expectation "*" "UsesNegation") + run Haskell (Expectation "*" "Uses:!") `shouldBe` (Expectation "*" "Uses:!") + + it "corrects java and" $ do + run Java (Expectation "*" "Uses:and") `shouldBe` (Expectation "*" "Uses:and") + run Java (Expectation "*" "Uses:&&") `shouldBe` (Expectation "*" "UsesAnd") + + it "corrects python and" $ do + run Python (Expectation "*" "Uses:and") `shouldBe` (Expectation "*" "UsesAnd") + run Python (Expectation "*" "Uses:&&") `shouldBe` (Expectation "*" "Uses:&&") + + it "corrects ruby and" $ do + run Ruby (Expectation "*" "Uses:and") `shouldBe` (Expectation "*" "UsesAnd") + run Ruby (Expectation "*" "Uses:&&") `shouldBe` (Expectation "*" "UsesAnd") + + describe "correct primitive declarations" $ do + it "corrects haskell otherwise negated" $ do + run Haskell (Expectation "*" "Not:Declares:otherwise") `shouldBe` (Expectation "*" "Not:DeclaresOtherwise") + + it "corrects haskell otherwise" $ do + run Haskell (Expectation "*" "Declares:otherwise") `shouldBe` (Expectation "*" "DeclaresOtherwise") + + it "corrects haskell and" $ do + run Haskell (Expectation "*" "Declares:and") `shouldBe` (Expectation "*" "Declares:and") + run Haskell (Expectation "*" "Declares:&&") `shouldBe` (Expectation "*" "DeclaresAnd") + + it "corrects haskell or" $ do + run Haskell (Expectation "*" "Declares:or") `shouldBe` (Expectation "*" "Declares:or") + run Haskell (Expectation "*" "Declares:||")`shouldBe` (Expectation "*" "DeclaresOr") + + it "corrects haskell not" $ do + run Haskell (Expectation "*" "Declares:not") `shouldBe` (Expectation "*" "DeclaresNegation") + run Haskell (Expectation "*" "Declares:!") `shouldBe` (Expectation "*" "Declares:!") + + it "corrects java and" $ do + run Java (Expectation "*" "Declares:and") `shouldBe` (Expectation "*" "Declares:and") + run Java (Expectation "*" "Declares:&&") `shouldBe` (Expectation "*" "DeclaresAnd") + + it "corrects python and" $ do + run Python (Expectation "*" "Declares:and") `shouldBe` (Expectation "*" "DeclaresAnd") + run Python (Expectation "*" "Declares:&&") `shouldBe` (Expectation "*" "Declares:&&") + + it "corrects ruby and" $ do + run Ruby (Expectation "*" "Declares:and") `shouldBe` (Expectation "*" "DeclaresAnd") + run Ruby (Expectation "*" "Declares:&&") `shouldBe` (Expectation "*" "DeclaresAnd") + + describe "corrects keyword usages" $ do + it "corrects haskell type usage with negation" $ do + run Haskell (Expectation "*" "Not:Uses:type") `shouldBe` (Expectation "*" "Not:DeclaresTypeAlias") + + it "corrects haskell type usage" $ do + run Haskell (Expectation "*" "Uses:type") `shouldBe` (Expectation "*" "DeclaresTypeAlias") + + it "corrects java if usage" $ do + run Java (Expectation "*" "Uses:if") `shouldBe` (Expectation "*" "UsesIf") + + it "corrects java class usage" $ do + run Java (Expectation "*" "Uses:class") `shouldBe` (Expectation "*" "DeclaresClass") + + it "corrects java interface usage" $ do + run Java (Expectation "*" "Uses:interface") `shouldBe` (Expectation "*" "DeclaresInterface") + + it "corrects java for usage" $ do + run Java (Expectation "*" "Uses:for") `shouldBe` (Expectation "*" "UsesForLoop") + + it "corrects python def usage" $ do + run Python (Expectation "*" "Uses:def") `shouldBe` (Expectation "*" "DeclaresComputation") + + it "corrects ruby class usage" $ do + run Ruby (Expectation "*" "Uses:class") `shouldBe` (Expectation "*" "DeclaresClass") + + it "corrects ruby include usage" $ do + run Ruby (Expectation "*" "Uses:include") `shouldBe` (Expectation "*" "Includes") + + it "corrects ruby def usage" $ do + run Ruby (Expectation "*" "Uses:def") `shouldBe` (Expectation "*" "DeclaresComputation") + + + describe "corrects keyword declarations" $ do + it "corrects haskell type declaration with negation" $ do + run Haskell (Expectation "*" "Not:Declares:type") `shouldBe` (Expectation "*" "Not:DeclaresTypeAlias") + + it "corrects haskell type declaration" $ do + run Haskell (Expectation "*" "Declares:type") `shouldBe` (Expectation "*" "DeclaresTypeAlias") + + it "corrects java if declaration" $ do + run Java (Expectation "*" "Declares:if") `shouldBe` (Expectation "*" "UsesIf") + + it "corrects java class declaration" $ do + run Java (Expectation "*" "Declares:class") `shouldBe` (Expectation "*" "DeclaresClass") + + it "corrects java interface declaration" $ do + run Java (Expectation "*" "Declares:interface") `shouldBe` (Expectation "*" "DeclaresInterface") + + it "corrects java for declaration" $ do + run Java (Expectation "*" "Declares:for") `shouldBe` (Expectation "*" "UsesForLoop") + + it "corrects python def declaration" $ do + run Python (Expectation "*" "Declares:def") `shouldBe` (Expectation "*" "DeclaresComputation") + + it "corrects ruby class declaration" $ do + run Ruby (Expectation "*" "Declares:class") `shouldBe` (Expectation "*" "DeclaresClass") + + it "corrects ruby include declaration" $ do + run Ruby (Expectation "*" "Declares:include") `shouldBe` (Expectation "*" "Includes") + + it "corrects ruby def declaration" $ do + run Ruby (Expectation "*" "Declares:def") `shouldBe` (Expectation "*" "DeclaresComputation") diff --git a/spec/ExpectationsAnalyzerSpec.hs b/spec/ExpectationsAnalyzerSpec.hs index e538fc0d5..c11dc7331 100644 --- a/spec/ExpectationsAnalyzerSpec.hs +++ b/spec/ExpectationsAnalyzerSpec.hs @@ -18,122 +18,144 @@ spec = describe "ExpectationsAnalyzer" $ do let ydeclares = Expectation "*" "Declares:y" (runAst None [ydeclares]) `shouldReturn` (result [failed ydeclares] []) - describe "Advanced expectations" $ do - it "evaluates unknown basic expectations" $ do - let hasTurtle = Expectation "x" "HasTurtle" - (run Haskell "x = 2" [hasTurtle]) `shouldReturn` (result [passed hasTurtle] []) - - it "evaluates unknown basic negated expectations" $ do - let notHasTurtle = Expectation "x" "Not:HasTurtle" - (run Haskell "x = 2" [notHasTurtle]) `shouldReturn` (result [passed notHasTurtle] []) - - it "evaluates empty expectations" $ do - (run Haskell "x = 2" []) `shouldReturn` (result [] []) - - it "evaluates present named expectations" $ do - let ydeclares = Expectation "*" "Declares:y" - let xdeclares = Expectation "*" "Declares:x" - (run Haskell "x = 2" [ydeclares, xdeclares]) `shouldReturn` (result [failed ydeclares, passed xdeclares] []) - - it "evaluates present expectations" $ do - let declaresF = Expectation "*" "DeclaresFunction" - let declaresT = Expectation "*" "DeclaresTypeAlias" - (run Haskell "f x = 2" [declaresF, declaresT]) `shouldReturn` (result [passed declaresF, failed declaresT] []) - - describe "Basic expectations" $ do - it "can be negated" $ do - let notDeclaresX = Expectation "*" "Not:Declares:x" - let notDeclaresY = Expectation "*" "Not:Declares:y" - (run Haskell "x = \"¡\"" [notDeclaresY, notDeclaresX]) `shouldReturn` (result [ - passed notDeclaresY, failed notDeclaresX] []) - it "works with Declares" $ do - let xdeclares = Expectation "*" "Declares:x" - let ydeclares = Expectation "*" "Declares:y" - (run Haskell "x = 2" [ydeclares, xdeclares]) `shouldReturn` (result [failed ydeclares, passed xdeclares] []) - - it "works with Uses" $ do - let usesy = Expectation "x" "Uses:y" - let usesz = Expectation "x" "Uses:z" - (run Haskell "x = y * 10" [usesy, usesz]) `shouldReturn` (result [passed usesy, failed usesz] []) - - it "works with DeclaresComputationWithArity" $ do - let hasArity2 = Expectation "*" "DeclaresComputationWithArity2:foo" - let hasArity3 = Expectation "*" "DeclaresComputationWithArity3:foo" - (run Prolog "foo(x, y)." [hasArity2, hasArity3]) `shouldReturn` (result [passed hasArity2, failed hasArity3] []) - - it "works with DeclaresTypeSignature" $ do - let declaresTypeSignature = Expectation "*" "DeclaresTypeSignature:f" - (run Haskell "f x y = y + x" [declaresTypeSignature]) `shouldReturn` (result [failed declaresTypeSignature] []) - (run Haskell "f :: Int -> Int -> Int \nf x y = y + x" [declaresTypeSignature]) `shouldReturn` (result [passed declaresTypeSignature] []) - - it "works with DeclaresTypeAlias" $ do - let hasTypeAlias = Expectation "*" "DeclaresTypeAlias:Words" - (run Haskell "type Works = [String]" [hasTypeAlias]) `shouldReturn` (result [failed hasTypeAlias] []) - (run Haskell "data Words = Words" [hasTypeAlias]) `shouldReturn` (result [failed hasTypeAlias] []) - (run Haskell "type Words = [String]" [hasTypeAlias]) `shouldReturn` (result [passed hasTypeAlias] []) - - it "works with UsesIf" $ do - let hasIf = Expectation "min" "UsesIf" - (run Haskell "min x y = True" [hasIf]) `shouldReturn` (result [failed hasIf] []) - (run Haskell "min x y = if x < y then x else y" [hasIf]) `shouldReturn` (result [passed hasIf] []) - - it "works with UsesGuards" $ do - let hasGuards = Expectation "min" "UsesGuards" - (run Haskell "min x y = x" [hasGuards]) `shouldReturn` (result [failed hasGuards] []) - (run Haskell "min x y | x < y = x | otherwise = y" [hasGuards]) `shouldReturn` (result [passed hasGuards] []) - - it "works with UsesAnonymousVariable" $ do - let hasAnonymousVariable = Expectation "c" "UsesAnonymousVariable" - (run Haskell "c x = 14" [hasAnonymousVariable]) `shouldReturn` (result [failed hasAnonymousVariable] []) - (run Haskell "c _ = 14" [hasAnonymousVariable]) `shouldReturn` (result [passed hasAnonymousVariable] []) - - it "works with UsesComposition" $ do - let hasComposition = Expectation "h" "UsesComposition" - (run Haskell "h = f" [hasComposition]) `shouldReturn` (result [failed hasComposition] []) - (run Haskell "h = f . g" [hasComposition]) `shouldReturn` (result [passed hasComposition] []) - - it "works with UsesForComprehension" $ do - let hasComprehension = Expectation "x" "UsesForComprehension" - (run Haskell "x = [m | m <- t]" [hasComprehension]) `shouldReturn` (result [passed hasComprehension] []) - - it "works with UsesForLoop" $ do - let hasForLoop = Expectation "f" "UsesForLoop" - (run JavaScript "function f() { var x; for (x = 0; x < 10; x++) { x; } }" [hasForLoop]) `shouldReturn` (result [passed hasForLoop] []) - - it "works with UsesConditional" $ do - let hasConditional = Expectation "min" "UsesConditional" - (run JavaScript "function min(x, y) { if (x < y) { return x } else { return y } }" [hasConditional]) `shouldReturn` (result [ - passed hasConditional] []) - - it "works with UsesWhile" $ do - let hasWhile = Expectation "f" "UsesWhile" - (run JavaScript "function f() { var x = 5; while (x < 10) { x++ } }" [hasWhile]) `shouldReturn` (result [passed hasWhile] []) - - it "works with HasForall" $ do - let hasForall = Expectation "f" "HasForall" - (run Prolog "f(X) :- isElement(Usuario), forall(isRelated(X, Y), complies(Y))." [hasForall]) `shouldReturn` (result [ - passed hasForall] []) - - it "works with UsesFindall" $ do - let hasFindall = Expectation "baz" "UsesFindall" - (run Prolog "baz(X):- bar(X, Y)." [hasFindall]) `shouldReturn` (result [failed hasFindall] []) - (run Prolog "baz(X):- findall(Y, bar(X, Y), Z)." [hasFindall]) `shouldReturn` (result [passed hasFindall] []) - - it "works with UsesLambda" $ do - let hasLambda = Expectation "f" "UsesLambda" - (run Haskell "f = map id" [hasLambda]) `shouldReturn` (result [failed hasLambda] []) - (run Haskell "f = map $ \\x -> x + 1" [hasLambda]) `shouldReturn` (result [passed hasLambda] []) - - it "works with DeclaresRecursively" $ do - let hasDirectRecursion = Expectation "*" "DeclaresRecursively:f" - (run Haskell "f x = if x < 5 then g (x - 1) else 2" [hasDirectRecursion]) `shouldReturn` (result [failed hasDirectRecursion] []) - (run Haskell "f x = if x < 5 then f (x - 1) else 2" [hasDirectRecursion]) `shouldReturn` (result [passed hasDirectRecursion] []) - - it "works with UsesNot" $ do - let hasNot = Expectation "foo" "UsesNot" - (run Prolog "foo(X) :- bar(X)." [hasNot]) `shouldReturn` (result [failed hasNot] []) - (run Prolog "foo(X) :- not(bar(X))." [hasNot]) `shouldReturn` (result [passed hasNot] []) - - it "properly reports parsing errors" $ do - let hasNot = Expectation "foo" "UsesNot" - (run Haskell " foo " [hasNot]) `shouldReturn` (AnalysisFailed "Parse error") + it "evaluates unknown basic expectations" $ do + let hasTurtle = Expectation "x" "HasTurtle" + (run Haskell "x = 2" [hasTurtle]) `shouldReturn` (result [passed hasTurtle] []) + + it "evaluates unknown basic negated expectations" $ do + let notHasTurtle = Expectation "x" "Not:HasTurtle" + (run Haskell "x = 2" [notHasTurtle]) `shouldReturn` (result [passed notHasTurtle] []) + + it "evaluates empty expectations" $ do + (run Haskell "x = 2" []) `shouldReturn` (result [] []) + + it "evaluates present named expectations" $ do + let ydeclares = Expectation "*" "Declares:y" + let xdeclares = Expectation "*" "Declares:x" + (run Haskell "x = 2" [ydeclares, xdeclares]) `shouldReturn` (result [failed ydeclares, passed xdeclares] []) + + it "evaluates present expectations" $ do + let declaresF = Expectation "*" "DeclaresFunction" + let declaresT = Expectation "*" "DeclaresTypeAlias" + (run Haskell "f x = 2" [declaresF, declaresT]) `shouldReturn` (result [passed declaresF, failed declaresT] []) + + it "can be negated" $ do + let notDeclaresX = Expectation "*" "Not:Declares:x" + let notDeclaresY = Expectation "*" "Not:Declares:y" + (run Haskell "x = \"¡\"" [notDeclaresY, notDeclaresX]) `shouldReturn` (result [ + passed notDeclaresY, failed notDeclaresX] []) + it "works with Declares" $ do + let xdeclares = Expectation "*" "Declares:x" + let ydeclares = Expectation "*" "Declares:y" + (run Haskell "x = 2" [ydeclares, xdeclares]) `shouldReturn` (result [failed ydeclares, passed xdeclares] []) + + it "works with Uses" $ do + let usesy = Expectation "x" "Uses:y" + let usesz = Expectation "x" "Uses:z" + (run Haskell "x = y * 10" [usesy, usesz]) `shouldReturn` (result [passed usesy, failed usesz] []) + + it "works with DeclaresComputationWithArity" $ do + let hasArity2 = Expectation "*" "DeclaresComputationWithArity2:foo" + let hasArity3 = Expectation "*" "DeclaresComputationWithArity3:foo" + (run Prolog "foo(x, y)." [hasArity2, hasArity3]) `shouldReturn` (result [passed hasArity2, failed hasArity3] []) + + it "works with DeclaresTypeSignature" $ do + let declaresTypeSignature = Expectation "*" "DeclaresTypeSignature:f" + (run Haskell "f x y = y + x" [declaresTypeSignature]) `shouldReturn` (result [failed declaresTypeSignature] []) + (run Haskell "f :: Int -> Int -> Int \nf x y = y + x" [declaresTypeSignature]) `shouldReturn` (result [passed declaresTypeSignature] []) + + it "works with DeclaresTypeAlias" $ do + let hasTypeAlias = Expectation "*" "DeclaresTypeAlias:Words" + (run Haskell "type Works = [String]" [hasTypeAlias]) `shouldReturn` (result [failed hasTypeAlias] []) + (run Haskell "data Words = Words" [hasTypeAlias]) `shouldReturn` (result [failed hasTypeAlias] []) + (run Haskell "type Words = [String]" [hasTypeAlias]) `shouldReturn` (result [passed hasTypeAlias] []) + + it "works with UsesIf" $ do + let hasIf = Expectation "min" "UsesIf" + (run Haskell "min x y = True" [hasIf]) `shouldReturn` (result [failed hasIf] []) + (run Haskell "min x y = if x < y then x else y" [hasIf]) `shouldReturn` (result [passed hasIf] []) + + it "works with UsesGuards" $ do + let hasGuards = Expectation "min" "UsesGuards" + (run Haskell "min x y = x" [hasGuards]) `shouldReturn` (result [failed hasGuards] []) + (run Haskell "min x y | x < y = x | otherwise = y" [hasGuards]) `shouldReturn` (result [passed hasGuards] []) + + it "works with UsesAnonymousVariable" $ do + let hasAnonymousVariable = Expectation "c" "UsesAnonymousVariable" + (run Haskell "c x = 14" [hasAnonymousVariable]) `shouldReturn` (result [failed hasAnonymousVariable] []) + (run Haskell "c _ = 14" [hasAnonymousVariable]) `shouldReturn` (result [passed hasAnonymousVariable] []) + + it "works with UsesComposition" $ do + let hasComposition = Expectation "h" "UsesComposition" + (run Haskell "h = f" [hasComposition]) `shouldReturn` (result [failed hasComposition] []) + (run Haskell "h = f . g" [hasComposition]) `shouldReturn` (result [passed hasComposition] []) + + it "works with UsesForComprehension" $ do + let hasComprehension = Expectation "x" "UsesForComprehension" + (run Haskell "x = [m | m <- t]" [hasComprehension]) `shouldReturn` (result [passed hasComprehension] []) + + it "works with UsesForLoop" $ do + let hasForLoop = Expectation "f" "UsesForLoop" + (run JavaScript "function f() { var x; for (x = 0; x < 10; x++) { x; } }" [hasForLoop]) `shouldReturn` (result [passed hasForLoop] []) + + it "works with UsesConditional" $ do + let hasConditional = Expectation "min" "UsesConditional" + (run JavaScript "function min(x, y) { if (x < y) { return x } else { return y } }" [hasConditional]) `shouldReturn` (result [ + passed hasConditional] []) + + it "works with UsesWhile" $ do + let hasWhile = Expectation "f" "UsesWhile" + (run JavaScript "function f() { var x = 5; while (x < 10) { x++ } }" [hasWhile]) `shouldReturn` (result [passed hasWhile] []) + + it "works with HasForall" $ do + let hasForall = Expectation "f" "HasForall" + (run Prolog "f(X) :- isElement(Usuario), forall(isRelated(X, Y), complies(Y))." [hasForall]) `shouldReturn` (result [ + passed hasForall] []) + + it "works with UsesFindall" $ do + let hasFindall = Expectation "baz" "UsesFindall" + (run Prolog "baz(X):- bar(X, Y)." [hasFindall]) `shouldReturn` (result [failed hasFindall] []) + (run Prolog "baz(X):- findall(Y, bar(X, Y), Z)." [hasFindall]) `shouldReturn` (result [passed hasFindall] []) + + it "works with UsesLambda" $ do + let hasLambda = Expectation "f" "UsesLambda" + (run Haskell "f = map id" [hasLambda]) `shouldReturn` (result [failed hasLambda] []) + (run Haskell "f = map $ \\x -> x + 1" [hasLambda]) `shouldReturn` (result [passed hasLambda] []) + + it "works with DeclaresRecursively" $ do + let hasDirectRecursion = Expectation "*" "DeclaresRecursively:f" + (run Haskell "f x = if x < 5 then g (x - 1) else 2" [hasDirectRecursion]) `shouldReturn` (result [failed hasDirectRecursion] []) + (run Haskell "f x = if x < 5 then f (x - 1) else 2" [hasDirectRecursion]) `shouldReturn` (result [passed hasDirectRecursion] []) + + it "works with UsesNot" $ do + let hasNot = Expectation "foo" "UsesNot" + (run Prolog "foo(X) :- bar(X)." [hasNot]) `shouldReturn` (result [failed hasNot] []) + (run Prolog "foo(X) :- not(bar(X))." [hasNot]) `shouldReturn` (result [passed hasNot] []) + + it "properly reports parsing errors" $ do + let hasNot = Expectation "foo" "UsesNot" + (run Haskell " foo " [hasNot]) `shouldReturn` (AnalysisFailed "Parse error") + + it "works with keyword-based expectation synthesis of declares" $ do + let usesType = Expectation "*" "Uses:type" + let declaresTypeAlias = Expectation "*" "DeclaresTypeAlias" + run Haskell "type X = Int" [usesType] `shouldReturn` (result [passed declaresTypeAlias] []) + + it "works with keyword-based expectation synthesis of uses" $ do + let usesType = Expectation "*" "Uses:type" + let declaresTypeAlias = Expectation "*" "DeclaresTypeAlias" + run Haskell "type X = Int" [usesType] `shouldReturn` (result [passed declaresTypeAlias] []) + + it "works with operator-based expectation synthesis of declares" $ do + let declaresNot = Expectation "*" "Declares:not" + let usesNegation = Expectation "*" "DeclaresNegation" + run Haskell "x = not True" [declaresNot] `shouldReturn` (result [failed usesNegation] []) + + it "works with operator-based expectation synthesis of uses" $ do + let usesNot = Expectation "*" "Uses:not" + let usesNegation = Expectation "*" "UsesNegation" + run Haskell "x = not True" [usesNot] `shouldReturn` (result [passed usesNegation] []) + + it "works with operators" $ do + let usesNegation = Expectation "*" "UsesNegation" + run Haskell "x = not True" [usesNegation] `shouldReturn` (result [passed usesNegation] []) diff --git a/spec/ExpectationsCompilerSpec.hs b/spec/ExpectationsCompilerSpec.hs index 73701636c..9a3f83dbf 100644 --- a/spec/ExpectationsCompilerSpec.hs +++ b/spec/ExpectationsCompilerSpec.hs @@ -227,6 +227,10 @@ spec = do run (java "class Foo implements Bar {}") "Foo" "Implements:Bar" `shouldBe` True run (java "class Foo implements Bar {}") "Foo" "Implements:Baz" `shouldBe` False + it "works with Print" $ do + run (java "public class Foo { public static void main() { } }") "*" "UsesPrint" `shouldBe` False + run (java "public class Foo { public static void main() { System.out.println((5).equals(6)); } }") "*" "UsesPrint" `shouldBe` True + it "works with Inherits" $ do run (java "class Foo extends Bar {}") "Foo" "Inherits:Bar" `shouldBe` True run (java "class Foo extends Bar {}") "Foo" "Inherits:Baz" `shouldBe` False @@ -235,3 +239,22 @@ spec = do run (java "class Foo implements Bar {}") "Foo" "Implements:[Bar|Baz|Foobar]" `shouldBe` True run (java "class Foo implements Baz {}") "Foo" "Implements:[Bar|Baz|Foobar]" `shouldBe` True run (java "class Foo implements Foobaz {}") "Foo" "Implements:[Bar|Baz|Foobar]" `shouldBe` False + + it "works with primitive operators in js" $ do + run (js "x == 4") "*" "UsesEqual" `shouldBe` True + run (js "x != 4") "*" "UsesEqual" `shouldBe` False + + it "works with primitive operators in hs" $ do + run (hs "x = x . y") "*" "UsesForwardComposition" `shouldBe` False + run (hs "x = x . y") "*" "UsesBackwardComposition" `shouldBe` True + + it "works with primitive operators usages in java" $ do + run (java "public class Foo { public static void main() { System.out.println((5).equals(6)); } }") "*" "UsesHash" `shouldBe` False + run (java "public class Foo { public static void main() { System.out.println((5).equals(6)); } }") "*" "UsesEqual" `shouldBe` True + + it "works with primitive operators declarations in java" $ do + run (java "public class Foo { public boolean equals(Object other) { return false; } }") "*" "DeclaresEqual" `shouldBe` True + run (java "public class Foo { public boolean equals(Object other) { return false; } }") "*" "DeclaresHash" `shouldBe` False + + it "does not mix keywords with inspections" $ do + run (hs "type X = Int") "*" "Uses:type" `shouldBe` False diff --git a/spec/GeneratorSpec.hs b/spec/GeneratorSpec.hs index ebc5303f6..386aa0e99 100644 --- a/spec/GeneratorSpec.hs +++ b/spec/GeneratorSpec.hs @@ -16,7 +16,7 @@ spec = do describe "referencedIdentifiers" $ do it "answers referenced identifiers" $ do let code = hs "f x = (:[]) . m x y . g h 2" - (referencedIdentifiers code) `shouldBe` [".", "flip", ":", "m","x","y", "g","h"] + (referencedIdentifiers code) `shouldBe` ["flip", ":", "m","x","y", "g","h"] describe "transitiveReferencedIdentifiers" $ do it "answers transitive referenced identifiers" $ do diff --git a/spec/InspectorSpec.hs b/spec/InspectorSpec.hs index 72faa772b..9bd477431 100644 --- a/spec/InspectorSpec.hs +++ b/spec/InspectorSpec.hs @@ -416,6 +416,20 @@ spec = do it "is False when using a matcher that does not match" $ do (callsMatching (with "1") anyone) (hs "f = g 2") `shouldBe` False + describe "usesBooleanLogic" $ do + it "is when it is used" $ do + usesBooleanLogic (hs "f x y = x || y") `shouldBe` True + usesBooleanLogic (hs "f x y = x && y") `shouldBe` True + usesBooleanLogic (hs "f x y = not x") `shouldBe` True + usesBooleanLogic (hs "f x y = (not) x") `shouldBe` True + usesBooleanLogic (hs "f x y = (&&) x y") `shouldBe` True + + it "is is not used otherwise" $ do + usesBooleanLogic (hs "f x y = x + y") `shouldBe` False + usesBooleanLogic (hs "f x y = x") `shouldBe` False + usesBooleanLogic (hs "f x y = and x") `shouldBe` False + usesBooleanLogic (hs "f x y = or x") `shouldBe` False + describe "usesExceptions" $ do it "is True when a raise is used, java" $ do usesExceptions (java "class Sample { void aMethod() { throw new RuntimeException(); } }") `shouldBe` True @@ -465,8 +479,11 @@ spec = do it "is True when required function is used as argument" $ do uses (named "m") (hs "y x = x m") `shouldBe` True + it "is False with primitives" $ do + uses (named "&&") (hs "y x = x && z") `shouldBe` False + it "is True when required function is used as operator" $ do - uses (named "&&" )(hs "y x = x && z") `shouldBe` True + uses (named "<>") (hs "y x = x <> z") `shouldBe` True it "is False when required function is not used in constant" $ do uses (named "m") (hs "y = 3") `shouldBe` False @@ -566,6 +583,32 @@ spec = do transitive "p" (uses (named "m")) (js "var o = {g: function(){ m }}\n\ \var p = {n: function() { o.g() }}") `shouldBe` True + describe "usesPrimitive, hs" $ do + it "is True when required primitive is used on application" $ do + usesPrimitive And (hs "y x = x && z") `shouldBe` True + usesPrimitive BackwardComposition (hs "y x = x . z") `shouldBe` True + usesPrimitive Negation (hs "y x = not z") `shouldBe` True + + it "is True when required primitive is used as argument" $ do + usesPrimitive And (hs "y x = f (&&) y z") `shouldBe` True + + it "is False when primitive is just apparently used" $ do + usesPrimitive And (hs "y x = and x") `shouldBe` False + + it "is False when primitive is not used" $ do + usesPrimitive Negation (hs "y x = m x") `shouldBe` False + + describe "usesPrimitive, js" $ do + it "is True when required primitive is used on application" $ do + usesPrimitive And (js "x && z") `shouldBe` True + usesPrimitive Negation (js "function () { return !z }") `shouldBe` True + + it "is False when primitive is just apparently used" $ do + usesPrimitive Or (js "or(x)") `shouldBe` False + + it "is False when primitive is not used" $ do + usesPrimitive ForwardComposition (js "f(g(x))") `shouldBe` False + describe "declaresComputation" $ do describe "with constants" $ do it "is False when exists" $ do diff --git a/spec/JavaScriptSpec.hs b/spec/JavaScriptSpec.hs index 3ceff0bad..da6a3649e 100644 --- a/spec/JavaScriptSpec.hs +++ b/spec/JavaScriptSpec.hs @@ -93,10 +93,10 @@ spec = do js "true" `shouldBe` MuTrue it "handles negation" $ do - js "!true" `shouldBe` (Application (Reference "!") [MuTrue]) + js "!true" `shouldBe` (Application (Primitive Negation) [MuTrue]) it "handles boolean binary operations" $ do - js "true || false " `shouldBe` (Application (Reference "||") [MuTrue, MuFalse]) + js "true || false " `shouldBe` (Application (Primitive Or) [MuTrue, MuFalse]) it "handles lambdas" $ do js "(function(x, y) { 1 })" `shouldBe` (Lambda [VariablePattern "x", VariablePattern "y"] (MuNumber 1)) diff --git a/spec/JavaSpec.hs b/spec/JavaSpec.hs index 709c79c82..1759f8c1b 100644 --- a/spec/JavaSpec.hs +++ b/spec/JavaSpec.hs @@ -12,7 +12,6 @@ import NeatInterpolation (text) run :: Text -> Expression run = java . unpack - spec :: Spec spec = do describe "parse" $ do @@ -71,7 +70,7 @@ spec = do class Foo { public A hello(A a) {} }|] `shouldBe` Class "Foo" Nothing (Sequence [ - SubroutineSignature "hello" ["A"] "A" [], + SubroutineSignature "hello" ["A"] "A" ["A"], (SimpleMethod "hello" [VariablePattern "a"] None)]) it "parses Methods with type arguments and type constraints" $ do @@ -79,7 +78,7 @@ spec = do class Foo { public A hello(A a) {} }|] `shouldBe` Class "Foo" Nothing (Sequence [ - SubroutineSignature "hello" ["A"] "A" [], + SubroutineSignature "hello" ["A"] "A" ["A"], (SimpleMethod "hello" [VariablePattern "a"] None)]) it "parses Empty Returns" $ do @@ -124,7 +123,7 @@ spec = do public boolean hello() { return !true; } }|] `shouldBe` Class "Foo" Nothing (Sequence [ SubroutineSignature "hello" [] "boolean" [], - (SimpleMethod "hello" [] (Return (SimpleSend MuTrue "!" [])))]) + (SimpleMethod "hello" [] (Return (PrimitiveSend MuTrue Negation [])))]) it "parses Chars In Returns" $ do run [text|class Foo { @@ -133,6 +132,15 @@ spec = do SubroutineSignature "hello" [] "char" [], (SimpleMethod "hello" [] (Return (MuChar 'f')))]) + it "parses equals methods invocations" $ do + run [text|public class Foo { + public static void main() { + System.out.println((5).equals(6)); + } + }|] `shouldBe` Class "Foo" Nothing (Sequence [ + SubroutineSignature "main" [] "void" [], + (SimpleMethod "main" [] (Print (PrimitiveSend (MuNumber 5) Equal [MuNumber 6])))]) + it "parses Parameters" $ do run "public class Foo extends Bar { int succ(int y) {} }" `shouldBe` Class "Foo" (Just "Bar") (Sequence [ SubroutineSignature "succ" ["int"] "int" [], @@ -262,7 +270,7 @@ spec = do }|] `shouldBe` Class "Foo" Nothing (Sequence [ SubroutineSignature "hello" ["String"] "void" [], SimpleMethod "hello" [VariablePattern "x"] ( - If (Send (Reference "x") Equal [MuString "foo"]) + If (Send (Reference "x") (Primitive Equal) [MuString "foo"]) None None)]) @@ -273,7 +281,7 @@ spec = do }|] `shouldBe` Class "Foo" Nothing (Sequence [ SubroutineSignature "hello" ["String"] "void" [], (SimpleMethod "hello" [VariablePattern "x"] ( - If (Send (Reference "x") NotEqual [MuString "foo"]) + If (Send (Reference "x") (Primitive NotEqual) [MuString "foo"]) None None))]) diff --git a/spec/PythonSpec.hs b/spec/PythonSpec.hs index 090c1c911..a7fabc05d 100644 --- a/spec/PythonSpec.hs +++ b/spec/PythonSpec.hs @@ -69,7 +69,7 @@ spec = do py "1;2;3" `shouldBe` Sequence [MuNumber 1, MuNumber 2, MuNumber 3] it "parses unary operators" $ do - py "not True" `shouldBe` (Application (Reference "not") [MuBool True]) + py "not True" `shouldBe` (Application (Primitive Negation) [MuBool True]) it "parses classes" $ do py "class DerivedClassName: pass" `shouldBe` Class "DerivedClassName" Nothing None diff --git a/spec/SmellSpec.hs b/spec/SmellSpec.hs index ab7c6a6e3..64e84aebd 100644 --- a/spec/SmellSpec.hs +++ b/spec/SmellSpec.hs @@ -83,13 +83,13 @@ spec = do hasRedundantBooleanComparison (hs "f x = True") `shouldBe` False it "is True when comparing self with a boolean, using a message" $ do - hasRedundantBooleanComparison (Send Self Equal [MuBool True]) `shouldBe` True + hasRedundantBooleanComparison (PrimitiveSend Self Equal [MuBool True]) `shouldBe` True it "is True when comparing a boolean with a reference, using a message" $ do - hasRedundantBooleanComparison (Send (MuBool False) NotEqual [Reference "x"]) `shouldBe` True + hasRedundantBooleanComparison (PrimitiveSend (MuBool False) NotEqual [Reference "x"]) `shouldBe` True it "is False when comparing references" $ do - hasRedundantBooleanComparison (Send (Reference "y") Equal [Reference "x"]) `shouldBe` False + hasRedundantBooleanComparison (PrimitiveSend (Reference "y") Equal [Reference "x"]) `shouldBe` False describe "hasRedundantLocalVariableReturn" $ do it "is True when local variable is not necessary" $ do @@ -191,6 +191,23 @@ spec = do it "is False when there is no guard" $ do hasRedundantGuards (hs "x = False") `shouldBe` False + describe "shouldUseOtherwise" $ do + it "is True when there are two guards and last one is a True" $ do + shouldUseOtherwise (hs "f x | c x = True\n\ + \ | True = False") `shouldBe` True + + it "is True when there are three guards and last one is a True" $ do + shouldUseOtherwise (hs "f x | c x = True\n\ + \ | g x = False\n\ + \ | True = False") `shouldBe` True + + it "is True when last guard is an otherwsie" $ do + shouldUseOtherwise (hs "f x | c x = True\n\ + \ | otherwise = False") `shouldBe` False + + it "is False when there no guards" $ do + shouldUseOtherwise (hs "f x = True") `shouldBe` False + describe "discardsExceptions" $ do it "is True when there is an empty catch" $ do discardsExceptions (javaStatement "try { new Bar().baz(); } catch (Exception e) { /*TODO handle exception*/ }") `shouldBe` True diff --git a/spec/SmellsAnalyzerSpec.hs b/spec/SmellsAnalyzerSpec.hs index c976ad72a..a95bfb8a1 100644 --- a/spec/SmellsAnalyzerSpec.hs +++ b/spec/SmellsAnalyzerSpec.hs @@ -11,20 +11,27 @@ runExcept language content smells = analyse (smellsAnalysis (CodeSample language runOnly language content smells = analyse (smellsAnalysis (CodeSample language content) (noSmellsBut smells)) spec = describe "SmellsAnalyzer" $ do - describe "Using domain language and nested structures" $ do + it "Using domain language and nested structures" $ do let runRuby sample = analyse (domainLanguageAnalysis (MulangSample sample Nothing) (DomainLanguage Nothing (Just RubyCase) (Just 3) Nothing)) - it "works with empty set" $ do - (runRuby (Sequence [ - (Object "Foo_Bar" (Sequence [ - (SimpleMethod "y" [] None), - (SimpleMethod "aB" [] None), - (SimpleMethod "fooBar" [] None)])), - (Object "Foo" None)])) `shouldReturn` (result [ - Expectation "y" "HasTooShortIdentifiers", - Expectation "aB" "HasTooShortIdentifiers", - Expectation "Foo_Bar" "HasWrongCaseIdentifiers", - Expectation "aB" "HasWrongCaseIdentifiers", - Expectation "fooBar" "HasWrongCaseIdentifiers"]) + (runRuby (Sequence [ + (Object "Foo_Bar" (Sequence [ + (SimpleMethod "y" [] None), + (SimpleMethod "aB" [] None), + (SimpleMethod "fooBar" [] None)])), + (Object "Foo" None)])) `shouldReturn` (result [ + Expectation "y" "HasTooShortIdentifiers", + Expectation "aB" "HasTooShortIdentifiers", + Expectation "Foo_Bar" "HasWrongCaseIdentifiers", + Expectation "aB" "HasWrongCaseIdentifiers", + Expectation "fooBar" "HasWrongCaseIdentifiers"]) + + it "works inferring domain language" $ do + let runPython sample = runExcept Python3 sample [] + runPython "def fooBar():\n\tpass\n\ndef foo_baz():\n\tpass\n\n" `shouldReturn` (result [Expectation "fooBar" "HasWrongCaseIdentifiers"]) + + it "works inferring caseStyl" $ do + let runPython sample = analyse (domainLanguageAnalysis (CodeSample Python3 sample) (DomainLanguage Nothing Nothing (Just 3) Nothing)) + runPython "def fooBar():\n\tpass\n\ndef foo_baz():\n\tpass\n\n" `shouldReturn` (result [Expectation "fooBar" "HasWrongCaseIdentifiers"]) describe "Using exclusion" $ do it "works with empty set, in java" $ do diff --git a/spec/UnjavaSpec.hs b/spec/UnjavaSpec.hs new file mode 100644 index 000000000..706b45df2 --- /dev/null +++ b/spec/UnjavaSpec.hs @@ -0,0 +1,97 @@ +{-# LANGUAGE QuasiQuotes, OverloadedStrings #-} + +module UnjavaSpec (spec) where + +import Test.Hspec +import Language.Mulang +import Language.Mulang.Parsers.Java (java) +import Language.Mulang.Unparsers.Java (unparseJava) + +shouldRoundTrip expression = (java.unparseJava) expression `shouldBe` expression + +spec :: Spec +spec = do + let wrapStatement expr = (Class "Sample" Nothing + (Sequence [ + SubroutineSignature "sample" [] "void" [], + SimpleMethod "sample" [] expr + ])) + let wrapExpression expr = wrapStatement (Return expr) + + describe "unparse" $ do + let itWorksWith expr expectedCode = it (show expr) (unparseJava expr `shouldBe` expectedCode) + let itWorksWithJavaExpression expr expectedCode = it (show expr) (unparseJava (wrapExpression expr) `shouldBe` expectedCode) + let itWorksWithJavaStatement expr expectedCode = it (show expr) (unparseJava (wrapStatement expr) `shouldBe` expectedCode) + + itWorksWithJavaExpression (MuNumber 1.5) "public class Sample { \tpublic void sample ( ) { return 1.5 ; }\n }" + itWorksWithJavaStatement ((Assignment "x" (Send (Reference "x") (Reference "+") [MuNumber 8]))) "public class Sample { \tpublic void sample ( ) { x = (x + 8) ; }\n }" + itWorksWithJavaStatement (Sequence [Print (MuString "hello"), Print (MuString "world")]) "public class Sample { \tpublic void sample ( ) { System.out.println(\"hello\");\n\tSystem.out.println(\"world\");\n\t }\n }" + itWorksWithJavaStatement (Sequence [Print (MuString "hello"), SimpleSend Self "foo" []]) "public class Sample { \tpublic void sample ( ) { System.out.println(\"hello\");\n\tthis.foo();\n\t }\n }" + + itWorksWithJavaStatement (While MuTrue None) "public class Sample { \tpublic void sample ( ) { while ( true ) { } }\n }" + itWorksWithJavaStatement (While MuTrue (SimpleSend Self "foo" [])) "public class Sample { \tpublic void sample ( ) { while ( true ) { \tthis.foo();\n\t } }\n }" + + describe "roundTrip" $ do + let itWorksWith expr = it (show expr) (shouldRoundTrip expr) + let itWorksWithJavaExpression expr = it (show expr) (shouldRoundTrip (wrapExpression expr)) + let itWorksWithJavaStatement expr = it (show expr) (shouldRoundTrip (wrapStatement expr)) + + describe "literals" $ do + itWorksWithJavaExpression (MuNumber 1.5) + itWorksWithJavaExpression (MuNumber 1) + itWorksWithJavaExpression MuTrue + itWorksWithJavaExpression (MuString "some string") + + itWorksWithJavaStatement (Assignment "one" (MuNumber 1)) + itWorksWithJavaExpression ((Reference "x")) + itWorksWithJavaExpression ((Send Self (Reference "f") [MuNumber 2])) + itWorksWithJavaExpression ((Send (Reference "o") (Reference "f") [(MuNumber 2)])) + itWorksWithJavaStatement ((Assignment "x" (Send (Reference "x") (Reference "+") [MuNumber 8]))) + itWorksWithJavaExpression ((Send (Reference "x") (Reference "+") [Reference "y"])) + itWorksWithJavaStatement (Sequence [Print (MuString "hello"), Print (MuString "world")]) + itWorksWithJavaStatement (Sequence [Print (MuString "hello"), SimpleSend Self "foo" []]) + itWorksWithJavaExpression ((Send MuTrue (Primitive Negation) [])) + + describe "classes" $ do + itWorksWith (Class "DerivedClassName" Nothing None) + itWorksWith (Class "DerivedClassName" (Just "BaseClassName") None) + itWorksWith (Class "Foo" Nothing None) + itWorksWith (Class "Foo" (Just "Bar") None) + + --itWorksWithJavaStatement (If MuTrue (MuNumber 1) (If MuFalse (MuNumber 2) (MuNumber 3))) + -- itWorksWith (Class "Sample" Nothing (SimpleMethod "foo" [VariablePattern "param"] (Print (Reference "param")))) + itWorksWithJavaStatement (While MuTrue None) + -- itWorksWithJavaStatement (For [Generator (TuplePattern [VariablePattern "x"]) (Application (Reference "range") [MuNumber 0, MuNumber 3])] None) + -- itWorksWithJavaStatement (Raise (Application (Reference "Exception") [MuString "something"])) + + -- describe "lambdas" $ do + -- itWorksWith $ (Lambda [VariablePattern "x"] (MuNumber 1)) + -- itWorksWith $ (Lambda [] (Reference "foo")) + + describe "boolean operations" $ do + let muand x y = (Send x (Primitive And) [y]) + let muor x y = (Send x (Primitive Or) [y]) + let muneg x = (Send x (Primitive Negation) []) + + itWorksWithJavaExpression $ (Reference "a") `muand` (Reference "b") + itWorksWithJavaExpression $ (Reference "a") `muor` (Reference "b") + itWorksWithJavaExpression $ (muneg (Reference "a")) `muor` (Reference "b") + itWorksWithJavaExpression $ muneg ((Reference "a") `muor` (Reference "b")) + itWorksWithJavaExpression $ muneg ((Reference "a") `muand` (Reference "b")) `muor` (Reference "c") + itWorksWithJavaExpression $ (Reference "a") `muand` ((Reference "b") `muor` (Reference "c")) + + describe "interface" $ do + itWorksWith (Interface "Foo" [] None) + itWorksWith (Interface "Foo" [] (SubroutineSignature "foo" [] "void" [])) + itWorksWith (Interface "Foo" [] (SubroutineSignature "foo" [] "A" ["A"])) + itWorksWith (Interface "Foo" [] (SubroutineSignature "foo" [] "int" [])) + itWorksWith (Interface "Foo" [] (Sequence [ + SubroutineSignature "foo" [] "void" [], + SubroutineSignature "bar" [] "int" []])) + itWorksWith (Interface "Foo" [] (SubroutineSignature "foo" ["String", "int"] "void" [])) + itWorksWith (Interface "Foo" ["Bar", "Baz"] None) + + -- shouldRoundTrip (Class "Foo" Nothing (Sequence [ + -- TypeSignature "hello" (ParameterizedType [] "void" []), + -- SimpleMethod "hello" [] None + -- ])) diff --git a/spec/UnpythonSpec.hs b/spec/UnpythonSpec.hs new file mode 100644 index 000000000..4fa089784 --- /dev/null +++ b/spec/UnpythonSpec.hs @@ -0,0 +1,104 @@ +module UnpythonSpec (spec) where + +import Test.Hspec +import Language.Mulang + +import Language.Mulang.Parsers.Python (py) +import Language.Mulang.Unparsers.Python (unparsePython) + +shouldRoundTrip expression = (py.unparsePython) expression `shouldBe` expression + +spec :: Spec +spec = do + describe "roundTrip" $ do + let itWorksWith expr = it (show expr) (shouldRoundTrip expr) + + describe "literals" $ do + itWorksWith $ (MuNumber 1.5) + itWorksWith $ (MuNumber 1) + itWorksWith $ MuTrue + itWorksWith $ (MuString "some string") + itWorksWith $ (MuList [MuNumber 1, MuNumber 2, MuNumber 3]) + itWorksWith $ (MuList [MuNumber 1, MuNumber 2, MuNumber 3]) + + itWorksWith $ (Assignment "one" (MuNumber 1)) + itWorksWith $ ((Reference "x")) + itWorksWith $ ((Application (Reference "f") [MuNumber 2])) + itWorksWith $ ((Send (Reference "o") (Reference "f") [(MuNumber 2)])) + itWorksWith $ ((Assignment "x" (Application (Reference "+") [Reference "x",MuNumber 8]))) + itWorksWith $ ((Application (Reference "+") [Reference "x",Reference "y"])) + itWorksWith $ (Sequence [MuNumber 1, MuNumber 2, MuNumber 3]) + itWorksWith $ ((Application (Primitive Negation) [MuTrue])) + + describe "classes" $ do + itWorksWith $ (Class "DerivedClassName" Nothing None) + itWorksWith $ (Class "DerivedClassName" (Just "BaseClassName") None) + + itWorksWith $ (If MuTrue (MuNumber 1) (If MuFalse (MuNumber 2) (MuNumber 3))) + itWorksWith $ (SimpleFunction "foo" [] (Return (MuNumber 1))) + itWorksWith $ (SimpleProcedure "foo" [VariablePattern "param"] (Print (Reference "param"))) + itWorksWith $ (While MuTrue None) + itWorksWith $ (For [Generator (TuplePattern [VariablePattern "x"]) (Application (Reference "range") [MuNumber 0, MuNumber 3])] None) + itWorksWith $ (Raise None) + itWorksWith $ (Raise (Application (Reference "Exception") [MuString "something"])) + + describe "lambdas" $ do + itWorksWith $ (Lambda [VariablePattern "x"] (MuNumber 1)) + itWorksWith $ (Lambda [] (Reference "foo")) + + itWorksWith $ (MuTuple [MuNumber 1, MuString "something"]) + itWorksWith $ (Yield (MuNumber 1)) + + describe "boolean operations" $ do + let muand x y = (Application (Primitive And) [x, y]) + let muor x y = (Application (Primitive Or) [x, y]) + let muneg x = (Application (Primitive Negation) [x]) + + itWorksWith $ (Reference "a") `muand` (Reference "b") + itWorksWith $ (Reference "a") `muor` (Reference "b") + itWorksWith $ (muneg (Reference "a")) `muor` (Reference "b") + itWorksWith $ muneg ((Reference "a") `muor` (Reference "b")) + itWorksWith $ muneg ((Reference "a") `muand` (Reference "b")) `muor` (Reference "c") + itWorksWith $ (Reference "a") `muand` ((Reference "b") `muor` (Reference "c")) + + describe "unparsePython" $ do + let itWorksWith expr expectedCode = it (show expr) (unparsePython expr `shouldBe` expectedCode) + + describe "literals" $ do + itWorksWith (MuNumber 1) "1" + itWorksWith (MuNumber 1.5) "1.5" + itWorksWith (MuNumber 1) "1" + itWorksWith MuTrue "True" + itWorksWith (MuString "some string") "\"some string\"" + itWorksWith (MuList [MuNumber 1, MuNumber 2, MuNumber 3]) "[1,2,3]" + itWorksWith MuNil "None" + + itWorksWith (Assignment "one" (MuNumber 1)) "one = 1" + itWorksWith ((Reference "x")) "x" + itWorksWith ((Application (Reference "f") [MuNumber 2])) "f(2)" + itWorksWith ((Send (Reference "o") (Reference "f") [(MuNumber 2)])) "o.f(2)" + itWorksWith ((Assignment "x" (Application (Reference "+") [Reference "x",MuNumber 8]))) "x = (x + 8)" + itWorksWith ((Application (Reference "+") [Reference "x",Reference "y"])) "(x + y)" + itWorksWith (Sequence [MuNumber 1, MuNumber 2, MuNumber 3]) "1\n2\n3\n" + itWorksWith ((Application (Primitive Negation) [MuTrue])) "(not True)" + + describe "clases" $ do + itWorksWith (Class "DerivedClassName" Nothing None) "class DerivedClassName:\n\tpass\n" + itWorksWith (Class "DerivedClassName" (Just "BaseClassName") None) "class DerivedClassName(BaseClassName):\n\tpass\n" + + describe "defs" $ do + itWorksWith (SimpleFunction "foo" [] (Return (MuNumber 1))) "def foo():\n\treturn 1\n" + itWorksWith (SimpleProcedure "foo" [VariablePattern "param"] (Print (Reference "param"))) "def foo(param):\n\tprint(param)\n" + + itWorksWith (While MuTrue None) "while True:\n\tpass\n" + itWorksWith (For [Generator (TuplePattern [VariablePattern "x"]) (Application (Reference "range") [MuNumber 0, MuNumber 3])] None) "for x in range(0,3): pass" + itWorksWith (Raise None) "raise" + itWorksWith (Raise (Application (Reference "Exception") [MuString "something"])) "raise Exception(\"something\")" + + describe "lambdas" $ do + itWorksWith (Lambda [VariablePattern "x"] (Reference "x")) "lambda x: x" + itWorksWith (Lambda [VariablePattern "x", VariablePattern "y"] (MuNumber 1)) "lambda x,y: 1" + itWorksWith (Lambda [] MuNil) "lambda : None" + + itWorksWith (MuTuple [MuNumber 1, MuString "something"]) "(1,\"something\")" + itWorksWith (Yield (MuNumber 1)) "yield 1" diff --git a/spec/UnrubySpec.hs b/spec/UnrubySpec.hs new file mode 100644 index 000000000..139d279b2 --- /dev/null +++ b/spec/UnrubySpec.hs @@ -0,0 +1,68 @@ +module UnrubySpec (spec) where + +import Test.Hspec +import Language.Mulang + +import Language.Mulang.Unparsers.Ruby (unparseRuby) + +spec :: Spec +spec = do + describe "unparseRuby" $ do + let itWorksWith expr expectedCode = it (show expr) (unparseRuby expr `shouldBe` expectedCode) + + describe "literals" $ do + itWorksWith (MuNumber 1.5) "1.5" + itWorksWith (MuNumber 1) "1" + itWorksWith MuTrue "true" + itWorksWith MuFalse "false" + itWorksWith (MuString "some string") "\"some string\"" + itWorksWith (MuList [MuNumber 1, MuNumber 2, MuNumber 3]) "[1,2,3]" + itWorksWith MuNil "nil" + + itWorksWith (Assignment "one" (MuNumber 1)) "one = 1" + itWorksWith ((Reference "x")) "x" + itWorksWith ((Application (Reference "f") [MuNumber 2])) "f(2)" + itWorksWith ((Send (Reference "o") (Reference "f") [(MuNumber 2)])) "o.f(2)" + itWorksWith ((Assignment "x" (Application (Reference "+") [Reference "x",MuNumber 8]))) "x = (x + 8)" + itWorksWith ((Application (Reference "+") [Reference "x",Reference "y"])) "(x + y)" + itWorksWith (Sequence [MuNumber 1, MuNumber 2, MuNumber 3]) "1\n2\n3\n" + itWorksWith ((Application (Primitive Negation) [MuTrue])) "(!true)" + + describe "classes" $ do + itWorksWith (Class "DerivedClassName" Nothing None) "class DerivedClassName\nend\n" + itWorksWith (Class "DerivedClassName" (Just "BaseClassName") None) "class DerivedClassName < BaseClassName\nend\n" + + itWorksWith (If MuTrue (MuNumber 1) (MuNumber 3)) "if true\n\t1\nelse\n\t3\nend\n" + + describe "functions and procedures" $ do + itWorksWith (SimpleFunction "foo" [] (Return (MuNumber 1))) "def foo()\n\treturn 1\nend\n" + itWorksWith (SimpleFunction "foo" [VariablePattern "x"] (Return (Reference "x"))) "def foo(x)\n\treturn x\nend\n" + itWorksWith (SimpleProcedure "foo" [] (Print (Reference "param"))) "def foo()\n\tputs(param)\nend\n" + itWorksWith (SimpleProcedure "foo" [VariablePattern "param"] (Print (Reference "param"))) "def foo(param)\n\tputs(param)\nend\n" + + describe "while" $ do + itWorksWith (While MuTrue None) "while true\nend\n" + itWorksWith (While MuTrue (Print (MuString "hi"))) "while true\n\tputs(\"hi\")\nend\n" + + describe "raise" $ do + itWorksWith (Raise None) "raise" + itWorksWith (Raise (MuString "something")) "raise \"something\"" + + describe "lambda" $ do + itWorksWith (Lambda [VariablePattern "x"] (Reference "x")) "lambda { |x| x }" + itWorksWith (Lambda [VariablePattern "x", VariablePattern "y"] (MuNumber 1)) "lambda { |x,y| 1 }" + itWorksWith (Lambda [] MuNil) "lambda { || nil }" + + itWorksWith (Yield (MuNumber 1)) "yield 1" + + describe "boolean operations" $ do + let muand x y = (Application (Primitive And) [x, y]) + let muor x y = (Application (Primitive Or) [x, y]) + let muneg x = (Application (Primitive Negation) [x]) + + itWorksWith ((Reference "a") `muand` (Reference "b")) "(a && b)" + itWorksWith ((Reference "a") `muor` (Reference "b")) "(a || b)" + itWorksWith ((muneg (Reference "a")) `muor` (Reference "b")) "((!a) || b)" + itWorksWith (muneg ((Reference "a") `muor` (Reference "b"))) "(!(a || b))" + itWorksWith (muneg ((Reference "a") `muand` (Reference "b")) `muor` (Reference "c")) "((!(a && b)) || c)" + itWorksWith ((Reference "a") `muand` ((Reference "b") `muor` (Reference "c"))) "(a && (b || c))" diff --git a/src/Language/Mulang/Analyzer.hs b/src/Language/Mulang/Analyzer.hs index afd436c25..800ac3436 100644 --- a/src/Language/Mulang/Analyzer.hs +++ b/src/Language/Mulang/Analyzer.hs @@ -1,90 +1,36 @@ module Language.Mulang.Analyzer ( - noSmells, - allSmells, - noSmellsBut, - allSmellsBut, - - emptyDomainLanguage, - emptyAnalysisSpec, - - emptyAnalysis, - domainLanguageAnalysis, - expectationsAnalysis, - smellsAnalysis, - signaturesAnalysis, - testsAnalysis, - - emptyCompletedAnalysisResult, - analyse, - module Language.Mulang.Analyzer.Analysis) where import Language.Mulang -import Language.Mulang.Analyzer.Analysis -import Language.Mulang.Analyzer.DomainLanguageCompiler (emptyDomainLanguage, compileDomainLanguage) +import Language.Mulang.Analyzer.Analysis hiding (Inspection) +import Language.Mulang.Analyzer.DomainLanguageCompiler (compileDomainLanguage) import Language.Mulang.Analyzer.ExpectationsAnalyzer (analyseExpectations) import Language.Mulang.Analyzer.FragmentParser (parseFragment) import Language.Mulang.Analyzer.SignaturesAnalyzer (analyseSignatures) import Language.Mulang.Analyzer.SmellsAnalyzer (analyseSmells) import Language.Mulang.Analyzer.TestsAnalyzer (analyseTests) +import Language.Mulang.Analyzer.Autocorrector (autocorrect) import Data.Maybe (fromMaybe) --- --- Builder functions --- - -noSmells :: Maybe SmellsSet -noSmells = Just $ NoSmells Nothing - -noSmellsBut :: [Smell] -> Maybe SmellsSet -noSmellsBut = Just . NoSmells . Just - -allSmells :: Maybe SmellsSet -allSmells = Just $ AllSmells Nothing - -allSmellsBut :: [Smell] -> Maybe SmellsSet -allSmellsBut = Just . AllSmells . Just - -emptyAnalysisSpec :: AnalysisSpec -emptyAnalysisSpec = AnalysisSpec Nothing Nothing Nothing Nothing Nothing Nothing - -emptyAnalysis :: Fragment -> Analysis -emptyAnalysis code = Analysis code emptyAnalysisSpec - -domainLanguageAnalysis :: Fragment -> DomainLanguage -> Analysis -domainLanguageAnalysis code domainLanguage = Analysis code (emptyAnalysisSpec { domainLanguage = Just domainLanguage, smellsSet = allSmells }) - -expectationsAnalysis :: Fragment -> [Expectation] -> Analysis -expectationsAnalysis code es = Analysis code (emptyAnalysisSpec { expectations = Just es }) - -smellsAnalysis :: Fragment -> Maybe SmellsSet -> Analysis -smellsAnalysis code set = Analysis code (emptyAnalysisSpec { smellsSet = set }) - -signaturesAnalysis :: Fragment -> SignatureStyle -> Analysis -signaturesAnalysis code style = Analysis code (emptyAnalysisSpec { signatureAnalysisType = Just (StyledSignatures style) }) - -testsAnalysis :: Fragment -> TestAnalysisType -> Analysis -testsAnalysis code testAnalysisType = Analysis code (emptyAnalysisSpec { testAnalysisType = Just testAnalysisType }) - -emptyCompletedAnalysisResult :: AnalysisResult -emptyCompletedAnalysisResult = AnalysisCompleted [] [] [] [] Nothing -- -- Analysis running -- -analyse :: Analysis -> IO AnalysisResult -analyse (Analysis sample spec) = analyseSample (parseFragment sample) +analyse, analyse' :: Analysis -> IO AnalysisResult +analyse = analyse' . autocorrect + +analyse' (Analysis sample spec) = analyseSample . parseFragment $ sample where analyseSample (Right ast) = analyseAst ast spec analyseSample (Left message) = return $ AnalysisFailed message analyseAst :: Expression -> AnalysisSpec -> IO AnalysisResult analyseAst ast spec = do - language <- compileDomainLanguage (domainLanguage spec) + domaingLang <- compileDomainLanguage (domainLanguage spec) testResults <- analyseTests ast (testAnalysisType spec) return $ AnalysisCompleted (analyseExpectations ast (expectations spec)) - (analyseSmells ast language (smellsSet spec)) + (analyseSmells ast domaingLang (smellsSet spec)) (analyseSignatures ast (signatureAnalysisType spec)) testResults (analyzeIntermediateLanguage ast spec) diff --git a/src/Language/Mulang/Analyzer/Analysis.hs b/src/Language/Mulang/Analyzer/Analysis.hs index 87f5e26c9..d435a2ad7 100644 --- a/src/Language/Mulang/Analyzer/Analysis.hs +++ b/src/Language/Mulang/Analyzer/Analysis.hs @@ -1,20 +1,39 @@ {-# LANGUAGE DeriveGeneric #-} module Language.Mulang.Analyzer.Analysis ( + noSmells, + allSmells, + noSmellsBut, + allSmellsBut, + + emptyDomainLanguage, + emptyAnalysisSpec, + + emptyAnalysis, + domainLanguageAnalysis, + expectationsAnalysis, + smellsAnalysis, + signaturesAnalysis, + testsAnalysis, + + emptyCompletedAnalysisResult, + Expectation(..), Analysis(..), AnalysisSpec(..), - SmellsSet(..), - DomainLanguage(..), + AutocorrectionRules, CaseStyle(..), - Smell, - SignatureAnalysisType(..), - TestAnalysisType(..), - InterpreterOptions(..), - SignatureStyle(..), + DomainLanguage(..), Fragment(..), + Inspection, + InterpreterOptions(..), Language(..), + SignatureAnalysisType(..), + SignatureStyle(..), + Smell, + SmellsSet(..), + TestAnalysisType(..), AnalysisResult(..), ExpectationResult(..)) where @@ -24,6 +43,7 @@ import GHC.Generics import Language.Mulang.Ast import Language.Mulang.Builder (NormalizationOptions) import Language.Mulang.Interpreter.Runner (TestResult) +import Data.Map.Strict (Map) --- -- Common structures @@ -31,6 +51,7 @@ import Language.Mulang.Interpreter.Runner (TestResult) type Smell = String type Inspection = String +type AutocorrectionRules = Map Inspection Inspection data Expectation = Expectation { binding :: String, @@ -52,7 +73,9 @@ data AnalysisSpec = AnalysisSpec { signatureAnalysisType :: Maybe SignatureAnalysisType, testAnalysisType :: Maybe TestAnalysisType, domainLanguage :: Maybe DomainLanguage, - includeIntermediateLanguage :: Maybe Bool + includeIntermediateLanguage :: Maybe Bool, + originalLanguage :: Maybe Language, + autocorrectionRules :: Maybe AutocorrectionRules } deriving (Show, Eq, Generic) data DomainLanguage = DomainLanguage { @@ -108,7 +131,10 @@ data Language | Haskell | Python | Python2 - | Python3 deriving (Show, Eq, Generic) + | Python3 + | Ruby + | Php + deriving (Show, Eq, Generic) -- -- Analysis Output structures @@ -128,4 +154,45 @@ data ExpectationResult = ExpectationResult { result :: Bool } deriving (Show, Eq, Generic) +-- +-- Builder functions +-- + +noSmells :: Maybe SmellsSet +noSmells = Just $ NoSmells Nothing + +noSmellsBut :: [Smell] -> Maybe SmellsSet +noSmellsBut = Just . NoSmells . Just + +allSmells :: Maybe SmellsSet +allSmells = Just $ AllSmells Nothing + +allSmellsBut :: [Smell] -> Maybe SmellsSet +allSmellsBut = Just . AllSmells . Just + +emptyAnalysisSpec :: AnalysisSpec +emptyAnalysisSpec = AnalysisSpec Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing + +emptyAnalysis :: Fragment -> Analysis +emptyAnalysis code = Analysis code emptyAnalysisSpec + +domainLanguageAnalysis :: Fragment -> DomainLanguage -> Analysis +domainLanguageAnalysis code domainLanguage = Analysis code (emptyAnalysisSpec { domainLanguage = Just domainLanguage, smellsSet = allSmells }) + +expectationsAnalysis :: Fragment -> [Expectation] -> Analysis +expectationsAnalysis code es = Analysis code (emptyAnalysisSpec { expectations = Just es }) + +smellsAnalysis :: Fragment -> Maybe SmellsSet -> Analysis +smellsAnalysis code set = Analysis code (emptyAnalysisSpec { smellsSet = set }) + +signaturesAnalysis :: Fragment -> SignatureStyle -> Analysis +signaturesAnalysis code style = Analysis code (emptyAnalysisSpec { signatureAnalysisType = Just (StyledSignatures style) }) + +testsAnalysis :: Fragment -> TestAnalysisType -> Analysis +testsAnalysis code testAnalysisType = Analysis code (emptyAnalysisSpec { testAnalysisType = Just testAnalysisType }) + +emptyCompletedAnalysisResult :: AnalysisResult +emptyCompletedAnalysisResult = AnalysisCompleted [] [] [] [] Nothing +emptyDomainLanguage :: DomainLanguage +emptyDomainLanguage = DomainLanguage Nothing Nothing Nothing Nothing diff --git a/src/Language/Mulang/Analyzer/Analysis/Json.hs b/src/Language/Mulang/Analyzer/Analysis/Json.hs index 5217329fc..3710e5aba 100644 --- a/src/Language/Mulang/Analyzer/Analysis/Json.hs +++ b/src/Language/Mulang/Analyzer/Analysis/Json.hs @@ -33,6 +33,7 @@ instance FromJSON Type instance FromJSON Assertion instance FromJSON TestAnalysisType instance FromJSON InterpreterOptions +instance FromJSON Operator instance ToJSON AnalysisResult instance ToJSON ExpectationResult @@ -48,3 +49,4 @@ instance ToJSON Type instance ToJSON Assertion instance ToJSON TestResult instance ToJSON TestStatus +instance ToJSON Operator diff --git a/src/Language/Mulang/Analyzer/Autocorrector.hs b/src/Language/Mulang/Analyzer/Autocorrector.hs new file mode 100644 index 000000000..5ce998bb2 --- /dev/null +++ b/src/Language/Mulang/Analyzer/Autocorrector.hs @@ -0,0 +1,132 @@ +module Language.Mulang.Analyzer.Autocorrector (autocorrect) where + +import Language.Mulang.Analyzer.Analysis +import Language.Mulang.Analyzer.Synthesizer (generateOperatorEncodingRules, generateInspectionEncodingRules) + +import Language.Mulang.Operators (Token, OperatorsTable, buildOperatorsTable) +import Language.Mulang.Operators.Haskell (haskellTokensTable) +import Language.Mulang.Operators.Ruby (rubyTokensTable) +import Language.Mulang.Operators.Java (javaTokensTable) +import Language.Mulang.Operators.Python (pythonTokensTable) + +import Data.Maybe (fromMaybe, fromJust) +import qualified Data.Map.Strict as Map + + +-- | Computes a derived Analysis infering missing values from context +-- +-- It performs the following corrections: +-- +-- 1. fills originalLanguage when it is not present but can be inferred from the code sample +-- 2. fills the autocorrectionRules when they are not present but can be inferred from the originalLanguage +-- 3. aguments the autocorrectionRules with operators-based rules when they can be inferred from the originalLanguage +-- 4. corrects the expectations' inspections using the autocorrectionRules +-- 5. fills the domainLanguage rules when it is not present but can be inferred from the originalLanguage +-- 6. fills the domainLanguage's caseStyle when it is not present but can be inferred from the originalLanguage +autocorrect :: Analysis -> Analysis +autocorrect (Analysis f s@(AnalysisSpec { originalLanguage = Just _ })) = Analysis f (autocorrectSpec s) +autocorrect (Analysis f@(CodeSample { language = l } ) s) = autocorrect (Analysis f s { originalLanguage = Just l }) -- (1) +autocorrect a = a + +autocorrectSpec :: AnalysisSpec -> AnalysisSpec +autocorrectSpec s = runFixes [rulesFix, rulesAgumentationFix, expectationsFix, emptyDomainLanguageFix, domainLanguageCaseStyleFix] + where + runFixes = foldl combine s . map ($ (justOriginalLanguage s)) + combine s f | Just s' <- f s = s' + | otherwise = s + +-- Fixes + +type Fix = Language -> AnalysisSpec -> Maybe AnalysisSpec + +rulesFix :: Fix -- (2) +rulesFix l s = do + AnalysisSpec { autocorrectionRules = Nothing } <- Just s + return s { autocorrectionRules = Just (inferAutocorrectionRules l) } + +rulesAgumentationFix :: Fix -- (3) +rulesAgumentationFix l s = do + AnalysisSpec { autocorrectionRules = Just rules } <- Just s + return s { autocorrectionRules = Just (augmentRules rules (inferOperatorsTable l)) } + where + augmentRules :: AutocorrectionRules -> OperatorsTable -> AutocorrectionRules + augmentRules rules tokens = Map.fromList (Map.toList rules ++ (concatMap generateOperatorEncodingRules . Map.toList) tokens) + +expectationsFix :: Fix -- (4) +expectationsFix _ s = do + AnalysisSpec { expectations = Just es } <- Just s + return s { expectations = Just (map autocorrectExpectation es) } + where + autocorrectExpectation :: Expectation -> Expectation + autocorrectExpectation (Expectation b i) = Expectation b . fromMaybe i . Map.lookup i . justAutocorrectionRules $ s + +emptyDomainLanguageFix :: Fix -- (5) +emptyDomainLanguageFix _ s = do + AnalysisSpec { domainLanguage = Nothing } <- Just s + return s { domainLanguage = Just emptyDomainLanguage } + +domainLanguageCaseStyleFix :: Fix -- (6) +domainLanguageCaseStyleFix l s = do + AnalysisSpec { domainLanguage = Just dl } <- Just s + DomainLanguage { caseStyle = Nothing } <- Just dl + return s { domainLanguage = Just (dl { caseStyle = Just (inferCaseStyle l) }) } + +-- Inferences + +type Inference a = Language -> a + +inferOperatorsTable :: Inference OperatorsTable +inferOperatorsTable = buildOperatorsTable . table + where + table Haskell = haskellTokensTable + table Java = javaTokensTable + table Ruby = rubyTokensTable + table Python = pythonTokensTable + table Python2 = pythonTokensTable + table Python3 = pythonTokensTable + table _ = Map.empty + +inferCaseStyle :: Inference CaseStyle +inferCaseStyle Python = RubyCase +inferCaseStyle Python2 = RubyCase +inferCaseStyle Python3 = RubyCase +inferCaseStyle Ruby = RubyCase +inferCaseStyle _ = CamelCase + +inferAutocorrectionRules :: Inference AutocorrectionRules +inferAutocorrectionRules = buildRules . rules + where + buildRules :: [(Token, Inspection)] -> AutocorrectionRules + buildRules = Map.fromList . concatMap generateInspectionEncodingRules + + rules Haskell = [ + ("type", "DeclaresTypeAlias"), + ("if", "UsesIf") + ] + rules Java = [ + ("if", "UsesIf"), + ("class", "DeclaresClass"), + ("interface", "DeclaresInterface"), + ("for", "UsesForLoop") + ] + rules Ruby = [ + ("if", "UsesIf"), + ("class", "DeclaresClass"), + ("def", "DeclaresComputation"), + ("for", "UsesForeach"), + ("include", "Includes") + ] + rules Python = [ + ("if", "UsesIf"), + ("class", "DeclaresClass"), + ("def", "DeclaresComputation"), + ("for", "UsesForeach") + ] + rules Python2 = rules Python + rules Python3 = rules Python + rules _ = [] + +-- Misc + +justOriginalLanguage = fromJust . originalLanguage +justAutocorrectionRules = fromJust . autocorrectionRules diff --git a/src/Language/Mulang/Analyzer/DomainLanguageCompiler.hs b/src/Language/Mulang/Analyzer/DomainLanguageCompiler.hs index 79a1acb99..e2a58be9a 100644 --- a/src/Language/Mulang/Analyzer/DomainLanguageCompiler.hs +++ b/src/Language/Mulang/Analyzer/DomainLanguageCompiler.hs @@ -1,5 +1,4 @@ module Language.Mulang.Analyzer.DomainLanguageCompiler ( - emptyDomainLanguage, compileDomainLanguage) where import Data.Maybe (fromMaybe) @@ -9,9 +8,6 @@ import Text.Inflections.Tokenizer (camelCase, rubyCase, snakeCase) import Text.Dictionary (fromFile, toDictionary) -emptyDomainLanguage :: DomainLanguage -emptyDomainLanguage = DomainLanguage Nothing Nothing Nothing Nothing - compileDomainLanguage :: Maybe DomainLanguage -> IO DL.DomainLanguage compileDomainLanguage Nothing = compileDomainLanguage (Just emptyDomainLanguage) compileDomainLanguage (Just (DomainLanguage path style size jargon)) = do diff --git a/src/Language/Mulang/Analyzer/ExpectationsAnalyzer.hs b/src/Language/Mulang/Analyzer/ExpectationsAnalyzer.hs index f94c280ef..8a86e1e5d 100644 --- a/src/Language/Mulang/Analyzer/ExpectationsAnalyzer.hs +++ b/src/Language/Mulang/Analyzer/ExpectationsAnalyzer.hs @@ -4,7 +4,7 @@ module Language.Mulang.Analyzer.ExpectationsAnalyzer ( import Data.Maybe (fromMaybe) import Language.Mulang -import Language.Mulang.Analyzer.Analysis (Expectation, ExpectationResult(..)) +import Language.Mulang.Analyzer.Analysis (Language, Expectation, ExpectationResult(..)) import Language.Mulang.Analyzer.ExpectationsCompiler (compileExpectation) analyseExpectations :: Expression -> Maybe [Expectation] -> [ExpectationResult] diff --git a/src/Language/Mulang/Analyzer/ExpectationsCompiler.hs b/src/Language/Mulang/Analyzer/ExpectationsCompiler.hs index f09fc75ef..c5020da25 100644 --- a/src/Language/Mulang/Analyzer/ExpectationsCompiler.hs +++ b/src/Language/Mulang/Analyzer/ExpectationsCompiler.hs @@ -1,8 +1,11 @@ +{-# LANGUAGE ViewPatterns #-} + module Language.Mulang.Analyzer.ExpectationsCompiler( compileExpectation) where import Language.Mulang import Language.Mulang.Analyzer.Analysis (Expectation(..)) +import Language.Mulang.Analyzer.Synthesizer (decodeUsageInspection, decodeDeclarationInspection) import Data.Maybe (fromMaybe) import Data.List.Split (splitOn) @@ -35,12 +38,16 @@ compileNegator ("Not":_) = negative compileNegator _ = id compileBaseInspection :: PredicateModifier -> [String] -> Maybe ContextualizedInspection -compileBaseInspection p ("Not":parts) = compileBaseInspection p parts -compileBaseInspection p [verb] = compileBaseInspection p [verb, "*"] -compileBaseInspection p [verb, object] = compileBaseInspection p [verb, object, "With"] -compileBaseInspection p (verb:"With":args) = compileBaseInspection p (verb:"*":"With":args) -compileBaseInspection p (verb:object:"With":args) = fmap ($ (compileObject p object)) (compileInspectionPrimitive (verb:args)) -compileBaseInspection _ _ = Nothing +compileBaseInspection p ("Not":parts) = compileBaseInspection p parts +compileBaseInspection p parts = compileAffirmativeInspection p parts + +compileAffirmativeInspection :: PredicateModifier -> [String] -> Maybe ContextualizedInspection +compileAffirmativeInspection p [verb] = compileAffirmativeInspection p [verb, "*"] +compileAffirmativeInspection p [verb, object] = compileAffirmativeInspection p [verb, object, "With"] +compileAffirmativeInspection p (verb:"With":args) = compileAffirmativeInspection p (verb:"*":"With":args) +compileAffirmativeInspection p (verb:object:"With":args) = fmap ($ (compileObject p object)) (compileInspectionPrimitive (verb:args)) +compileAffirmativeInspection _ _ = Nothing + compileObject :: PredicateModifier -> String -> IdentifierPredicate compileObject p "*" = p $ anyone @@ -89,12 +96,13 @@ compileInspectionPrimitive = f f ["Instantiates"] = bound instantiates f ["Raises"] = bound raises f ["Rescues"] = bound rescues + f ["Returns", value] = plain (returnsMatching (with value)) f ["TypesAs"] = bound typesAs f ["TypesParameterAs"] = bound typesParameterAs f ["TypesReturnAs"] = bound typesReturnAs - f ["Returns", value] = plain (returnsMatching (with value)) f ["Uses"] = bound uses f ["UsesAnonymousVariable"] = plain usesAnonymousVariable + f ["UsesBooleanLogic"] = plain usesBooleanLogic f ["UsesComposition"] = plain usesComposition f ["UsesComprehension"] = f ["UsesForComprehension"] f ["UsesConditional"] = plain usesConditional @@ -117,6 +125,7 @@ compileInspectionPrimitive = f f ["UsesNot"] = plain usesNot f ["UsesObjectComposition"] = plain usesObjectComposition f ["UsesPatternMatching"] = plain usesPatternMatching + f ["UsesPrint"] = plain usesPrint f ["UsesRepeat"] = plain usesRepeat f ["UsesStaticMethodOverload"] = plain usesStaticMethodOverload f ["UsesStaticPolymorphism"] = contextualized usesStaticPolymorphism' @@ -125,7 +134,13 @@ compileInspectionPrimitive = f f ["UsesType"] = bound usesType f ["UsesWhile"] = plain usesWhile f ["UsesYield"] = plain usesYield - f _ = Nothing + f [primitiveUsage -> Just p] = plain (usesPrimitive p) + f [primitiveDeclaration -> Just p] = plain (declaresPrimitive p) + f _ = Nothing + + primitiveUsage = decodeUsageInspection + primitiveDeclaration = decodeDeclarationInspection + contextualized :: ContextualizedInspection -> Maybe ContextualizedBoundInspection contextualized = Just . contextualizedBind diff --git a/src/Language/Mulang/Analyzer/SmellsAnalyzer.hs b/src/Language/Mulang/Analyzer/SmellsAnalyzer.hs index 4f0769aad..3c444eb3d 100644 --- a/src/Language/Mulang/Analyzer/SmellsAnalyzer.hs +++ b/src/Language/Mulang/Analyzer/SmellsAnalyzer.hs @@ -4,7 +4,7 @@ module Language.Mulang.Analyzer.SmellsAnalyzer ( import Language.Mulang import Language.Mulang.Inspector.Generic.Smell import Language.Mulang.DomainLanguage -import Language.Mulang.Analyzer.Analysis hiding (DomainLanguage) +import Language.Mulang.Analyzer.Analysis hiding (DomainLanguage, Inspection, allSmells) import Data.List ((\\)) import Data.Maybe (fromMaybe) @@ -31,6 +31,7 @@ allSmells = [ "HasMisspelledIdentifiers", "HasRedundantBooleanComparison", "HasRedundantGuards", + "ShouldUseOtherwise", "HasRedundantIf", "HasRedundantLambda", "HasRedundantLocalVariableReturn", @@ -61,6 +62,7 @@ detectionFor "HasMisspelledBindings" = withLanguage hasMisspelledIdent detectionFor "HasMisspelledIdentifiers" = withLanguage hasMisspelledIdentifiers detectionFor "HasRedundantBooleanComparison" = simple hasRedundantBooleanComparison detectionFor "HasRedundantGuards" = simple hasRedundantGuards +detectionFor "ShouldUseOtherwise" = simple shouldUseOtherwise detectionFor "HasRedundantIf" = simple hasRedundantIf detectionFor "HasRedundantLambda" = simple hasRedundantLambda detectionFor "HasRedundantLocalVariableReturn" = simple hasRedundantLocalVariableReturn diff --git a/src/Language/Mulang/Analyzer/Synthesizer.hs b/src/Language/Mulang/Analyzer/Synthesizer.hs new file mode 100644 index 000000000..784a36a5e --- /dev/null +++ b/src/Language/Mulang/Analyzer/Synthesizer.hs @@ -0,0 +1,60 @@ +-- Module por synthesizing inspections +-- from tokens, keywords and operators +module Language.Mulang.Analyzer.Synthesizer ( + encodeUsageInspection, + encodeDeclarationInspection, + decodeUsageInspection, + decodeDeclarationInspection, + generateInspectionEncodingRules, + generateOperatorEncodingRules +) where + +import Language.Mulang.Analyzer.Analysis (Inspection) + +import Language.Mulang.Operators (Token) +import Language.Mulang.Ast (Operator) + +import Control.Monad ((>=>)) + +import Text.Read (readMaybe) +import Data.List (stripPrefix) + +type Encoder a = a -> Inspection +type Decoder a = Inspection -> Maybe a + +type EncodingRuleGenerator a b = (a, b) -> [(Inspection, Inspection)] + +-- converts an operator into an inspection +encodeUsageInspection :: Encoder Operator +encodeUsageInspection = encodeInspection "Uses" + +encodeDeclarationInspection :: Encoder Operator +encodeDeclarationInspection = encodeInspection "Declares" + +encodeInspection :: String -> Encoder Operator +encodeInspection prefix = (prefix ++) . show + +-- extract an operator from an inspection + +decodeUsageInspection :: Decoder Operator +decodeUsageInspection = decodeInspection "Uses" + +decodeDeclarationInspection :: Decoder Operator +decodeDeclarationInspection = decodeInspection "Declares" + +decodeInspection :: String -> Decoder Operator +decodeInspection prefix = stripPrefix prefix >=> readMaybe + +generateInspectionEncodingRules :: EncodingRuleGenerator Token Inspection +generateInspectionEncodingRules = generateEncodingRules id id + +generateOperatorEncodingRules :: EncodingRuleGenerator Token Operator +generateOperatorEncodingRules = generateEncodingRules encodeUsageInspection encodeDeclarationInspection + +generateEncodingRules :: Encoder a -> Encoder a -> EncodingRuleGenerator Token a +generateEncodingRules usageEncoder declarationEncoder (k, v) = concatMap generateEncodingNegationRules baseEncodings + where + baseEncodings = [("Uses:" ++ k, usageEncoder v), ("Declares:" ++ k, declarationEncoder v)] + + generateEncodingNegationRules :: EncodingRuleGenerator Inspection Inspection + generateEncodingNegationRules (k, v) = [(k, v), ("Not:" ++ k, "Not:" ++ v)] diff --git a/src/Language/Mulang/Ast.hs b/src/Language/Mulang/Ast.hs index aa554177b..3e2f3ae23 100644 --- a/src/Language/Mulang/Ast.hs +++ b/src/Language/Mulang/Ast.hs @@ -24,6 +24,7 @@ module Language.Mulang.Ast ( Pattern(..), Identifier, SubroutineBody, + Operator(..), debug, debugType, debugPattern, @@ -32,6 +33,7 @@ module Language.Mulang.Ast ( pattern SimpleProcedure, pattern SimpleMethod, pattern SimpleSend, + pattern PrimitiveSend, pattern SubroutineSignature, pattern VariableSignature, pattern ModuleSignature, @@ -104,8 +106,7 @@ data Expression | Procedure Identifier SubroutineBody -- ^ Imperative programming procedure declaration. It is composed by a name and one or more equations | Method Identifier SubroutineBody - | EqualMethod SubroutineBody - | HashMethod SubroutineBody + | PrimitiveMethod Operator SubroutineBody | Variable Identifier Expression | Assignment Identifier Expression | Attribute Identifier Expression @@ -135,6 +136,9 @@ data Expression -- ^ Logic programming universal cuantification | Reference Identifier -- ^ Generic variable + | Primitive Operator + -- ^ Reference to special, low level, universal operations like logical operaions and math, that may or may not be primitives + -- in the original language | Application Expression [Expression] -- ^ Generic, non-curried application of a function or procedure, composed by the applied element itself, and the application arguments | Send Expression Expression [Expression] @@ -168,8 +172,6 @@ data Expression -- ^ Generic sequence of statements | Other (Maybe Code) (Maybe Expression) -- ^ Unrecognized expression, with optional description and body - | Equal - | NotEqual | Self | None -- ^ Generic value indicating an absent expression, such as when there is no finally in a try or default in a switch or js' undefined @@ -197,6 +199,31 @@ data Expression -- ^ Generic assertion expression such as assert, expect, etc. The first parameter indicates whether the assertion is negated or not deriving (Eq, Show, Read, Generic, Ord) +data Operator + = Equal + -- equal operator + | NotEqual + -- ^ distinct operator + | Negation + -- ^ not operator + | And + -- ^ and operator + | Or + -- ^ or operator + | Hash + -- ^ hashcode operator + | GreatherOrEqualThan + | GreatherThan + | LessOrEqualThan + | LessThan + | Otherwise + -- ^ guard's otherwise operator + | ForwardComposition + -- (f >> g)(x) = (g . f)(x) = g(f(x)) operator + | BackwardComposition + -- (f << g)(x) = (f . g)(x) = f(g(x)) operator + deriving (Eq, Show, Read, Generic, Ord) + data Assertion = Equality Expression Expression -- ^ assert equality between two expressions. e.g.: assert.equals(succ(3), 4) @@ -254,6 +281,7 @@ pattern ModuleSignature name cs = TypeSignature name (ConstrainedType pattern SimpleEquation params body = Equation params (UnguardedBody body) pattern SimpleSend receptor selector args = Send receptor (Reference selector) args +pattern PrimitiveSend receptor selector args = Send receptor (Primitive selector) args pattern SimpleFunction name params body = Function name [SimpleEquation params body] pattern SimpleProcedure name params body = Procedure name [SimpleEquation params body] diff --git a/src/Language/Mulang/Generator.hs b/src/Language/Mulang/Generator.hs index c7bd60f14..769e112bc 100644 --- a/src/Language/Mulang/Generator.hs +++ b/src/Language/Mulang/Generator.hs @@ -80,6 +80,7 @@ expressions expr = expr : concatMap expressions (subExpressions expr) subExpressions (Not e) = [e] subExpressions (Object _ v) = [v] subExpressions (Other _ (Just e)) = [e] + subExpressions (Print v) = [v] subExpressions (Repeat e1 e2) = [e1, e2] subExpressions (Return v) = [v] subExpressions (Sequence es) = es diff --git a/src/Language/Mulang/Inspector/Functional.hs b/src/Language/Mulang/Inspector/Functional.hs index 6878568e8..8090e8846 100644 --- a/src/Language/Mulang/Inspector/Functional.hs +++ b/src/Language/Mulang/Inspector/Functional.hs @@ -20,7 +20,8 @@ usesConditional = alternative usesIf usesGuards -- in its definition usesComposition :: Inspection usesComposition = containsExpression f - where f (Reference ".") = True + where f (Primitive BackwardComposition) = True + f (Primitive ForwardComposition) = True f _ = False -- | Inspection that tells whether an expression uses pattern matching diff --git a/src/Language/Mulang/Inspector/Generic.hs b/src/Language/Mulang/Inspector/Generic.hs index 121e26b89..787255e36 100644 --- a/src/Language/Mulang/Inspector/Generic.hs +++ b/src/Language/Mulang/Inspector/Generic.hs @@ -19,10 +19,13 @@ module Language.Mulang.Inspector.Generic ( returnsMatching, uses, usesAnonymousVariable, + usesBooleanLogic, usesExceptionHandling, usesExceptions, usesFor, usesIf, + usesPrimitive, + usesPrint, usesYield) where import Language.Mulang.Ast @@ -55,6 +58,11 @@ uses :: BoundInspection uses p = containsExpression f where f = any p . referencedIdentifiers +usesPrimitive :: Operator -> Inspection +usesPrimitive operator = containsExpression f + where f (Primitive o) = operator == o + f _ = False + calls :: BoundInspection calls = unmatching callsMatching @@ -85,6 +93,11 @@ usesYield = containsExpression f where f (Yield _) = True f _ = False +usesPrint :: Inspection +usesPrint = containsExpression f + where f (Print _) = True + f _ = False + usesFor :: Inspection usesFor = containsExpression f where f (For _ _) = True @@ -143,6 +156,13 @@ declaresComputationWithArity' arityPredicate = containsBoundDeclaration f argsHaveArity = arityPredicate.length +usesBooleanLogic :: Inspection +usesBooleanLogic = containsExpression f + where f (Primitive Negation) = True + f (Primitive And) = True + f (Primitive Or) = True + f _ = False + raises :: BoundInspection raises predicate = containsExpression f where f (Raise (New (Reference n) _)) = predicate n diff --git a/src/Language/Mulang/Inspector/Generic/Duplication.hs b/src/Language/Mulang/Inspector/Generic/Duplication.hs index 5a106523e..65d5ee297 100644 --- a/src/Language/Mulang/Inspector/Generic/Duplication.hs +++ b/src/Language/Mulang/Inspector/Generic/Duplication.hs @@ -18,7 +18,7 @@ isLightweight (Reference _) = True isLightweight Self = True isLightweight None = True isLightweight MuNil = True -isLightweight Equal = True +isLightweight (Primitive _) = True isLightweight (Application _ es) = not $ any isApplication es isLightweight (Return e) = isLightweight e isLightweight (Assignment _ e) = isLightweight e diff --git a/src/Language/Mulang/Inspector/Generic/Smell.hs b/src/Language/Mulang/Inspector/Generic/Smell.hs index 62a20a770..0ad6c5495 100644 --- a/src/Language/Mulang/Inspector/Generic/Smell.hs +++ b/src/Language/Mulang/Inspector/Generic/Smell.hs @@ -1,7 +1,10 @@ +{-# LANGUAGE ViewPatterns #-} + module Language.Mulang.Inspector.Generic.Smell ( hasRedundantBooleanComparison, hasRedundantIf, hasRedundantGuards, + shouldUseOtherwise, hasRedundantLambda, hasRedundantParameter, hasRedundantLocalVariableReturn, @@ -22,7 +25,7 @@ import Language.Mulang.Ast import Language.Mulang.Generator (identifierReferences) import Language.Mulang.Inspector.Primitive --- | Inspection that tells whether an identifier has expressions like 'x == True' +-- | Inspection that tells whether an expression has expressions like 'x == True' hasRedundantBooleanComparison :: Inspection hasRedundantBooleanComparison = compares isBooleanLiteral @@ -44,16 +47,16 @@ isLongCode = containsExpression f compares :: (Expression -> Bool) -> Inspection compares f = containsExpression (any f.comparisonOperands) -comparisonOperands (Call Equal [a1, a2]) = [a1, a2] -comparisonOperands (Call NotEqual [a1, a2]) = [a1, a2] -comparisonOperands _ = [] +comparisonOperands (Call (Primitive Equal) [a1, a2]) = [a1, a2] +comparisonOperands (Call (Primitive NotEqual) [a1, a2]) = [a1, a2] +comparisonOperands _ = [] returnsNil :: Inspection returnsNil = containsExpression f where f (Return MuNil) = True f _ = False --- | Inspection that tells whether an identifier has an if expression where both branches return +-- | Inspection that tells whether an expression has an if expression where both branches return -- boolean literals hasRedundantIf :: Inspection hasRedundantIf = containsExpression f @@ -64,24 +67,28 @@ hasRedundantIf = containsExpression f f (If _ x y) = all isBooleanLiteral [x, y] f _ = False --- | Inspection that tells whether an identifier has guards where both branches return +-- | Inspection that tells whether an expression has guards where both branches return -- boolean literals hasRedundantGuards :: Inspection hasRedundantGuards = containsBody f -- TODO not true when condition is a pattern where f (GuardedBody [ (_, Return x), - (Reference "otherwise", Return y)]) = all isBooleanLiteral [x, y] + (Primitive Otherwise, Return y)]) = all isBooleanLiteral [x, y] f _ = False +-- | Inspection that tells whether an expression has guards with a hardcoded false instead of an otherwise +shouldUseOtherwise :: Inspection +shouldUseOtherwise = containsBody f + where f (GuardedBody (last -> (MuTrue, _))) = True + f _ = False --- | Inspection that tells whether an identifier has lambda expressions like '\x -> g x' +-- | Inspection that tells whether an expression has lambda expressions like '\x -> g x' hasRedundantLambda :: Inspection hasRedundantLambda = containsExpression f where f (Lambda [VariablePattern (x)] (Return (Call _ [Reference (y)]))) = x == y f _ = False - --- | Inspection that tells whether an identifier has parameters that +-- | Inspection that tells whether an expression has parameters that -- can be avoided using point-free hasRedundantParameter :: Inspection hasRedundantParameter = containsExpression f @@ -114,7 +121,6 @@ discardsExceptions = containsExpression f f (Try _ [(_, Print _)] _) = True f _ = False - doesConsolePrint :: Inspection doesConsolePrint = containsExpression f where f (Print _) = True @@ -136,14 +142,14 @@ hasTooManyMethods = containsExpression f overridesEqualOrHashButNotBoth :: Inspection overridesEqualOrHashButNotBoth = containsExpression f where f (Sequence expressions) = (any isEqual expressions) /= (any isHash expressions) - f (Class _ _ (EqualMethod _)) = True - f (Class _ _ (HashMethod _)) = True + f (Class _ _ (PrimitiveMethod Equal _)) = True + f (Class _ _ (PrimitiveMethod Hash _)) = True f _ = False - isEqual (EqualMethod _) = True + isEqual (PrimitiveMethod Equal _) = True isEqual _ = False - isHash (HashMethod _) = True + isHash (PrimitiveMethod Hash _) = True isHash _ = False hasEmptyIfBranches :: Inspection @@ -153,10 +159,10 @@ hasEmptyIfBranches = containsExpression f hasUnreachableCode :: Inspection hasUnreachableCode = containsExpression f - where f subroutine@(Subroutine _ equations) = any equationMatchesAnyValue . init $ equations - f _ = False - - equationMatchesAnyValue (Equation patterns body) = all patternMatchesAnyValue patterns && bodyMatchesAnyValue body + where f (Subroutine _ equations) = any equationMatchesAnyValue . init $ equations + f _ = False + + equationMatchesAnyValue (Equation patterns body) = all patternMatchesAnyValue patterns && bodyMatchesAnyValue body patternMatchesAnyValue WildcardPattern = True patternMatchesAnyValue (VariablePattern _) = True @@ -166,5 +172,5 @@ hasUnreachableCode = containsExpression f bodyMatchesAnyValue (GuardedBody guards) = any (isTruthy . fst) guards isTruthy (MuBool True) = True - isTruthy (Reference "otherwise") = True + isTruthy (Primitive Otherwise) = True isTruthy _ = False diff --git a/src/Language/Mulang/Inspector/Literal.hs b/src/Language/Mulang/Inspector/Literal.hs index 8c787f0fa..fdf152a8e 100644 --- a/src/Language/Mulang/Inspector/Literal.hs +++ b/src/Language/Mulang/Inspector/Literal.hs @@ -1,7 +1,7 @@ module Language.Mulang.Inspector.Literal (isLiteral) where import Language.Mulang.Ast -import Language.Mulang.Inspector.Primitive (containsExpression, Inspection) +import Language.Mulang.Inspector.Primitive (Inspection) import Text.Read (readMaybe) diff --git a/src/Language/Mulang/Inspector/ObjectOriented.hs b/src/Language/Mulang/Inspector/ObjectOriented.hs index 0cc348521..3263ff6d7 100644 --- a/src/Language/Mulang/Inspector/ObjectOriented.hs +++ b/src/Language/Mulang/Inspector/ObjectOriented.hs @@ -11,7 +11,8 @@ module Language.Mulang.Inspector.ObjectOriented ( declaresInterface, declaresEnumeration, declaresAttribute, - declaresMethod) where + declaresMethod, + declaresPrimitive) where import Language.Mulang.Ast import Language.Mulang.Identifier @@ -77,3 +78,8 @@ declaresMethod = containsBoundDeclaration f where f (Method _ _) = True f _ = False +-- primitive can only be declared as methods +declaresPrimitive :: Operator -> Inspection +declaresPrimitive operator = containsExpression f + where f (PrimitiveMethod o _) = operator == o + f _ = False diff --git a/src/Language/Mulang/Interpreter.hs b/src/Language/Mulang/Interpreter.hs index b854e2523..0c71c9eea 100644 --- a/src/Language/Mulang/Interpreter.hs +++ b/src/Language/Mulang/Interpreter.hs @@ -116,7 +116,7 @@ evalExpr (Mu.Assert negated (Mu.Equality expected actual)) = | muEquals v1 v2 /= negated = return nullRef | otherwise = raiseString $ "Expected " ++ show v1 ++ " but got: " ++ show v2 -evalExpr (Mu.Application (Mu.Reference ">=") expressions) = +evalExpr (Mu.Application (Mu.Primitive Mu.GreatherOrEqualThan) expressions) = evalExpressionsWith expressions f where f [MuNumber n1, MuNumber n2] = createReference $ MuBool $ n1 >= n2 @@ -125,24 +125,24 @@ evalExpr (Mu.Application (Mu.Reference "%") expressions) = where f [MuNumber n1, MuNumber n2] = createReference $ MuNumber $ n1 `mod'` n2 f params = error $ "Bad parameters, expected two numbers but got " ++ show params -evalExpr (Mu.Application (Mu.Reference ">") expressions) = +evalExpr (Mu.Application (Mu.Primitive Mu.GreatherThan) expressions) = evalExpressionsWith expressions f where f [MuNumber n1, MuNumber n2] = createReference $ MuBool $ n1 > n2 f params = error $ "Bad parameters, expected two bools but got " ++ show params -- TODO make this evaluation non strict on both parameters -evalExpr (Mu.Application (Mu.Reference "||") expressions) = +evalExpr (Mu.Application (Mu.Primitive Mu.Or) expressions) = evalExpressionsWith expressions f where f [MuBool b1, MuBool b2] = createReference $ MuBool $ b1 || b2 f params = error $ "Bad parameters, expected two bools but got " ++ show params -- TODO make this evaluation non strict on both parameters -evalExpr (Mu.Application (Mu.Reference "&&") expressions) = +evalExpr (Mu.Application (Mu.Primitive Mu.And) expressions) = evalExpressionsWith expressions f where f [MuBool b1, MuBool b2] = createReference $ MuBool $ b1 && b2 f params = error $ "Bad parameters, expected two bools but got " ++ show params -evalExpr (Mu.Application (Mu.Reference "!") expressions) = +evalExpr (Mu.Application (Mu.Primitive Mu.Negation) expressions) = evalExpressionsWith expressions f where f [MuBool b] = createReference $ MuBool $ not b f params = error $ "Bad parameters, expected one bool but got " ++ show params @@ -151,20 +151,20 @@ evalExpr (Mu.Application (Mu.Reference "*") expressions) = evalExpressionsWith expressions f where f [MuNumber n1, MuNumber n2] = createReference $ MuNumber $ n1 * n2 -evalExpr (Mu.Application Mu.Equal expressions) = do +evalExpr (Mu.Application (Mu.Primitive Mu.Equal) expressions) = do params <- mapM evalExpr expressions let [r1, r2] = params muValuesEqual r1 r2 -evalExpr (Mu.Application Mu.NotEqual expressions) = do - evalExpr $ Mu.Application (Mu.Reference "!") [Mu.Application Mu.Equal expressions] +evalExpr (Mu.Application (Mu.Primitive Mu.NotEqual) expressions) = do + evalExpr $ Mu.Application (Mu.Primitive Mu.Negation) [Mu.Application (Mu.Primitive Mu.Equal) expressions] -evalExpr (Mu.Application (Mu.Reference "<=") expressions) = +evalExpr (Mu.Application (Mu.Primitive Mu.LessOrEqualThan) expressions) = evalExpressionsWith expressions f where f [MuNumber n1, MuNumber n2] = createReference $ MuBool $ n1 <= n2 f params = raiseString $ "Bad parameters, expected two numbers but got " ++ show params -evalExpr (Mu.Application (Mu.Reference "<") expressions) = +evalExpr (Mu.Application (Mu.Primitive Mu.LessThan) expressions) = evalExpressionsWith expressions f where f [MuNumber n1, MuNumber n2] = createReference $ MuBool $ n1 < n2 f params = raiseString $ "Bad parameters, expected two numbers but got " ++ show params @@ -350,9 +350,6 @@ dereference ref = do updateGlobalObjects f context = context { globalObjects = f $ globalObjects context } -updateLocalVariables f context = - context { scopes = f $ scopes context } - incrementRef (Reference n) = Reference $ n + 1 createReference :: Value -> Executable Reference diff --git a/src/Language/Mulang/Operators.hs b/src/Language/Mulang/Operators.hs new file mode 100644 index 000000000..23a483eb4 --- /dev/null +++ b/src/Language/Mulang/Operators.hs @@ -0,0 +1,54 @@ +{-# LANGUAGE TupleSections #-} + +module Language.Mulang.Operators ( + buildTokensTable, + buildOperatorsTable, + parseOperator, + unparseOperator, + Token, + TokensTable, + OperatorsTable) where + +import Language.Mulang.Ast (Operator (..)) + +import Data.Map.Strict (Map) +import qualified Data.Map.Strict as Map +import Data.Tuple (swap) + + +type Token = String + +type TokensTable = Map Operator [Token] +type OperatorsTable = Map Token Operator + +-- C-style tokens +defaultTokensTable :: TokensTable +defaultTokensTable = + Map.fromList [ + (Equal, ["=="]), + (NotEqual, ["!="]), + (Negation, ["!"]), + (And, ["&&"]), + (Or, ["||"]), + (GreatherOrEqualThan, [">="]), + (GreatherThan, [">"]), + (LessOrEqualThan, ["<="]), + (LessThan, ["<"]) + ] + +buildTokensTable :: [(Operator, [Token])] -> TokensTable +buildTokensTable = flip Map.union defaultTokensTable . Map.fromList + +buildOperatorsTable :: TokensTable -> OperatorsTable +buildOperatorsTable = Map.fromList . concatMap (fill . swap) . Map.toList + where + fill (xs, t) = map (,t) xs + +unparseOperator :: Operator -> TokensTable -> Maybe Token +unparseOperator target = fmap head . (Map.lookup target) + +parseOperator :: Token -> TokensTable -> Maybe Operator +parseOperator target = (Map.lookup target) . buildOperatorsTable + + + diff --git a/src/Language/Mulang/Operators/Haskell.hs b/src/Language/Mulang/Operators/Haskell.hs new file mode 100644 index 000000000..7b61bf667 --- /dev/null +++ b/src/Language/Mulang/Operators/Haskell.hs @@ -0,0 +1,14 @@ +{-# LANGUAGE ViewPatterns #-} + +module Language.Mulang.Operators.Haskell (haskellTokensTable) where +import Language.Mulang.Operators (TokensTable, buildTokensTable) +import Language.Mulang.Ast (Operator (..)) + +haskellTokensTable :: TokensTable +haskellTokensTable = + buildTokensTable [ + (NotEqual, ["/="]), + (Negation, ["not"]), + (Otherwise, ["otherwise"]), + (BackwardComposition, ["."]) + ] diff --git a/src/Language/Mulang/Operators/Java.hs b/src/Language/Mulang/Operators/Java.hs new file mode 100644 index 000000000..d83f40064 --- /dev/null +++ b/src/Language/Mulang/Operators/Java.hs @@ -0,0 +1,8 @@ +{-# LANGUAGE ViewPatterns #-} + +module Language.Mulang.Operators.Java (javaTokensTable) where +import Language.Mulang.Operators (TokensTable, buildTokensTable) +import Language.Mulang.Ast (Operator (..)) + +javaTokensTable :: TokensTable +javaTokensTable = buildTokensTable [(Hash, ["hashCode"])] diff --git a/src/Language/Mulang/Operators/Python.hs b/src/Language/Mulang/Operators/Python.hs new file mode 100644 index 000000000..7dab20307 --- /dev/null +++ b/src/Language/Mulang/Operators/Python.hs @@ -0,0 +1,15 @@ +{-# LANGUAGE ViewPatterns #-} + +module Language.Mulang.Operators.Python (pythonTokensTable) where +import Language.Mulang.Operators (TokensTable, buildTokensTable) +import Language.Mulang.Ast (Operator (..)) + +pythonTokensTable :: TokensTable +pythonTokensTable = + buildTokensTable [ + (NotEqual, ["!=", "<>"]), + (Negation, ["not"]), + (And, ["and"]), + (Or, ["or"]), + (Hash, ["hash"]) + ] diff --git a/src/Language/Mulang/Operators/Ruby.hs b/src/Language/Mulang/Operators/Ruby.hs new file mode 100644 index 000000000..7ba98bbe1 --- /dev/null +++ b/src/Language/Mulang/Operators/Ruby.hs @@ -0,0 +1,15 @@ +{-# LANGUAGE ViewPatterns #-} + +module Language.Mulang.Operators.Ruby (rubyTokensTable) where +import Language.Mulang.Operators (TokensTable, buildTokensTable) +import Language.Mulang.Ast (Operator (..)) + +rubyTokensTable :: TokensTable +rubyTokensTable = + buildTokensTable [ + (And, ["&&", "and"]), + (Or, ["||", "or"]), + (Hash, ["hash"]), + (ForwardComposition, [">>"]), + (BackwardComposition, ["<<"]) + ] diff --git a/src/Language/Mulang/Parsers/Haskell.hs b/src/Language/Mulang/Parsers/Haskell.hs index 839169420..bd429a656 100644 --- a/src/Language/Mulang/Parsers/Haskell.hs +++ b/src/Language/Mulang/Parsers/Haskell.hs @@ -1,6 +1,8 @@ module Language.Mulang.Parsers.Haskell (hs, parseHaskell) where import Language.Mulang.Ast +import Language.Mulang.Operators.Haskell (haskellTokensTable) +import Language.Mulang.Operators (parseOperator) import Language.Mulang.Builder (compact, normalizeWith, defaultNormalizationOptions, NormalizationOptions(..), SequenceSortMode(..)) import Language.Mulang.Parsers @@ -106,8 +108,7 @@ mu (HsModule _ _ _ _ decls) = compact (concatMap muDecls decls) muLit (HsDoublePrim v) = MuNumber . fromRational $ v muVar :: String -> Expression - muVar "==" = Equal - muVar "/=" = NotEqual + muVar v | (Just op) <- parseOperator v haskellTokensTable = Primitive op muVar v = Reference v muName :: HsName -> String diff --git a/src/Language/Mulang/Parsers/Java.hs b/src/Language/Mulang/Parsers/Java.hs index ec6725a3f..5be3d6277 100644 --- a/src/Language/Mulang/Parsers/Java.hs +++ b/src/Language/Mulang/Parsers/Java.hs @@ -3,8 +3,8 @@ module Language.Mulang.Parsers.Java (java, parseJava) where -import Language.Mulang.Ast hiding (While, Return, Equal, Lambda, Try, Switch, Assert) -import qualified Language.Mulang.Ast as M (Expression(While, Return, Equal, Lambda, Try, Switch, Assert)) +import Language.Mulang.Ast hiding (Primitive, While, Return, Equal, Lambda, Try, Switch, Assert, Operator(..)) +import qualified Language.Mulang.Ast as M import Language.Mulang.Parsers import Language.Mulang.Builder (compact, compactMap, compactConcatMap, normalize) @@ -55,19 +55,21 @@ muDecl (MemberDecl memberDecl) = muMemberDecl memberDecl muDecl (InitDecl _ block) = [muBlock block] muMemberDecl :: MemberDecl -> [Expression] -muMemberDecl (FieldDecl _ typ varDecls) = concatMap (variableToAttribute.muVarDecl typ) varDecls -muMemberDecl (MethodDecl _ _ typ name params _ (MethodBody Nothing)) = return $ muMethodSignature name params typ +muMemberDecl (FieldDecl _ typ varDecls) = concatMap (variableToAttribute.muVarDecl typ) varDecls +muMemberDecl (MethodDecl _ typeParams typ name params _ (MethodBody Nothing)) = return $ muMethodSignature name params typ typeParams muMemberDecl (MethodDecl (elem Static -> True) _ Nothing (Ident "main") [_] _ body) - = return $ EntryPoint "main" (muMethodBody body) -muMemberDecl (MethodDecl _ _ _ (Ident "equals") params _ body) = return $ EqualMethod [SimpleEquation (map muFormalParam params) (muMethodBody body)] -muMemberDecl (MethodDecl _ _ _ (Ident "hashCode") params _ body) = return $ HashMethod [SimpleEquation (map muFormalParam params) (muMethodBody body)] -muMemberDecl (MethodDecl _ _ returnType name params _ body) = [ muMethodSignature name params returnType, - SimpleMethod (i name) (map muFormalParam params) (muMethodBody body)] -muMemberDecl e@(ConstructorDecl _ _ _ _params _ _constructorBody) = return . debug $ e -muMemberDecl (MemberClassDecl decl) = return $ muClassTypeDecl decl -muMemberDecl (MemberInterfaceDecl decl) = return $ muInterfaceTypeDecl decl - -muMethodSignature name params returnType = SubroutineSignature (i name) (map muFormalParamType params) (muReturnType returnType) [] + = return $ EntryPoint "main" (muMethodBody body) +muMemberDecl (MethodDecl _ _ _ (Ident "equals") params _ body) = return $ PrimitiveMethod M.Equal [SimpleEquation (map muFormalParam params) (muMethodBody body)] +muMemberDecl (MethodDecl _ _ _ (Ident "hashCode") params _ body) = return $ PrimitiveMethod M.Hash [SimpleEquation (map muFormalParam params) (muMethodBody body)] +muMemberDecl (MethodDecl _ typeParams returnType name params _ body) = [ muMethodSignature name params returnType typeParams, + SimpleMethod (i name) (map muFormalParam params) (muMethodBody body)] +muMemberDecl e@(ConstructorDecl _ _ _ _params _ _constructorBody) = return . debug $ e +muMemberDecl (MemberClassDecl decl) = return $ muClassTypeDecl decl +muMemberDecl (MemberInterfaceDecl decl) = return $ muInterfaceTypeDecl decl + +muMethodSignature name params returnType typeParams = SubroutineSignature (i name) (map muFormalParamType params) (muReturnType returnType) (map muTypeParam typeParams) +muTypeParam (TypeParam (Ident i) _) = i + muEnumConstant (EnumConstant name _ _) = i name muFormalParam (FormalParam _ _ _ id) = VariablePattern (v id) @@ -109,7 +111,7 @@ muExp (Cond cond ifTrue ifFalse) = If (muExp cond) (muExp ifTrue) (muExp muExp (ExpName name) = muName name muExp (Assign lhs EqualA exp) = Assignment (muLhs lhs) (muExp exp) muExp (InstanceCreation _ clazz args _) = New (Reference $ r clazz) (map muExp args) -muExp (PreNot exp) = SimpleSend (muExp exp) "!" [] +muExp (PreNot exp) = PrimitiveSend (muExp exp) M.Negation [] muExp (Lambda params exp) = M.Lambda (muLambdaParams params) (muLambdaExp exp) muExp (MethodRef _ message) = M.Lambda [VariablePattern "it"] (SimpleSend (Reference "it") (i message) []) muExp e = debug e @@ -145,14 +147,16 @@ muOp Div = Reference "/" muOp Rem = Reference "rem" muOp Add = Reference "+" muOp Sub = Reference "-" -muOp LThan = Reference "<" -muOp LThanE = Reference "<=" -muOp GThan = Reference ">" -muOp GThanE = Reference ">=" -muOp And = Reference "&&" -muOp Or = Reference "||" -muOp Equal = M.Equal -muOp NotEq = NotEqual +muOp LThan = M.Primitive M.LessThan +muOp LThanE = M.Primitive M.LessOrEqualThan +muOp GThan = M.Primitive M.GreatherThan +muOp GThanE = M.Primitive M.GreatherOrEqualThan +muOp And = M.Primitive M.And +muOp Or = M.Primitive M.Or +muOp CAnd = M.Primitive M.And +muOp COr = M.Primitive M.Or +muOp Equal = M.Primitive M.Equal +muOp NotEq = M.Primitive M.NotEqual muOp e = debug e muVarDecl typ (VarDecl id init) = [ @@ -169,13 +173,15 @@ muMethodInvocation (MethodCall (Name [Ident "System", Ident "out", Ident "print" muMethodInvocation (MethodCall (Name [Ident "System", Ident "out", Ident "printf"]) (expr:_)) = Print (muExp expr) muMethodInvocation (MethodCall (Name [message]) args) = muNormalizeReference $ SimpleSend Self (i message) (map muExp args) -muMethodInvocation (MethodCall (Name receptorAndMessage) args) = SimpleSend (Reference (ns . init $ receptorAndMessage)) (i . last $ receptorAndMessage) (map muExp args) -muMethodInvocation (PrimaryMethodCall receptor _ selector args) = SimpleSend (muExp receptor) (i selector) (map muExp args) +muMethodInvocation (MethodCall (Name receptorAndMessage) args) = muNormalizeReference $ SimpleSend (Reference (ns . init $ receptorAndMessage)) (i . last $ receptorAndMessage) (map muExp args) +muMethodInvocation (PrimaryMethodCall receptor _ selector args) = muNormalizeReference $ SimpleSend (muExp receptor) (i selector) (map muExp args) muMethodInvocation e = debug e muNormalizeReference (SimpleSend Self "assertTrue" [expression]) = M.Assert False $ Truth expression muNormalizeReference (SimpleSend Self "assertFalse" [expression]) = M.Assert True $ Truth expression muNormalizeReference (SimpleSend Self "assertEquals" [expected, actual]) = M.Assert False $ Equality expected actual +muNormalizeReference (SimpleSend one "equals" [other]) = M.PrimitiveSend one M.Equal [other] +muNormalizeReference (SimpleSend one "hashCode" [other]) = M.PrimitiveSend one M.Hash [other] muNormalizeReference e = e muRefType (ClassRefType clazz) = r clazz diff --git a/src/Language/Mulang/Parsers/JavaScript.hs b/src/Language/Mulang/Parsers/JavaScript.hs index eb539328b..ca2e8a7f6 100644 --- a/src/Language/Mulang/Parsers/JavaScript.hs +++ b/src/Language/Mulang/Parsers/JavaScript.hs @@ -137,7 +137,7 @@ muJSExpression (JSMemberNew _ (JSIdentifier _ name) _ args _) = New (Refer muJSExpression (JSMemberSquare receptor _ index _) = Send (muJSExpression receptor) (Reference "[]") [muJSExpression index] muJSExpression (JSNewExpression _ (JSIdentifier _ name)) = New (Reference name) [] muJSExpression (JSObjectLiteral _ propertyList _) = MuObject (compactMap muJSObjectProperty . muJSCommaTrailingList $ propertyList) -muJSExpression (JSUnaryExpression (JSUnaryOpNot _) e) = Application (Reference "!") [muJSExpression e] +muJSExpression (JSUnaryExpression (JSUnaryOpNot _) e) = Application (Primitive Negation) [muJSExpression e] muJSExpression (JSUnaryExpression op (JSIdentifier _ name)) = Assignment name (muJSUnaryOp op name) muJSExpression (JSVarInitExpression (JSIdentifier _ name) initial) = Variable name (muJSVarInitializer initial) muJSExpression e = debug e @@ -146,26 +146,26 @@ removeQuotes = filter (flip notElem quoteMarks) where quoteMarks = "\"'" muJSBinOp:: JSBinOp -> Expression -muJSBinOp (JSBinOpAnd _) = Reference "&&" -muJSBinOp (JSBinOpBitAnd _) = Reference "&" -muJSBinOp (JSBinOpBitOr _) = Reference "|" +muJSBinOp (JSBinOpAnd _) = Primitive And +muJSBinOp (JSBinOpBitAnd _) = Primitive And +muJSBinOp (JSBinOpBitOr _) = Primitive Or muJSBinOp (JSBinOpBitXor _) = Reference "^" muJSBinOp (JSBinOpDivide _) = Reference "/" -muJSBinOp (JSBinOpEq _) = Equal -muJSBinOp (JSBinOpGe _) = Reference ">=" -muJSBinOp (JSBinOpGt _) = Reference ">" +muJSBinOp (JSBinOpEq _) = Primitive Equal +muJSBinOp (JSBinOpGe _) = Primitive GreatherOrEqualThan +muJSBinOp (JSBinOpGt _) = Primitive GreatherThan muJSBinOp (JSBinOpInstanceOf _) = Reference "instanceof" -muJSBinOp (JSBinOpLe _) = Reference "<=" +muJSBinOp (JSBinOpLe _) = Primitive LessOrEqualThan muJSBinOp (JSBinOpLsh _) = Reference "<<" -muJSBinOp (JSBinOpLt _) = Reference "<" +muJSBinOp (JSBinOpLt _) = Primitive LessThan muJSBinOp (JSBinOpMinus _) = Reference "-" muJSBinOp (JSBinOpMod _) = Reference "%" -muJSBinOp (JSBinOpNeq _) = NotEqual -muJSBinOp (JSBinOpOr _) = Reference "||" +muJSBinOp (JSBinOpNeq _) = Primitive NotEqual +muJSBinOp (JSBinOpOr _) = Primitive Or muJSBinOp (JSBinOpPlus _) = Reference "+" muJSBinOp (JSBinOpRsh _) = Reference ">>" -muJSBinOp (JSBinOpStrictEq _) = Equal -muJSBinOp (JSBinOpStrictNeq _) = NotEqual +muJSBinOp (JSBinOpStrictEq _) = Primitive Equal +muJSBinOp (JSBinOpStrictNeq _) = Primitive NotEqual muJSBinOp (JSBinOpTimes _) = Reference "*" diff --git a/src/Language/Mulang/Parsers/Python.hs b/src/Language/Mulang/Parsers/Python.hs index 077fdc970..4fb13476b 100644 --- a/src/Language/Mulang/Parsers/Python.hs +++ b/src/Language/Mulang/Parsers/Python.hs @@ -205,35 +205,35 @@ muArgument e = M.debug e --muYieldArg (YieldFrom expr _)(Expr annot) annot -- ^ Yield from a generator (Version 3 only) muYieldArg (YieldExpr expr) = muExpr expr -muOp (Equality _) = M.Equal -muOp (NotEquals _) = M.NotEqual -muOp op = M.Reference $ muOpReference op - -muOpReference (And _) = "and" -muOpReference (Or _) = "or" -muOpReference (Not _) = "not" -muOpReference (Exponent _) = "**" -muOpReference (LessThan _) = "<" -muOpReference (GreaterThan _) = ">" -muOpReference (GreaterThanEquals _) = ">=" -muOpReference (LessThanEquals _) = "<=" -muOpReference (NotEqualsV2 _) = "<>" -- Version 2 only. -muOpReference (In _) = "in" -muOpReference (Is _) = "is" -muOpReference (IsNot _) = "is not" -muOpReference (NotIn _) = "not in" -muOpReference (BinaryOr _) = "|" -muOpReference (Xor _) = "^" -muOpReference (BinaryAnd _) = "&" -muOpReference (ShiftLeft _) = "<<" -muOpReference (ShiftRight _) = ">>" -muOpReference (Multiply _) = "*" -muOpReference (Plus _) = "+" -muOpReference (Minus _) = "-" -muOpReference (Divide _) = "/" -muOpReference (FloorDivide _) = "//" -muOpReference (Invert _) = "~" -muOpReference (Modulo _) = "%" +muOp (Equality _) = M.Primitive M.Equal +muOp (NotEquals _) = M.Primitive M.NotEqual +muOp op = muOpReference op + +muOpReference (And _) = M.Primitive M.And +muOpReference (Or _) = M.Primitive M.Or +muOpReference (Not _) = M.Primitive M.Negation +muOpReference (Exponent _) = M.Reference "**" +muOpReference (LessThan _) = M.Primitive M.LessThan +muOpReference (GreaterThan _) = M.Primitive M.GreatherThan +muOpReference (GreaterThanEquals _) = M.Primitive M.GreatherOrEqualThan +muOpReference (LessThanEquals _) = M.Primitive M.LessOrEqualThan +muOpReference (NotEqualsV2 _) = M.Primitive M.NotEqual -- Version 2 only. +muOpReference (In _) = M.Reference "in" +muOpReference (Is _) = M.Reference "is" +muOpReference (IsNot _) = M.Reference "is not" +muOpReference (NotIn _) = M.Reference "not in" +muOpReference (BinaryOr _) = M.Primitive M.Or +muOpReference (Xor _) = M.Reference "^" +muOpReference (BinaryAnd _) = M.Primitive M.And +muOpReference (ShiftLeft _) = M.Reference "<<" +muOpReference (ShiftRight _) = M.Reference ">>" +muOpReference (Multiply _) = M.Reference "*" +muOpReference (Plus _) = M.Reference "+" +muOpReference (Minus _) = M.Reference "-" +muOpReference (Divide _) = M.Reference "/" +muOpReference (FloorDivide _) = M.Reference "//" +muOpReference (Invert _) = M.Reference "~" +muOpReference (Modulo _) = M.Reference "%" muAssignOp (PlusAssign _) = "+" muAssignOp (MinusAssign _) = "-" diff --git a/src/Language/Mulang/Unbuilder.hs b/src/Language/Mulang/Unbuilder.hs new file mode 100644 index 000000000..aed35b8ec --- /dev/null +++ b/src/Language/Mulang/Unbuilder.hs @@ -0,0 +1,28 @@ +{-# LANGUAGE ViewPatterns #-} + +module Language.Mulang.Unbuilder ( + tab, + binary, + parenthesize, + binaryOperator, + number) where + +import Language.Mulang.Operators (unparseOperator, Token, TokensTable) +import Language.Mulang.Ast (Operator) +import Data.Maybe (fromJust) + +tab :: String -> String +tab = unlines . map ("\t"++) . lines + +binary :: String -> String -> String -> String +binary op arg1 arg2 = parenthesize . unwords $ [arg1, op, arg2] + +parenthesize :: String -> String +parenthesize = ("("++) . (++")") + +number :: Double -> String +number (properFraction -> (i, 0)) = show i +number n = show n + +binaryOperator :: Operator -> String -> String -> TokensTable -> String +binaryOperator op arg1 arg2 tokensTable = binary (fromJust $ unparseOperator op tokensTable) arg1 arg2 diff --git a/src/Language/Mulang/Unparsers.hs b/src/Language/Mulang/Unparsers.hs new file mode 100644 index 000000000..e1259b11e --- /dev/null +++ b/src/Language/Mulang/Unparsers.hs @@ -0,0 +1,5 @@ +module Language.Mulang.Unparsers(Unparser) where + +import Language.Mulang.Ast (Expression) + +type Unparser = Expression -> String diff --git a/src/Language/Mulang/Unparsers/Java.hs b/src/Language/Mulang/Unparsers/Java.hs new file mode 100644 index 000000000..3115c179a --- /dev/null +++ b/src/Language/Mulang/Unparsers/Java.hs @@ -0,0 +1,83 @@ +{-# LANGUAGE ViewPatterns #-} + +module Language.Mulang.Unparsers.Java (unparseJava) where +import Language.Mulang.Unparsers (Unparser) +import Language.Mulang.Ast +import Language.Mulang.Unbuilder (tab, parenthesize, number, binary) + +import Data.List (intercalate) +import Data.String (unwords) + +unparseJava :: Unparser +unparseJava = unparse + +unparseClassBody :: Unparser +unparseClassBody (Sequence xs) = unparseClassBodyMembers xs +unparseClassBody other = unparse other + +unparseClassBodyMembers :: [Expression] -> String +unparseClassBodyMembers [] = "" +unparseClassBodyMembers (s@SubroutineSignature {}:m@SimpleMethod {}:xs) = unparseMethod s m ++ unparseClassBodyMembers xs +unparseClassBodyMembers (x:xs) = unparse x ++ unparseClassBodyMembers xs + +unparseMethod :: Expression -> Expression -> String +unparseMethod (SubroutineSignature name args typ gens) (SimpleMethod name2 args2 body) + = unwords ["public", "void", name, "(", unparseParams args args2, ")", "{", unparse body, "}"] + +unparseParams :: [Identifier] -> [Pattern] -> String +unparseParams types names = intercalate "," $ zipWith unparseParam types names + +unparseParam :: Identifier -> Pattern -> [Char] +unparseParam typ (VariablePattern name) = unwords [typ, name] + +unparseStatements :: Expression -> String +unparseStatements (Sequence xs) = unlines (map unparseStatements xs) +unparseStatements c@(Call {}) = unparse c ++ ";" +unparseStatements other = unparse other + +unparse :: Unparser +unparse (Class name Nothing body) = unwords ["public class", name, "{", (tab . unparseClassBody) body, "}"] +unparse (Class name (Just superclass) body) = unwords ["public class", name, "extends", superclass, "{", (tab . unparseClassBody) body, "}"] +unparse (Interface name extends body) = unwords ["public interface", name, unparseExtends extends, "{", (tab . unparse) body, "}"] +unparse MuNil = "null" +unparse None = "" +unparse Self = "this" +unparse s@(Sequence members) = unparseStatements s +unparse (SubroutineSignature name args typ gens) = unwords ["public abstract", unTypeParams gens, typ, name, "(", unparam args, ");"] +unparse (Print exp) = "System.out.println(" ++ unparse exp ++ ");" +unparse (Return exp) = unwords ["return", unparse exp, ";"] +unparse (MuNumber n) = number n +unparse MuTrue = "true" +unparse MuFalse = "false" +unparse (MuString s) = show s +unparse (Reference id) = id +unparse (Assignment id value) = unwords [id, "=", unparse value, ";"] +unparse (Call (Reference "+") [arg1, arg2]) = binary "+" (unparse arg1) (unparse arg2) +unparse (Call (Reference "*") [arg1, arg2]) = binary "*" (unparse arg1) (unparse arg2) +unparse (Call (Reference "/") [arg1, arg2]) = binary "/" (unparse arg1) (unparse arg2) +unparse (Call (Reference "-") [arg1, arg2]) = binary "-" (unparse arg1) (unparse arg2) +unparse (Call (Primitive And) [arg1, arg2]) = binary "&&" (unparse arg1) (unparse arg2) +unparse (Call (Primitive Or) [arg1, arg2]) = binary "||" (unparse arg1) (unparse arg2) +unparse (Call (Primitive Negation) [arg1]) = parenthesize $ "!" ++ unparse arg1 +unparse (Send receptor (Reference id) args) = unparse receptor ++ "."++ id ++"("++ unparseMany args ++")" +unparse (While cond body) = unwords ["while", "(", unparse cond, ")", "{", (tab . unparseStatements) body, "}"] +unparse (Raise arg) = unwords ["throw", unparse arg, ";"] +-- unparse (Lambda params body) = "lambda { |"++unparseParams params++"| "++ unparse body ++" }" +-- unparse (If cond trueBody falseBody) = "if "++ unparse cond ++"\n"++ (tab . unparse) trueBody ++ "else\n" ++ (tab . unparse) falseBody ++ "end\n" +unparse other = error . show $ other + +unparseMany :: [Expression] -> String +unparseMany = intercalate "," . map unparse + +unparseExtends :: [Identifier] -> String +unparseExtends [] = "" +unparseExtends parents = unwords ["extends", intercalate "," parents] + +unTypeParams :: [String] -> String +unTypeParams = unwords . map (("<"++).(++">")) + +unparam :: [Identifier] -> String +unparam = intercalate "," . zipWith buildParam [0..] + where + buildParam :: Int -> Identifier -> String + buildParam index typeParam = typeParam ++ " arg" ++ show index diff --git a/src/Language/Mulang/Unparsers/Python.hs b/src/Language/Mulang/Unparsers/Python.hs new file mode 100644 index 000000000..2f41abc1a --- /dev/null +++ b/src/Language/Mulang/Unparsers/Python.hs @@ -0,0 +1,59 @@ +{-# LANGUAGE ViewPatterns #-} + +module Language.Mulang.Unparsers.Python (unparsePython) where + +import Language.Mulang.Operators.Python (pythonTokensTable) +import Language.Mulang.Unparsers (Unparser) +import Language.Mulang.Ast +import Language.Mulang.Unbuilder (tab, binary, parenthesize, number, binaryOperator) + +import Data.List (intercalate) + +unparsePython :: Unparser +unparsePython = unparse + +unparse MuNil = "None" +unparse (Return body) = "return " ++ unparse body +unparse (Print exp) = "print(" ++ unparse exp ++ ")" +unparse (MuNumber n) = number n +unparse (MuBool b) = show b +unparse (MuString s) = show s +unparse (MuList xs) = "[" ++ unparseMany xs ++ "]" +unparse (Assignment id value) = id ++ " = " ++ unparse value +unparse (Reference id) = id +unparse (Call (Primitive Negation) [arg1]) = parenthesize $ "not " ++ unparse arg1 +unparse (Call (Reference "+") [arg1, arg2]) = binary "+" (unparse arg1) (unparse arg2) +unparse (Call (Reference "*") [arg1, arg2]) = binary "*" (unparse arg1) (unparse arg2) +unparse (Call (Reference "/") [arg1, arg2]) = binary "/" (unparse arg1) (unparse arg2) +unparse (Call (Reference "-") [arg1, arg2]) = binary "-" (unparse arg1) (unparse arg2) +unparse (Call (Primitive op) [arg1, arg2]) = binaryOperator op (unparse arg1) (unparse arg2) pythonTokensTable +unparse (Application (Reference id) args) = id ++ "(" ++ unparseMany args ++ ")" +unparse (Sequence xs) = unlines . map unparse $ xs +unparse (While cond body) = "while "++ unparse cond ++ ":\n" ++ (tab . unparse) body +unparse (For [Generator (TuplePattern [VariablePattern "x"]) generator] None) = "for x in "++ unparse generator ++": pass" +unparse (Raise None) = "raise" +unparse (Raise arg) = "raise " ++ unparse arg +unparse (Lambda params body) = "lambda "++ unparseParams params++ ": " ++ unparse body +unparse (MuTuple args) = "("++ unparseMany args++")" +unparse (Yield value) = "yield " ++ unparse value +unparse (Class id Nothing body) = "class "++ id ++":\n" ++ (tab . unparse) body +unparse (Class id (Just parent) body) = "class "++ id ++"("++parent++"):\n" ++ (tab . unparse) body +unparse None = "pass" +unparse (Send receptor (Reference id) args) = unparse receptor ++ "."++ id ++"("++ unparseMany args ++")" +unparse (If cond trueBody falseBody) = "if "++ unparse cond ++":\n"++ (tab . unparse) trueBody ++ "\nelse:\n" ++ (tab . unparse) falseBody +unparse (SimpleFunction id args body) = unparseDef id args body +unparse (SimpleProcedure id args body) = unparseDef id args body +unparse other = error . show $ other + +unparseDef :: String -> [Pattern] -> Expression -> String +unparseDef id args body = "def "++ id ++"("++ unparseParams args++"):\n" ++ (tab . unparse) body + +unparseParams :: [Pattern] -> String +unparseParams = intercalate "," . map unparseParam + +unparseParam :: Pattern -> String +unparseParam (VariablePattern id) = id +unparseParam other = error . show $ other + +unparseMany :: [Expression] -> String +unparseMany = intercalate "," . map unparse diff --git a/src/Language/Mulang/Unparsers/Ruby.hs b/src/Language/Mulang/Unparsers/Ruby.hs new file mode 100644 index 000000000..431d72f5a --- /dev/null +++ b/src/Language/Mulang/Unparsers/Ruby.hs @@ -0,0 +1,60 @@ +{-# LANGUAGE ViewPatterns #-} + +module Language.Mulang.Unparsers.Ruby (unparseRuby) where + +import Language.Mulang.Operators.Ruby (rubyTokensTable) +import Language.Mulang.Unparsers (Unparser) +import Language.Mulang.Unbuilder (tab, binary, parenthesize, number, binaryOperator) +import Language.Mulang.Ast + +import Data.List (intercalate) + +unparseRuby :: Unparser +unparseRuby = unparse + +unparse :: Unparser +unparse MuNil = "nil" +unparse (Print exp) = "puts(" ++ unparse exp ++ ")" +unparse (Return exp) = "return " ++ unparse exp +unparse (MuNumber n) = number n +unparse MuTrue = "true" +unparse MuFalse = "false" +unparse (MuString s) = show s +unparse (MuList xs) = "[" ++ unparseMany xs ++ "]" +unparse (Assignment id value) = id ++ " = " ++ unparse value +unparse (Reference id) = id +unparse (Call (Primitive Negation) [arg1]) = parenthesize $ "!" ++ unparse arg1 +unparse (Call (Reference "+") [arg1, arg2]) = binary "+" (unparse arg1) (unparse arg2) +unparse (Call (Reference "*") [arg1, arg2]) = binary "*" (unparse arg1) (unparse arg2) +unparse (Call (Reference "/") [arg1, arg2]) = binary "/" (unparse arg1) (unparse arg2) +unparse (Call (Reference "-") [arg1, arg2]) = binary "-" (unparse arg1) (unparse arg2) +unparse (Call (Primitive op) [arg1, arg2]) = binaryOperator op (unparse arg1) (unparse arg2) rubyTokensTable +unparse (Application (Reference id) args) = id ++ "(" ++ unparseMany args ++ ")" +unparse (Sequence xs) = unlines . map unparse $ xs +unparse (While cond body) = "while "++ unparse cond ++ "\n" ++ (tab . unparse) body ++ "end\n" +unparse (Raise None) = "raise" +unparse (Raise arg) = "raise " ++ unparse arg +unparse (Lambda params body) = "lambda { |"++unparseParams params++"| "++ unparse body ++" }" +unparse (MuTuple args) = "("++ unparseMany args++")" +unparse (Yield value) = "yield " ++ unparse value +unparse (Class id Nothing body) = "class "++ id ++"\n" ++ (tab . unparse) body ++ "end\n" +unparse (Class id (Just parent) body) = "class "++ id ++" < "++parent++"\n" ++ (tab . unparse) body ++ "end\n" +unparse None = "" +unparse (Send receptor (Reference id) args) = unparse receptor ++ "."++ id ++"("++ unparseMany args ++")" +unparse (If cond trueBody falseBody) = "if "++ unparse cond ++"\n"++ (tab . unparse) trueBody ++ "else\n" ++ (tab . unparse) falseBody ++ "end\n" +unparse (SimpleFunction id args body) = unparseDef id args body +unparse (SimpleProcedure id args body) = unparseDef id args body +unparse other = error . show $ other + +unparseDef :: String -> [Pattern] -> Expression -> String +unparseDef id args body = "def "++ id ++"("++ unparseParams args++")\n" ++ (tab . unparse) body ++ "end\n" + +unparseParams :: [Pattern] -> String +unparseParams = intercalate "," . map unparseParam + +unparseParam :: Pattern -> String +unparseParam (VariablePattern id) = id +unparseParam other = error . show $ other + +unparseMany :: [Expression] -> String +unparseMany = intercalate "," . map unparse