on aee2191 help sign in
elk/src/Generate/JavaScript/Variable.hs
211 lines 4.8 kB history raw
1{-# OPTIONS_GHC -Wall #-}
2{-# LANGUAGE OverloadedStrings #-}
3{-# LANGUAGE UnboxedTuples #-}
4module Generate.JavaScript.Variable
5 ( Generator
6 , run
7 , Table
8 , init
9 , fresh
10 , local
11 , safe
12 , global
13 , getGlobalName
14 )
15 where
18import Prelude hiding (init)
19import qualified Control.Monad.State as State
20import qualified Data.Char as Char
21import Data.Monoid ((<>))
22import qualified Data.Map as Map
23import qualified Data.Set as Set
24import qualified Data.Text as Text
25import Data.Text (Text)
26import Data.Word (Word16, Word32)
28import qualified AST.Helpers as Help
29import qualified AST.Module.Name as ModuleName
30import qualified AST.Variable as Var
31import qualified Elm.Package as Pkg
32import qualified Elm.Compiler.Objects.Internal as Obj
33import qualified Generate.JavaScript.Builder as JS
34import qualified Generate.JavaScript.Helpers as JS
38-- GENERATOR
41type Generator = State.State Table
44run :: Table -> Generator a -> ( a, Table )
45run table generator =
46 State.runState generator table
50-- STATE
53data Table =
54 Table
55 { _uid :: !Int
56 , _names :: Map.Map Var.Global Text
57 , _symbols :: Obj.SymbolTable
58 }
61init :: Obj.SymbolTable -> Table
62init symbols =
63 Table 0 Map.empty symbols
67-- FRESH NAMES
70fresh :: Generator Text
71fresh =
72 do (Table uid names symbols) <- State.get
73 State.put (Table (uid + 1) names symbols)
74 return (Text.pack ("_v" ++ show uid))
78-- LOCAL NAMES
81local :: Text -> JS.Expr
82local name =
83 JS.ref (safe name)
86safe :: Text -> Text
87safe name =
88 if Set.member name reservedWords then
89 "_" <> name
90 else
91 name
94reservedWords :: Set.Set Text
95reservedWords =
96 Set.fromList
97 -- reserved by JavaScript
98 [ "null", "undefined", "Nan", "Infinity", "true", "false", "eval"
99 , "arguments", "int", "byte", "char", "goto", "long", "final", "float"
100 , "short", "double", "native", "throws", "boolean", "abstract", "volatile"
101 , "transient", "synchronized", "function", "break", "case", "catch"
102 , "continue", "debugger", "default", "delete", "do", "else", "finally"
103 , "for", "function", "if", "in", "instanceof", "new", "return", "switch"
104 , "this", "throw", "try", "typeof", "var", "void", "while", "with", "class"
105 , "const", "enum", "export", "extends", "import", "super", "implements"
106 , "interface", "let", "package", "private", "protected", "public"
107 , "static", "yield"
108 -- reserved by Elm
109 , "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9"
110 , "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9"
111 , "ctor"
112 ]
116-- GLOBAL NAMES
119global :: Var.Global -> Generator JS.Expr
120global var =
121 do table@(Table _ _ symbols) <- State.get
122 case Obj.lookup var symbols of
123 Nothing ->
124 JS.ref <$> getGlobalNameHelp var table
126 Just (Obj.Symbol symHome symName) ->
127 return $
128 JS.DotRef
129 (JS.ref (symbolHomeToText symHome))
130 (JS.Id (symbolNameToText symName))
133getGlobalName :: Var.Global -> Generator Text
134getGlobalName var =
135 do table <- State.get
136 getGlobalNameHelp var table
139getGlobalNameHelp :: Var.Global -> Table -> Generator Text
140getGlobalNameHelp var@(Var.Global home name) (Table uid names symbols) =
141 case Map.lookup var names of
142 Just jsName ->
143 return jsName
145 Nothing ->
146 let
147 (# newUid, newName #) =
148 if Help.isOp name
149 then (# uid + 1, "_op" <> Text.pack (show uid) #)
150 else (# uid, name #)
151 in
152 do let jsName = globalToName home newName
153 let newNames = Map.insert var jsName names
154 State.put (Table newUid newNames symbols)
155 return jsName
158globalToName :: ModuleName.Canonical -> Text -> Text
159globalToName (ModuleName.Canonical (Pkg.Name user project) moduleName) name =
160 if ModuleName.isKernel moduleName then
161 "_" <> ModuleName.getKernel moduleName <> "_" <> name
163 else
164 Text.replace "-" "_" user
165 <> "$" <> Text.replace "-" "_" project
166 <> "$" <> Text.replace "." "_" moduleName
167 <> "$" <> name
171-- SYMBOLS
174symbolHomeToText :: Word16 -> Text
175symbolHomeToText word =
176 Text.pack ('$' : show word)
179symbolNameToText :: Word32 -> Text
180symbolNameToText word =
181 Text.pack (makeName word "")
184makeName :: Word32 -> String -> String
185makeName word str =
186 if word < 26 then
187 makeChar 97 word : str
189 else if word < 52 then
190 makeChar 65 (word - 26) : str
192 else
193 let
194 (next, leftover) =
195 quotRem word 52
196 in
197 makeName next (makeInnerChar leftover : str)
200makeInnerChar :: Word32 -> Char
201makeInnerChar word =
202 if word < 26 then
203 makeChar 97 word
204 else
205 makeChar 65 (word - 26)
208{-# INLINE makeChar #-}
209makeChar :: Word32 -> Word32 -> Char
210makeChar root offset =
211 Char.chr (fromIntegral (root + offset))