public
Description: Haskell implemented JavaScript interpreter
Homepage:
Clone URL: git://github.com/motemen/jusk.git
jusk / Eval.hs
100644 475 lines (395 sloc) 18.312 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
{-# OPTIONS_GHC -fglasgow-exts #-}
{-
Eval.hs
値の評価
http://www2u.biglobe.ne.jp/~oz-07ams/prog/ecma262r3/10_Execution_Contexts.html
-}
 
module Eval (module Eval, module JSType) where
import IO
import List
import Maybe
import qualified Data.Map as Map
import Control.Monad.Cont hiding (Cont)
 
import DataTypes
import {-# SOURCE #-} Operator
import {-# SOURCE #-} JSType
import {-# SOURCE #-} Internal
import Context
 
ePutStrLn :: String -> IO ()
ePutStrLn = hPutStrLn stderr
 
evalProgram :: JavaScriptProgram -> Evaluate Value
evalProgram program =
    do env <- getEnv
       if null program || ParseOnly `elem` envFlags env
          then return Void
          else if (Debug `elem` envFlags env)
                  then liftM last $ mapM (\e -> do { liftIO $ ePutStrLn $ "parsed: " ++ inspect e; eval e }) program
                  else liftM last $ mapM eval program
 
instance Eval Statement where
    eval (STVarDef bindings) =
        do mapM bindVariable bindings
           return Void
        where bindVariable (name, Nothing) =
                  defineVar name Undefined
              bindVariable (name, Just expr) =
                  eval expr >>= getValue >>= defineVar name
 
    eval (STFuncDef { funcDefName = name, funcDefFunc = func }) =
        do frames <- liftM envFrames getEnv
           protoObj <- makeNewObject
           defineVar name $ nullObject {
                   objPropMap = mkPropMap [("prototype", protoObj, [])],
                   objObject = func { funcScope = frames }
               }
 
    eval STEmpty =
        return Void
    
    eval (STExpression expr) =
        getValue =<< eval expr
    
    eval (STIf condition thenStatement maybeElseStatement) =
        ifM (toBoolean =<< getValue =<< eval condition)
            (eval thenStatement)
            (maybe (return Void) eval maybeElseStatement)
    
    eval (STBlock []) =
        return Void
    
    eval (STBlock statements) =
        liftM last $ mapM eval statements
    
    eval (STDoWhile condition block) =
        withCC (CBreak Nothing)
               (evalDoWhileBlock Void)
        where evalDoWhileBlock lastValue =
                  do value <- withCC (CContinue Nothing) (eval block)
                     ifM (toBoolean =<< eval condition)
                         (evalDoWhileBlock value)
                         (return lastValue)
 
    eval (STWhile condition block) =
        withCC (CBreak Nothing)
               (evalWhileBlock Void)
        where evalWhileBlock lastValue =
                  ifM (toBoolean =<< eval condition)
                      (do value <- withCC (CContinue Nothing) (eval block)
                          evalWhileBlock value)
                      (return lastValue)
              
    eval (STFor initialize condition update block) =
        withCC (CBreak Nothing)
               (do eval initialize
                   value <- evalForBlock Void
                   return value)
        where evalForBlock lastValue =
                  ifM (toBoolean =<< eval condition)
                      (do value <- withCC (CContinue Nothing) (eval block)
                          eval update
                          evalForBlock value)
                      (return lastValue)
 
    eval (STForIn init object block) =
        do name <- case init of
                        STVarDef [(name, expr)] ->
                            do defineVar name =<< maybe (return Undefined) eval expr
                               return name
                        STExpression (Identifier name) ->
                            do defineVar name Undefined -- 既に定義されていてもどのみち上書きする
                               return name
                        _ -> throw "ReferenceError" "invalid left-hand side of for-in loop" >> return ""
           withCC (CBreak Nothing)
                  (do object <- readRef =<< getValue =<< eval object
                      evalForInForObj name object)
        where evalForInForObj varName Object { objPropMap = props, objPrototype = proto } =
                  do forM (propToEnum props)
                          (evalForInBlock varName)
                     readRef proto >>= evalForInForObj varName
              evalForInForObj _ _ =
                  return Void
              evalForInBlock varName n =
                  do setVar varName $ String n
                     withCC (CContinue Nothing) (eval block)
              propToEnum = Map.keys . Map.filter (notElem DontEnum . propAttr)
 
    eval (STContinue label) =
        returnCont (CContinue label) Void
 
    eval (STBreak label) =
        returnCont (CBreak label) Void
 
    eval (STReturn (Just (Operator "()" (callee:args)))) =
        do callee <- eval callee
           mapM evalValue args >>= jumpToFunc callee
 
    eval (STReturn expr) =
        do value <- getValue =<< maybe (return Undefined) eval expr
           returnCont CReturn value
 
    eval (STWith expr st) =
        do object <- getValue =<< eval expr
           pushWithFrame object
           value <- eval st
           popFrame
           return value
 
    eval (STLabelled label st) =
        do callCC
           $ \cc -> do pushCont cc (CBreak $ Just label)
                       pushCont cc (CContinue $ Just label)
                       eval st
 
    eval (STSwitch expr statements) =
        do value <- getValue =<< eval expr
           withCC (CBreak Nothing) (evalSwitchStatement value statements Nothing Void)
        where evalSwitchStatement :: Value -> [(Maybe Expression, Statement)] -> Maybe Statement -> Value -> Evaluate Value
              evalSwitchStatement _ [] (Just st) _ =
                  eval st
                  
              evalSwitchStatement _ [] Nothing lastValue =
                  return lastValue
 
              evalSwitchStatement value ((Nothing, st):cs) Nothing lastValue =
                  evalSwitchStatement value cs (Just st) lastValue
 
              evalSwitchStatement value clauses@((Just e, _):cs) defaultClause lastValue =
                  do e <- getValue =<< eval e
                     ifM (toBoolean =<< comparisonOp (==) value e)
                         (liftM last $ mapM ((getValue =<<) . eval . snd) clauses)
                         (evalSwitchStatement value cs defaultClause lastValue)
 
    eval (STThrow expr) =
        do value <- getValue =<< eval expr
           returnCont CThrow value
 
    eval (STTry tryStatement catchClause finallyClause) =
        do e <- withCC CThrow (eval tryStatement >> return Void)
           v1 <- case catchClause of
                      Just (p, st) | not (isVoid e)
                           -> do binding <- bindParamArgs [p] [e]
                                 pushScope binding
                                 value <- eval st
                                 popScope
                                 return value
                      _ -> return Void
           v2 <- case finallyClause of
                      Just st -> eval st
                      Nothing -> return Void
           return $ v1 ||| v2
        where x ||| Void = x
              Void ||| y = y
 
evalValue :: (Eval a) => a -> Evaluate Value
evalValue x = getValue =<< eval x
 
-- Reference (Ref baseRef, p) の形になるまで評価する
instance Eval Expression where
    eval expr =
        do unless (isLiteral expr)
                  (debug $ "eval: " ++ show expr)
           eval' expr
        where
            eval' (Identifier name) =
                do frame <- liftM2 fromMaybe (liftM frObject currentFrame) (getVarFrameObject name)
                   return $ Reference frame name
            
            eval' (Keyword "this") =
                getThis
            
            eval' (Operator "[]" [base, p]) =
                do objRef <- getValue =<< eval base
                   prop <- toString =<< getValue =<< eval p
                   return $ Reference objRef prop
            
            eval' (Operator "()" (callee:args)) =
                do callee <- eval callee
                   mapM evalValue args >>= call callee
            
            eval' (Operator "new" (klass:args)) =
                do klass <- getValue =<< eval klass
                   args <- mapM evalValue args
                   construct klass args
            
            eval' (Operator "++" [x]) =
                eval $ Let x (Operator "+" [x, Literal $ Number $ Integer 1])
 
            eval' (Operator "_++" [x]) =
                do value <- readRef =<< evalValue x
                   eval $ Operator "++" [x]
                   return value
 
            eval' (Operator "--" [x]) =
                eval $ Let x (Operator "-" [x, Literal $ Number $ Integer 1])
 
            eval' (Operator "_--" [x]) =
                do value <- readRef =<< evalValue x
                   eval $ Operator "--" [x]
                   return value
 
            eval' (Operator "&&" [a, b]) =
                ifM (toBoolean =<< eval a)
                    (eval b)
                    (eval a)
 
            eval' (Operator "||" [a, b]) =
                ifM (toBoolean =<< eval a)
                    (eval a)
                    (eval b)
 
            eval' (Operator "?:" [c, t, e]) =
                ifM (toBoolean =<< eval c)
                    (eval t)
                    (eval e)
 
            eval' (Operator op exprs) =
                if elem op ["*=", "/=", "%=", "+=", "-=", "<<=", ">>=", ">>>=", "&=", "^=", "|="]
                   then eval $ Let (head exprs) (Operator (init op) (tail exprs))
                   else do args <- mapM eval exprs
                           evalOperator op args
            
            eval' (ArrayLiteral exprs) =
                do items <- mapM evalValue exprs
                   makeNewArray items
            
            eval' (ObjectLiteral pairs) =
                do object <- makeNewObject
                   forM pairs $ \(n,e) -> do
                        n <- toString =<< eval n
                        p <- getValue =<< eval e
                        object ! n <~ p
                   return object
 
            eval' (RegExpLiteral pattern flags) =
                new "RegExp" [String pattern, String flags]
            
            eval' (List []) =
                return Void
            
            eval' (List exprs) =
                liftM last $ mapM evalValue exprs
            
            eval' (Let left right) =
                do left <- eval left
                   value <- getValue =<< eval right
                   putValue left value
                   return value
            
            eval' (Literal num@(Number _)) =
                return $ tidyNumber num
            
            eval' (Literal obj@Object { objObject = func@Function { } }) =
                do frames <- liftM envFrames getEnv
                   protoObj <- makeNewObject
                   return $ obj {
                           objPropMap = mkPropMap [("prototype", protoObj, [])],
                           objObject = func { funcScope = frames }
                        }
 
            eval' (Literal value) =
                return value
            
            eval' expr =
                return $ String $ show expr
-- }}}
 
-- Operator {{{
evalOperator :: String -> [Value] -> Evaluate Value
evalOperator op [x] =
    maybe (throw "NotImplemented" $ "operator " ++ op)
          (\op -> liftM tidyNumber $ opUnaryFunc op x)
          (find (isUnaryOp op) operatorsTable)
    where isUnaryOp op (Unary op' _) = op == op'
          isUnaryOp _ _ = False
 
evalOperator op [x,y] =
    maybe (throw "NotImplemented" $ "operator " ++ op)
          (\op -> liftM tidyNumber $ opBinaryFunc op x y)
          (find (isBinaryOp op) operatorsTable)
    where isBinaryOp op (Binary op' _) = op == op'
          isBinaryOp _ _ = False
 
evalOperator _ _ =
    return Undefined
-- }}}
 
-- [[Call]]
call :: Value -> [Value] -> Evaluate Value
call Reference { refBase = base, refName = name } args =
    callMethod base name args
 
call function args =
    do global <- getGlobal
       debug $ "call: " ++ show function ++ " " ++ show args
       callWithThis global function args
 
callWithThis :: Value -> Value -> [Value] -> Evaluate Value
callWithThis this callee@Object { objName = name,
                                  objObject = Function {
                                      funcParam = param,
                                      funcBody = body,
                                      funcScope = scope
                                  }
                                } args =
    do debug $ "callWithThis: " ++ show this ++ " " ++ name ++ " " ++ show args
       arguments <- makeArguments
       binding <- makeRef =<< bindParamArgs (["arguments"] ++ param) ([arguments] ++ args ++ repeat Undefined)
       withScope scope
                 $ do pushFrame this binding
                      withCC CReturn (eval body >> returnCont CReturn Undefined)
    where makeArguments
              = return $ nullObject { objPropMap = mkPropMap argProps }
          argProps = zip3 (map show [0..]) (args) (repeat [DontEnum]) ++
                         [("callee", callee, [DontEnum]), ("length", toValue $ length args, [DontEnum])]
 
callWithThis this Object { objName = name, objObject = NativeFunction { funcNatCode = nativeFunc } } args =
    do debug $ "callWithThis: " ++ name
       nativeFunc this args
 
callWithThis _ Object { objName = name } _ =
    throw "TypeError" $ name ++ " is not a function"
 
callWithThis this ref@Ref { } args =
    do object <- readRef ref
       callWithThis this object args
 
callWithThis this ref@Reference { } args =
    do callee <- getValue ref
       ifM (toBoolean callee)
           (callWithThis this callee args)
           (throw "TypeError" $ show callee ++ " is not a function")
 
callWithThis _ value _ =
    throw "TypeError" $ show value ++ " is not a function"
 
callMethod :: Value -> String -> [Value] -> Evaluate Value
callMethod object name args =
    do method <- readRef =<< getProp object name
       if isFunction method || isNativeFunction method
          then callWithThis object method args
          else throw "TypeError" $ show method ++ " is not a function"
 
-- 末尾再帰用
jumpToFunc :: Value -> [Value] -> Evaluate Value
jumpToFunc Reference { refBase = base, refName = name } args =
    jumpToMethod base name args
 
jumpToFunc function args =
    do global <- getGlobal
       jumpToFuncWithThis global function args
 
jumpToFuncWithThis :: Value -> Value -> [Value] -> Evaluate Value
jumpToFuncWithThis this callee@Object { objObject = Function { funcParam = param, funcBody = body, funcScope = scope } } args =
    do debug $ "jumpToFuncWithThis: " ++ objName callee
       arguments <- makeArguments
       binding <- makeRef =<< bindParamArgs (["arguments"] ++ param) ([arguments] ++ args ++ repeat Undefined)
       modifyScope scope
       pushFrame this binding
       eval body
    where makeArguments
              = return $ nullObject { objPropMap = mkPropMap argProps }
          argProps = zip3 (map show [0..]) (args) (repeat [DontEnum]) ++
                         [("callee", callee, [DontEnum]), ("length", toValue $ length args, [DontEnum])]
 
jumpToFuncWithThis this Object { objName = name, objObject = NativeFunction { funcNatCode = nativeFunc } } args =
    do debug $ "jumpToFuncWithThis: " ++ name
       returnCont CReturn =<< nativeFunc this args
 
jumpToFuncWithThis this ref@Ref { } args =
    do object <- readRef ref
       jumpToFuncWithThis this object args
 
jumpToFuncWithThis _ object _ =
    throw "TypeError" $ getName object ++ " is not a function"
 
jumpToMethod :: Value -> String -> [Value] -> Evaluate Value
jumpToMethod object name args =
    do method <- readRef =<< getProp object name
       if isFunction method || isNativeFunction method
          then jumpToFuncWithThis object method args
          else throw "TypeError" $ getName object ++ "." ++ name ++ " is not a function"
 
-- [[Construct]]
construct :: Value -> [Value] -> Evaluate Value
construct obj@Object { objConstruct = Just constructor } args =
    do proto <- getProp obj "prototype"
       object <- makeRef =<< constructor Null args
       modifyValue object $ setObjProto proto
       return object
 
construct obj@Object { objObject = Function { } } args =
    do proto <- getProp obj "prototype"
       object <- makeRef $ nullObject { objPrototype = proto }
       callWithThis object obj args
       return object
 
construct obj@Object { objObject = NativeFunction { } } args =
    do proto <- getProp obj "prototype"
       object <- makeRef $ nullObject { objPrototype = proto }
       callWithThis object obj args
       return object
 
construct ref@Ref { } args =
    readRef ref >>= flip construct args
 
construct Object { objValue = value } args | not $ isNull value =
    construct value args
 
construct c _ =
    throw "TypeError" $ getName c ++ " is not a constructor"
 
new :: String -> [Value] -> Evaluate Value
new name args =
    do klass <- getVar name
       makeRef =<< construct klass args
 
defaultValue :: Value -> String -> Evaluate Value
defaultValue object hint =
    (case hint of
          "String" -> liftM2 mplus (tryMethod "toString") (tryMethod "valueOf")
          "Number" -> liftM2 mplus (tryMethod "valueOf") (tryMethod "toString")
          _ -> ifM (liftM ("Date" ==) $ classOf object)
                   (liftM2 mplus (tryMethod "toString") (tryMethod "valueOf"))
                   (liftM2 mplus (tryMethod "valueOf") (tryMethod "toString")))
    >>= maybe (throw "NotImplemented" $ "defaultValue: " ++ show object ++ " " ++ hint)
              (return)
    where tryMethod :: String -> Evaluate (Maybe Value)
          tryMethod name =
              do method <- getProp object name
                 if isNull method || isUndefined method
                    then return Nothing
                    else do result <- callWithThis object method []
                            if isPrimitive result
                               then return $ Just result
                               else return Nothing
 
try :: Evaluate a -> Evaluate ()
try thunk =
    do e <- withCC CThrow $ do { thunk; return Void }
       unless (isVoid e)
              (toString e >>= liftIO . ePutStrLn)