-
-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Jscompilation #145
base: master
Are you sure you want to change the base?
Jscompilation #145
Changes from all commits
8cb3bd3
3ee2abb
62f4d51
a8a3179
bb0a843
24271b1
b33fcf1
8d2e941
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,4 @@ dist/** | |
cabal.sandbox.config | ||
.cabal-sandbox/** | ||
.stack-work/** | ||
.idea |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
module JSCompilerSpec (spec) where | ||
|
||
import Test.Hspec | ||
import Language.Mulang.Ast | ||
import Language.Mulang.JSCompiler | ||
|
||
spec :: Spec | ||
spec = do | ||
-- It would be much better to test this by running the JS code instead of checking the string output... | ||
-- Also, I don't care about output format right now, so all \n are ignored. | ||
describe "JS Compilation" $ do | ||
|
||
it "MuNumber" $ do | ||
(MuNumber 5) `shouldBeCompiledTo` "new MuNumber(5.0)" | ||
|
||
it "MuNumber" $ do | ||
(MuBool True) `shouldBeCompiledTo` "new MuBool(true)" | ||
|
||
it "MuString" $ do | ||
(MuString "foo") `shouldBeCompiledTo` "new MuString(\"foo\")" | ||
|
||
it "MuSymbol" $ do | ||
(MuSymbol "foo") `shouldBeCompiledTo` "new MuSymbol(\"foo\")" | ||
|
||
it "MuTuple" $ do | ||
(MuTuple [MuNumber 1, MuNumber 2, MuNumber 3]) `shouldBeCompiledTo` "new MuTuple([new MuNumber(1.0), new MuNumber(2.0), new MuNumber(3.0)])" | ||
|
||
it "MuList" $ do | ||
(MuList [MuNumber 1, MuNumber 2, MuNumber 3]) `shouldBeCompiledTo` "new MuList([new MuNumber(1.0), new MuNumber(2.0), new MuNumber(3.0)])" | ||
|
||
it "Enumeration" $ do | ||
(Enumeration "E" ["A", "B", "C"]) `shouldBeCompiledTo` ( | ||
"var E = { A: 0, B: 1, C: 2 }; " ++ | ||
"var A = E['A'];" ++ | ||
"var B = E['B'];" ++ | ||
"var C = E['C'];" | ||
) | ||
|
||
it "EntryPoint" $ do | ||
(EntryPoint "foo" (MuBool True)) `shouldBeCompiledTo` "function foo() { return new MuBool(true) }" | ||
|
||
it "TypeSignature" $ do | ||
(TypeSignature "foo" [] "bar") `shouldBeCompiledTo` "" | ||
|
||
it "TypeAlias" $ do | ||
(TypeAlias "foo") `shouldBeCompiledTo` "" | ||
|
||
it "Record" $ do | ||
(Record "foo") `shouldBeCompiledTo` "" | ||
|
||
it "Function" $ do | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a side note: I prefer to declare those groups of tests as a |
||
(Function "f" []) `shouldBeCompiledTo` "function f() {try { throw new MuPatternMatchError() }catch($error) { if($error.constructor === MuReturn) { return $error.value } else { throw $error } } }" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd use |
||
(Function "f" [Equation [] (UnguardedBody (Return (MuNumber 5)))]) `shouldBeCompiledTo` ( | ||
"function f() {try { " ++ | ||
"if(arguments.length === 0){ return function(){ throw new MuReturn(new MuNumber(5.0)) }() } " ++ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice! We definitely must check the arguments count. |
||
"throw new MuPatternMatchError() " ++ | ||
"}catch($error) { if($error.constructor === MuReturn) { return $error.value } else { throw $error } } }" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why you are dealing with normal application scenario using exceptions for control flow? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sight... Here is the issue: Since some constructions that many languages use as expressions are not expressions in JS (if, try/catch, etc.) I need to wrap them in a function call to make them return values. Problem is, that stops the return statements from leaving the current context. So, for example, if you had some java: int f (){ The first return would exit the if function context but not the f function itself as expected. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it |
||
) | ||
(Function "f" [Equation [VariablePattern "x"] (UnguardedBody $ MuNumber 5)]) `shouldBeCompiledTo` ( | ||
"function f() {try { " ++ | ||
"if(arguments.length === 1 && function(){ return true }(arguments[0])){ var x = arguments[0]; return new MuNumber(5.0) } " ++ | ||
"throw new MuPatternMatchError() " ++ | ||
"}catch($error) { if($error.constructor === MuReturn) { return $error.value } else { throw $error } } }" | ||
) | ||
(Function "f" [Equation [VariablePattern "x", VariablePattern "y"] (UnguardedBody $ MuNumber 5)]) `shouldBeCompiledTo` ( | ||
"function f() {try { " ++ | ||
"if(arguments.length === 2 && function(){ return true }(arguments[0]) && function(){ return true }(arguments[1])){ var x = arguments[0];var y = arguments[1]; return new MuNumber(5.0) } " ++ | ||
"throw new MuPatternMatchError() " ++ | ||
"}catch($error) { if($error.constructor === MuReturn) { return $error.value } else { throw $error } } }" | ||
) | ||
(Function "f" [Equation [] (UnguardedBody (Sequence [If (Reference "c") (Return (Reference "x")) (Sequence []), Reference "y"]))]) `shouldBeCompiledTo` ( | ||
"function f() {try { " ++ | ||
"if(arguments.length === 0){ " ++ | ||
"return function(){ " ++ | ||
"function(){ if(c) { return function(){ throw new MuReturn(x) }() } else { return undefined } }(); " ++ | ||
"return y " ++ | ||
"}() " ++ | ||
"} " ++ | ||
"throw new MuPatternMatchError() " ++ | ||
"}catch($error) { if($error.constructor === MuReturn) { return $error.value } else { throw $error } } }" | ||
) | ||
pending --TODO: Other cases | ||
|
||
it "Procedures" $ do pending | ||
|
||
it "Variable" $ do | ||
(Variable "x" $ Reference "y") `shouldBeCompiledTo` "var x = y" | ||
|
||
it "Assignment" $ do | ||
(Assignment "x" $ Reference "y") `shouldBeCompiledTo` "x = y" | ||
|
||
it "Reference" $ do | ||
(Reference "x") `shouldBeCompiledTo` "x" | ||
|
||
it "Application" $ do | ||
(Application (Reference "f") []) `shouldBeCompiledTo` "f()" | ||
(Application (Reference "f") [Reference "x", Reference "y"]) `shouldBeCompiledTo` "f(x, y)" | ||
(Application (Application (Reference "f") [Reference "x"]) [Application (Reference "g") [Reference "y"]]) `shouldBeCompiledTo` "f(x)(g(y))" | ||
|
||
it "Send" $ do | ||
(Send (Reference "o") (Reference "m") []) `shouldBeCompiledTo` "o['m']()" | ||
(Send (Reference "o") (Reference "m") [Reference "x", Reference "y"]) `shouldBeCompiledTo` "o['m'](x, y)" | ||
(Send (Send (Reference "o") (Reference "m") [Reference "x"]) (Reference "n") [Reference "y"]) `shouldBeCompiledTo` "o['m'](x)['n'](y)" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Side note: while I was reading the PR, I was wondering.... #149 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hehe, I guess that depends on what crazy cases you are planning on parsing :p I guess if you had JS that does Still, I think I preffer the identifier. |
||
|
||
it "New" $ do | ||
(New "C" []) `shouldBeCompiledTo` "new C()" | ||
(New "C" [Reference "x", Reference "y"]) `shouldBeCompiledTo` "new C(x, y)" | ||
|
||
it "Raise" $ do | ||
(Raise $ New "E" []) `shouldBeCompiledTo` "function(){ throw new E() }()" | ||
|
||
it "Print" $ do | ||
(Print $ Reference "x") `shouldBeCompiledTo` "console.log(x)" | ||
|
||
it "MuNull" $ do | ||
MuNull `shouldBeCompiledTo` "new MuNull()" | ||
|
||
it "Sequence" $ do pending | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that a sequence should be just the join of the translated expressions using a Sequence [Send (Reference "x") (Reference "foo") [], Send (Reference "x") (Reference "bar") []] should map to: x.foo() ; x.bar() There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sequences are being compiled, the tests are the ones pending. I did pretty much that, but prepended a return to the last element so it would leave the context with that value, because I wasn't sure if the parsers where inserting Return statements on languages with default return. |
||
|
||
it "If" $ do | ||
(If (Reference "c") (Reference "x") (Reference "y")) `shouldBeCompiledTo` "function(){ if(c) { return x } else { return y } }()" | ||
|
||
it "While" $ do | ||
(While (Reference "c") (Reference "x")) `shouldBeCompiledTo` "function(){ while(c) { x } }()" | ||
|
||
it "Repeat" $ do | ||
(Repeat (Reference "c") (Reference "x")) `shouldBeCompiledTo` "function(){ for(var $$i=0; $$i < c; $$i++) { x } }()" | ||
|
||
it "Class" $ do | ||
(Class "C" Nothing MuNull) `shouldBeCompiledTo` ( | ||
"function C() { " ++ | ||
"this.constructor.prototype = Object.create(MuObject.prototype); MuObject.call(this); " ++ | ||
"}" | ||
) | ||
|
||
(Class "C" (Just "S") MuNull) `shouldBeCompiledTo` ( | ||
"function C() { " ++ | ||
"this.constructor.prototype = Object.create(S.prototype); S.call(this); " ++ | ||
"}" | ||
) | ||
|
||
(Class "C" Nothing $ Sequence [Method "m" [Equation [] (UnguardedBody $ Reference "x")]]) `shouldBeCompiledTo` ( | ||
"function C() { " ++ | ||
"this.constructor.prototype = Object.create(MuObject.prototype); MuObject.call(this); " ++ | ||
"this.constructor.prototype['m'] = function () { " ++ | ||
"try { if(arguments.length === 0){ return x } throw new MuPatternMatchError() } " ++ | ||
"catch($error) { if($error.constructor === MuReturn) { return $error.value } else { throw $error } } " ++ | ||
"}" ++ | ||
"}" | ||
) | ||
|
||
(Class "C" Nothing $ Sequence [Attribute "f" $ Reference "x"]) `shouldBeCompiledTo` ( | ||
"function C() { " ++ | ||
"this.constructor.prototype = Object.create(MuObject.prototype); MuObject.call(this); " ++ | ||
"this['f'] = x" ++ | ||
"}" | ||
) | ||
|
||
(Class "C" Nothing $ Sequence [Include "M"]) `shouldBeCompiledTo` ( | ||
"function C() { " ++ | ||
"this.constructor.prototype = Object.create(MuObject.prototype); MuObject.call(this); " ++ | ||
"this.constructor.prototype = Object.assign(this.constructor.prototype, Object.create(M.prototype)); M.call(this)" ++ | ||
"}" | ||
) | ||
|
||
it "Object" $ do | ||
(Object "o" MuNull) `shouldBeCompiledTo` ( | ||
"var o = new function () { " ++ | ||
"this.constructor.prototype = Object.create(MuObject.prototype); MuObject.call(this); " ++ | ||
"}()" | ||
) | ||
|
||
shouldBeCompiledTo expression expected = (fmap (filter (/='\n')) . toJS) expression `shouldBe` Just expected |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since it is an entry point, and JS does not actually have a
main
program, we should also execute this code, or register into theMRE
so it is called after all the code is loaded.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, never mind, we should definitely no execute the entry points, since it could have many and it is resposibility of the code running system to call it 😅