├── README.md ├── index.html ├── codemirror.css ├── reference.html ├── script.js └── javascript.js /README.md: -------------------------------------------------------------------------------- 1 | ## GitHub Game Off 2016 2 | # HackShot 3 | 4 | A small Javascript coding game where you control a lone gun tower against hordes of incoming enemies. 5 | Play it [here](http://buch415.github.io/game-off-2016), and check out the 6 | [manual and reference](http://buch415.github.io/game-off-2016/reference.html) 7 | 8 | Uses [CodeMirror](https://codemirror.net/) for code editors and syntax highlighting. 9 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 14 | 15 | 16 | HackShot 17 | 18 | 127 | 128 | 129 | 130 |
131 |
132 |
133 | 141 |
142 |
143 | Play 144 | -speed: 1+ 145 |
150 |
151 |
152 | 153 | 154 | -------------------------------------------------------------------------------- /codemirror.css: -------------------------------------------------------------------------------- 1 | /* BASICS */ 2 | 3 | .CodeMirror { 4 | /* Set height, width, borders, and global font properties here */ 5 | font-family: monospace; 6 | height: 300px; 7 | color: black; 8 | } 9 | 10 | /* PADDING */ 11 | 12 | .CodeMirror-lines { 13 | padding: 4px 0; /* Vertical padding around content */ 14 | } 15 | .CodeMirror pre { 16 | padding: 0 8px; /* Horizontal padding of content */ 17 | } 18 | 19 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 20 | background-color: white; /* The little square between H and V scrollbars */ 21 | } 22 | 23 | /* GUTTER */ 24 | 25 | .CodeMirror-gutters { 26 | background-color: #151515; 27 | white-space: nowrap; 28 | } 29 | .CodeMirror-linenumbers {} 30 | .CodeMirror-linenumber { 31 | padding: 0 3px 0 5px; 32 | min-width: 20px; 33 | text-align: right; 34 | color: #777; 35 | white-space: nowrap; 36 | } 37 | 38 | .CodeMirror-guttermarker { color: black; } 39 | .CodeMirror-guttermarker-subtle { color: #999; } 40 | 41 | /* CURSOR */ 42 | 43 | .CodeMirror-cursor { 44 | border-left: 1px solid black; 45 | border-right: none; 46 | width: 0; 47 | } 48 | /* Shown when moving in bi-directional text */ 49 | .CodeMirror div.CodeMirror-secondarycursor { 50 | border-left: 1px solid silver; 51 | } 52 | .cm-fat-cursor .CodeMirror-cursor { 53 | width: auto; 54 | border: 0 !important; 55 | background: #7e7; 56 | } 57 | .cm-fat-cursor div.CodeMirror-cursors { 58 | z-index: 1; 59 | } 60 | 61 | .cm-animate-fat-cursor { 62 | width: auto; 63 | border: 0; 64 | -webkit-animation: blink 1.06s steps(1) infinite; 65 | -moz-animation: blink 1.06s steps(1) infinite; 66 | animation: blink 1.06s steps(1) infinite; 67 | background-color: #7e7; 68 | } 69 | @-moz-keyframes blink { 70 | 0% {} 71 | 50% { background-color: transparent; } 72 | 100% {} 73 | } 74 | @-webkit-keyframes blink { 75 | 0% {} 76 | 50% { background-color: transparent; } 77 | 100% {} 78 | } 79 | @keyframes blink { 80 | 0% {} 81 | 50% { background-color: transparent; } 82 | 100% {} 83 | } 84 | 85 | /* Can style cursor different in overwrite (non-insert) mode */ 86 | .CodeMirror-overwrite .CodeMirror-cursor {} 87 | 88 | .cm-tab { display: inline-block; text-decoration: inherit; } 89 | 90 | .CodeMirror-rulers { 91 | position: absolute; 92 | left: 0; right: 0; top: -50px; bottom: -20px; 93 | overflow: hidden; 94 | } 95 | .CodeMirror-ruler { 96 | border-left: 1px solid #ccc; 97 | top: 0; bottom: 0; 98 | position: absolute; 99 | } 100 | 101 | /* DEFAULT THEME */ 102 | 103 | .cm-s-default .cm-header {color: blue;} 104 | .cm-s-default .cm-quote {color: #090;} 105 | .cm-negative {color: #d44;} 106 | .cm-positive {color: #292;} 107 | .cm-header, .cm-strong {font-weight: bold;} 108 | .cm-em {font-style: italic;} 109 | .cm-link {text-decoration: underline;} 110 | .cm-strikethrough {text-decoration: line-through;} 111 | 112 | .cm-s-default .cm-keyword {color: #7fff3f;} 113 | .cm-s-default .cm-atom {color: #219;} 114 | .cm-s-default .cm-number {color: #ff2323;} 115 | .cm-s-default .cm-def {color: #ff2323;} 116 | .cm-s-default .cm-variable, 117 | .cm-s-default .cm-punctuation, 118 | .cm-s-default .cm-property, 119 | .cm-s-default .cm-operator {} 120 | .cm-s-default .cm-variable-2 {color: #05a;} 121 | .cm-s-default .cm-variable-3 {color: #085;} 122 | .cm-s-default .cm-comment {color: #505050;} 123 | .cm-s-default .cm-string {color: #a11;} 124 | .cm-s-default .cm-string-2 {color: #f50;} 125 | .cm-s-default .cm-meta {color: #555;} 126 | .cm-s-default .cm-qualifier {color: #555;} 127 | .cm-s-default .cm-builtin {color: #30a;} 128 | .cm-s-default .cm-bracket {color: #997;} 129 | .cm-s-default .cm-tag {color: #170;} 130 | .cm-s-default .cm-attribute {color: #00c;} 131 | .cm-s-default .cm-hr {color: #999;} 132 | .cm-s-default .cm-link {color: #00c;} 133 | 134 | .cm-s-default .cm-error {color: #f00;} 135 | .cm-invalidchar {color: #f00;} 136 | 137 | .CodeMirror-composing { border-bottom: 2px solid; } 138 | 139 | /* Default styles for common addons */ 140 | 141 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 142 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 143 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } 144 | .CodeMirror-activeline-background {background: #e8f2ff;} 145 | 146 | /* STOP */ 147 | 148 | /* The rest of this file contains styles related to the mechanics of 149 | the editor. You probably shouldn't touch them. */ 150 | 151 | .CodeMirror { 152 | position: relative; 153 | overflow: hidden; 154 | background: white; 155 | } 156 | 157 | .CodeMirror-scroll { 158 | overflow: scroll !important; /* Things will break if this is overridden */ 159 | /* 30px is the magic margin used to hide the element's real scrollbars */ 160 | /* See overflow: hidden in .CodeMirror */ 161 | margin-bottom: -30px; margin-right: -30px; 162 | padding-bottom: 30px; 163 | height: 100%; 164 | outline: none; /* Prevent dragging from highlighting the element */ 165 | position: relative; 166 | } 167 | .CodeMirror-sizer { 168 | position: relative; 169 | border-right: 30px solid transparent; 170 | } 171 | 172 | /* The fake, visible scrollbars. Used to force redraw during scrolling 173 | before actual scrolling happens, thus preventing shaking and 174 | flickering artifacts. */ 175 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 176 | position: absolute; 177 | z-index: 6; 178 | display: none; 179 | } 180 | .CodeMirror-vscrollbar { 181 | right: 0; top: 0; 182 | overflow-x: hidden; 183 | overflow-y: scroll; 184 | } 185 | .CodeMirror-hscrollbar { 186 | bottom: 0; left: 0; 187 | overflow-y: hidden; 188 | overflow-x: scroll; 189 | } 190 | .CodeMirror-scrollbar-filler { 191 | right: 0; bottom: 0; 192 | } 193 | .CodeMirror-gutter-filler { 194 | left: 0; bottom: 0; 195 | } 196 | 197 | .CodeMirror-gutters { 198 | position: absolute; left: 0; top: 0; 199 | min-height: 100%; 200 | z-index: 3; 201 | } 202 | .CodeMirror-gutter { 203 | white-space: normal; 204 | height: 100%; 205 | display: inline-block; 206 | vertical-align: top; 207 | margin-bottom: -30px; 208 | } 209 | .CodeMirror-gutter-wrapper { 210 | position: absolute; 211 | z-index: 4; 212 | background: none !important; 213 | border: none !important; 214 | } 215 | .CodeMirror-gutter-background { 216 | position: absolute; 217 | top: 0; bottom: 0; 218 | z-index: 4; 219 | } 220 | .CodeMirror-gutter-elt { 221 | position: absolute; 222 | cursor: default; 223 | z-index: 4; 224 | } 225 | .CodeMirror-gutter-wrapper { 226 | -webkit-user-select: none; 227 | -moz-user-select: none; 228 | user-select: none; 229 | } 230 | 231 | .CodeMirror-lines { 232 | cursor: text; 233 | min-height: 1px; /* prevents collapsing before first draw */ 234 | } 235 | .CodeMirror pre { 236 | /* Reset some styles that the rest of the page might have set */ 237 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; 238 | border-width: 0; 239 | background: transparent; 240 | font-family: inherit; 241 | font-size: inherit; 242 | margin: 0; 243 | white-space: pre; 244 | word-wrap: normal; 245 | line-height: inherit; 246 | color: inherit; 247 | z-index: 2; 248 | position: relative; 249 | overflow: visible; 250 | -webkit-tap-highlight-color: transparent; 251 | -webkit-font-variant-ligatures: none; 252 | font-variant-ligatures: none; 253 | } 254 | .CodeMirror-wrap pre { 255 | word-wrap: break-word; 256 | white-space: pre-wrap; 257 | word-break: normal; 258 | } 259 | 260 | .CodeMirror-linebackground { 261 | position: absolute; 262 | left: 0; right: 0; top: 0; bottom: 0; 263 | z-index: 0; 264 | } 265 | 266 | .CodeMirror-linewidget { 267 | position: relative; 268 | z-index: 2; 269 | overflow: auto; 270 | } 271 | 272 | .CodeMirror-widget {} 273 | 274 | .CodeMirror-code { 275 | outline: none; 276 | } 277 | 278 | /* Force content-box sizing for the elements where we expect it */ 279 | .CodeMirror-scroll, 280 | .CodeMirror-sizer, 281 | .CodeMirror-gutter, 282 | .CodeMirror-gutters, 283 | .CodeMirror-linenumber { 284 | -moz-box-sizing: content-box; 285 | box-sizing: content-box; 286 | } 287 | 288 | .CodeMirror-measure { 289 | position: absolute; 290 | width: 100%; 291 | height: 0; 292 | overflow: hidden; 293 | visibility: hidden; 294 | } 295 | 296 | .CodeMirror-cursor { 297 | position: absolute; 298 | pointer-events: none; 299 | } 300 | .CodeMirror-measure pre { position: static; } 301 | 302 | div.CodeMirror-cursors { 303 | visibility: hidden; 304 | position: relative; 305 | z-index: 3; 306 | } 307 | div.CodeMirror-dragcursors { 308 | visibility: visible; 309 | } 310 | 311 | .CodeMirror-focused div.CodeMirror-cursors { 312 | visibility: visible; 313 | } 314 | 315 | .CodeMirror-selected { background: #d9d9d9; } 316 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 317 | .CodeMirror-crosshair { cursor: crosshair; } 318 | .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; } 319 | .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } 320 | 321 | .cm-searching { 322 | background: #ffa; 323 | background: rgba(255, 255, 0, .4); 324 | } 325 | 326 | /* Used to force a border model for a node */ 327 | .cm-force-border { padding-right: .1px; } 328 | 329 | @media print { 330 | /* Hide the cursor when printing */ 331 | .CodeMirror div.CodeMirror-cursors { 332 | visibility: hidden; 333 | } 334 | } 335 | 336 | /* See issue #2901 */ 337 | .cm-tab-wrap-hack:after { content: ''; } 338 | 339 | /* Help users use markselection to safely style text background */ 340 | span.CodeMirror-selectedtext { background: none; } 341 | -------------------------------------------------------------------------------- /reference.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 14 | HackShot - Manual and API reference 15 | 52 | 53 | 63 | 64 | 65 | 66 |
67 |

Game manual

68 |

The aim of this game is to programmatically control a cannon to shoot 69 | and destroy hordes of enemies. The cannon exposes a JavaScript API for you 70 | to use to control it. You will find some examples and a 71 | complete API reference below. 72 |

73 |

Every time an enemy reaches the cannon, the cannon 74 | loses 1 health point (out of 10). Once health goes down to 0, you lose. 75 |

76 |

The cannon can basically perform two actions: it can 77 | shoot or self-repair from damage taken. Both actions will require some 78 | time for the cannon to be ready again. 79 |

80 |

Aiming

81 |

You can aim the cannon using the function 82 | this.setCannonAngle (A), which takes as a parameter 83 | the angle you want to point the cannon at, measured in radians, counter-clockwise, 84 | starting from the horizontal (which means, 0 is horizontal, Math.PI / is 85 | vertical). The cannon has a minimum and maximum angle, which you can 86 | respectively read in the variables cannonMinAngle and cannonMaxAngle. 87 | When the function is called, the cannon starts moving towards the wanted 88 | angle. You can check what angle it currently is at using the function 89 | this.getCannonAngle(). 90 |

91 | 92 |

Shooting

93 |

Using the function this.shoot(), the 94 | cannon will shoot if ready. After shooting, it will have to reload and 95 | won't be able to shoot for a while. 96 |

97 | 98 |

Repairing

99 |

You can recover your cannon's health with the function 100 | this.repair(). As for shooting, this will take some 101 | time for the cannon to be ready again to shoot or repair. Repair reload 102 | time is twice as long as shoot reload time. 103 |

104 | 105 |

Enemies

106 |

You can access information about the enemies via the 107 | this.getEnemies() function. Enemies can come 108 | both on the ground or in the air. The pattern they will follow depends 109 | on the test you choose. 110 |

111 | 112 |

Scoring

113 |

The score will be calculated as follows:
114 | [survived time] + 5*[kills] + 20*[accuracy] - [repairs]

115 | 116 |

Examples

117 |

Basic shooting:

118 |

setup: function () { /* Miscellaneous setup things here */ }, 119 | 120 | update: function () { 121 | this.shoot(); // Shoots the cannon 122 | }

123 | 124 |

"Up'n'down":

125 |

setup: function() { 126 | this.setCannonAngle ( cannonMaxAngle ); 127 | }, 128 | 129 | update: function () { 130 | // Moves the cannon from up to down continuously 131 | if ( this.getCannonAngle() == cannonMaxAngle ) 132 | this.setCannonAngle ( cannonMinAngle ); 133 | if ( this.getCannonAngle() == cannonMinAngle ) 134 | this.setCannonAngle ( cannonMaxAngle ); 135 | 136 | this.shoot(); 137 | }

138 | 139 |

Repairing:

140 |

setup: function () { /* Miscellaneous setup things here */ }, 141 | 142 | update: function () { 143 | if ( this.hp() < 5 ) // EMERGENCY! Low health => repair 144 | this.repair(); 145 | 146 | this.shoot(); 147 | }

148 | 149 |

Cannon API reference

150 |

Methods:

151 |

152 | this.shoot(): 153 | shoots the cannon; it works only if the cannon 154 | is ready; you can check that programmatically via the 155 | this.ready() function. The function returns 1 156 | if the cannon could shoot, 0 otherwise. 157 |

158 | 159 |

160 | this.repair(): 161 | repairs your cannon, restoring 1 hit point; it 162 | works only if the cannon is ready; you can check that programmatically via the 163 | this.ready() function. The function returns 1 164 | if the cannon could repair, 0 otherwise. 165 |

166 | 167 |

168 | this.setCannonAngle(A): 169 | tells the cannon to move to angle A, given in radians 170 | (0 means horizontal, Math.PI/2 means vertical - note that cannon angle has 171 | a maximum and a minimum, see more here). 172 |

173 | 174 |

175 | this.getCannonAngle(): 176 | returns current cannon angle, in radians; 0 means 177 | horizontal and Math.PI/2 means vertical (note that cannon angle has 178 | a maximum and a minimum, see more here). 179 |

180 | 181 |

182 | this.getEnemies(): 183 | returns the current alive enemies as an array; 184 | each element of the array is a two-element array [x,y], where x is 185 | the x coordinate of the enemy (0 is the cannon's base center position) 186 | and y is the vertical coordinate (0 is the ground). Enemies are sorted 187 | in ascending x order, so that the first element of the array is always 188 | the closest to the cannon. 189 |

190 | 191 |

192 | this.ready(): 193 | returns 1 if the cannon is ready to shoot, 194 | or repair 0 otherwise. 195 |

196 | 197 |

198 | this.hp(): 199 | returns the amount of hit points left the cannon has. 200 |

201 | 202 |

203 | this.mark(i): 204 | you can use this function to mark the enemy of index i. 205 | The marked enemy is graphically highlighted. You can use this function for 206 | debugging purposes; you can mark only one enemy at a time, so the only 207 | actually marked enemy will be the last one on which this function was called. 208 |

209 | 210 |

211 | this.unmark(): 212 | removes the mark from the currently marked enemy. 213 |

214 | 215 |

Constants:

216 |

217 | cannonShootSpeed: 218 | the speed of the cannon projectile when shot. 219 |

220 | 221 |

222 | g: 223 | gravity force. 224 |

225 | 226 |

227 | controllerT: 228 | game time in seconds between two subsequent calls of the update() function 229 | (game time is measured with respect to the timer in the top left corner of game screen). 230 |

231 | 232 |

233 | cannonMinAngle: 234 | minimum cannon angle. 235 |

236 | 237 |

238 | cannonMaxAngle: 239 | maximum cannon angle. 240 |

241 |
242 | 243 | 244 | -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | var canvas = 0, context = 0; 2 | const fps = 60; 3 | 4 | // Graphics settings of sorts 5 | var gfx_lvlColor_alive = "#7fff3f"; 6 | var gfx_lvlColor_dead = "#ff2323"; 7 | var gfx_bgColor = "#101010"; 8 | var gfx_rangeColor = "#151515"; 9 | var gfx_linew = 4; 10 | 11 | var gfx_enemyH = 17; 12 | var gfx_enemyR = 10; 13 | 14 | var levelY = 0; // Level base y 15 | 16 | const cannonR = 20; // Cannon base radius 17 | const cannonL = 20; // Cannon length 18 | const cannonX = 20; // Cannon X coordinate 19 | const cannonSpeed = (Math.PI / 2); // Cannon rotation speed 20 | const cannonShootSpeed = 350; // Bullet (initial) speed 21 | 22 | const cannonMinAngle = Math.PI / 16; 23 | const cannonMaxAngle = Math.PI / 2 - cannonMinAngle; 24 | 25 | const cannonMaxHP = 10; 26 | 27 | const g = 200; // Gravity 28 | 29 | const shootReloadSpeed = 1; // Cannon reload speed 30 | const repairReloadSpeed = 0.5; // Repair reload speed 31 | const boomRange = 20; // Bullet explosion range 32 | 33 | const controllerT = 1 / 10 / fps; 34 | 35 | var gameSpeed = 1; 36 | var pause = 0; // 1 = play, 0 = pause 37 | var _gameOver = 0; 38 | 39 | var spawners = [ 40 | { 41 | id: "exp_amount", 42 | name: "Exp_Am", 43 | desc: "the enemy spawn frequency increases exponentially over time", 44 | spawnTimeMin: 0, 45 | spawnTimeMax: 3, 46 | spawnTimeT: 50, 47 | speed: 60, 48 | condition: function ( l ) { 49 | return l.lastSpawn < 0 || l.time - l.lastSpawn > this.spawnTimeMin + (this.spawnTimeMax - this.spawnTimeMin) * Math.exp ( -l.time / this.spawnTimeT ); 50 | }, 51 | spawn: function ( l ) { 52 | return [ canvas.width + gfx_enemyR, 0, 60 ]; 53 | } 54 | }, 55 | 56 | { 57 | id: "exp_amount_air", 58 | name: "Exp_Air", 59 | desc: "the enemy spawn frequency increases exponentially over time", 60 | spawnTimeMin: 0, 61 | spawnTimeMax: 3, 62 | spawnTimeT: 50, 63 | speed: 60, 64 | condition: function ( l ) { 65 | return l.lastSpawn < 0 || l.time - l.lastSpawn > this.spawnTimeMin + (this.spawnTimeMax - this.spawnTimeMin) * Math.exp ( -l.time / this.spawnTimeT ); 66 | }, 67 | spawn: function ( l ) { 68 | return [ canvas.width + gfx_enemyR, 40, 60 ]; 69 | } 70 | }, 71 | 72 | { 73 | id: "exp_all", 74 | name: "Exp_All", 75 | desc: "the enemy spawn frequency and speed increases exponentially over time", 76 | spawnTimeMin: 0, 77 | spawnTimeMax: 3, 78 | spawnTimeT: 50, 79 | speedMin: 60, 80 | speedMax: 100, 81 | speedT: 50, 82 | condition: function ( l ) { 83 | return l.lastSpawn < 0 || l.time - l.lastSpawn > this.spawnTimeMin + (this.spawnTimeMax - this.spawnTimeMin) * Math.exp ( -l.time / this.spawnTimeT ); 84 | }, 85 | spawn: function ( l ) { 86 | return [ canvas.width + gfx_enemyR, 0, this.speedMax - (this.speedMax - this.speedMin) * Math.exp ( -l.time / this.speedT ) ]; 87 | } 88 | }, 89 | 90 | { 91 | id: "packs", 92 | name: "Packs", 93 | desc: "enemies are spawned in packs", 94 | packSize: 0, 95 | spawnedPacks: 0, 96 | air: 0, 97 | packSizeMin: 1, 98 | packSizeMax: 10, 99 | packSizeT: 50, 100 | packT: 0.38, 101 | spawnTime: 2.5, 102 | condition: function ( l ) { 103 | if ( this.packSize > 0 && l.time - l.lastSpawn > this.packT ) { this.packSize--; return 1; } 104 | else if ( l.lastSpawn < 0 || l.time - l.lastSpawn > this.spawnTime ) { 105 | this.packSize = this.packSizeMax - Math.floor ( (this.packSizeMax - this.packSizeMin) * Math.exp ( -l.time / this.packSizeT ) ); 106 | this.spawnedPacks++; 107 | this.air = (this.spawnedPacks % 4) == 0; 108 | } 109 | }, 110 | spawn: function ( l ) { 111 | return [ canvas.width + gfx_enemyR, this.air * 40, 60 ]; 112 | } 113 | }, 114 | 115 | { 116 | id: "air_var", 117 | name: "Air", 118 | desc: "enemies fly at different heights", 119 | spawnTimeMin: 0, 120 | spawnTimeMax: 3, 121 | spawnTimeT: 50, 122 | speed: 60, 123 | condition: function ( l ) { 124 | return l.lastSpawn < 0 || l.time - l.lastSpawn > this.spawnTimeMin + (this.spawnTimeMax - this.spawnTimeMin) * Math.exp ( -l.time / this.spawnTimeT ); 125 | }, 126 | spawn: function ( l ) { 127 | return [ canvas.width + gfx_enemyR, 30 + 30 * Math.sin((l.time % 5) / 5), 60 ]; 128 | } 129 | }, 130 | 131 | { 132 | id: "rand", 133 | name: "Rand", 134 | desc: "many random things", 135 | spawnChanceBase: 0.005, 136 | spawnChanceMax: 0.025, 137 | spawnChanceT: 50, 138 | speedBase: 50, 139 | speedDelta: 30, 140 | speedT: 50, 141 | condition: function ( l ) { 142 | return Math.random() < this.spawnChanceMax - ( this.spawnChanceMax - this.spawnChanceBase ) * Math.exp ( -l.time / this.spawnChanceT ); 143 | }, 144 | spawn: function ( l ) { 145 | var h = [ 0, 30, 60 ]; 146 | return [ canvas.width + gfx_enemyR, h [ Math.floor(Math.random() * 3) ], 147 | this.speedBase + this.speedDelta * ( 1 - Math.exp ( -l.time / this.speedT ) ) * Math.random() ]; 148 | } 149 | } 150 | ]; 151 | 152 | function togglePause () { 153 | pause = 1 - pause; 154 | pauseBtn = document.getElementById("pause"); 155 | 156 | if ( pause ) pauseBtn.innerText = "Pause"; 157 | else pauseBtn.innerText = "Play"; 158 | } 159 | 160 | function unpauseGame () { 161 | pause = 1; 162 | pauseBtn = document.getElementById("pause"); 163 | pauseBtn.innerText = "Pause"; 164 | } 165 | 166 | function pauseGame () { 167 | pause = 0; 168 | pauseBtn = document.getElementById("pause"); 169 | pauseBtn.innerText = "Play"; 170 | } 171 | 172 | function gradientCol ( a, b, k ) { 173 | var ar = parseInt ( a.substr(1,2), 16 ); 174 | var ag = parseInt ( a.substr(3,2), 16 ); 175 | var ab = parseInt ( a.substr(5,2), 16 ); 176 | 177 | var br = parseInt ( b.substr(1,2), 16 ); 178 | var bg = parseInt ( b.substr(3,2), 16 ); 179 | var bb = parseInt ( b.substr(5,2), 16 ); 180 | 181 | var mida = Math.round(ar * k + br * (1-k)); 182 | var midg = Math.round(ag * k + bg * (1-k)); 183 | var midb = Math.round(ab * k + bb * (1-k)); 184 | 185 | function hex (x) { 186 | var s = x.toString(16); 187 | return s.length > 1 ? s : ('0' + s); 188 | } 189 | 190 | return "#" + hex(mida) + hex(midg) + hex(midb); 191 | } 192 | 193 | var setup = function () { 194 | canvas = document.getElementById("canvas"); 195 | context = canvas.getContext("2d"); 196 | context.lineJoin = "round"; 197 | 198 | if ( localStorage.controller != undefined ) 199 | document.getElementById("code").innerText = localStorage.controller; 200 | 201 | codeMirror = CodeMirror.fromTextArea(document.getElementById("code"), { lineNumbers: true } ); 202 | 203 | levelY = 7 * canvas.height / 8; 204 | 205 | document.getElementById("apply").onclick = function () { 206 | mainLevel.reset(); 207 | _gameOver = 0; 208 | mainLevel.applyController ( codeMirror.doc.getValue() ); 209 | localStorage.controller = codeMirror.doc.getValue(); 210 | unpauseGame(); 211 | } 212 | 213 | document.getElementById("pause").onclick = togglePause; 214 | 215 | document.getElementById("slow").onclick = function () { 216 | gameSpeed -= 1; if ( gameSpeed < 1 ) gameSpeed = 1; 217 | document.getElementById("speed").innerText = "speed: " + gameSpeed; 218 | localStorage.gameSpeed = gameSpeed; 219 | } 220 | document.getElementById("fast").onclick = function () { 221 | gameSpeed += 1; 222 | document.getElementById("speed").innerText = "speed: " + gameSpeed; 223 | localStorage.gameSpeed = gameSpeed; 224 | } 225 | 226 | for ( var i = 0; i < spawners.length; i++ ) { 227 | $('' + i + '').appendTo("#spawners"); 228 | } 229 | 230 | if ( localStorage.gameSpeed ) { 231 | gameSpeed = parseInt(localStorage.gameSpeed); 232 | document.getElementById("speed").innerText = "speed: " + gameSpeed; 233 | } 234 | 235 | $("#spawners #0").addClass("active"); 236 | 237 | $("#spawners .btn").click ( function() { 238 | $("#spawners .btn.active").removeClass("active"); 239 | $(this).addClass("active"); 240 | mainLevel.spawner = spawners[ this.id ]; 241 | pauseGame(); 242 | mainLevel.reset(); 243 | } ); 244 | 245 | $("#reset").click ( function () { 246 | if ( $(this).text() == "Confirm?" ) { 247 | for ( var i = 0; i < spawners.length; i++ ) 248 | delete ( localStorage["hiscore_" + spawners[i].id] ); 249 | $(this).text("Hi-scores reset"); 250 | } 251 | else $(this).text("Confirm?"); 252 | } ); 253 | 254 | $("#reset").on ( 'mouseout', function () { 255 | $(this).text("Reset hi-scores"); 256 | } ); 257 | 258 | setInterval ( frame, 1000 / fps ); 259 | } 260 | 261 | var frame = function () { 262 | draw(); 263 | update(); 264 | } 265 | 266 | var screenShakeStartT = -1; 267 | function screenShake() { screenShakeStartT = (new Date()).getTime(); } 268 | function getScreenShake () { 269 | if ( screenShakeStartT < 0 ) return 0; 270 | 271 | t = (new Date()).getTime() - screenShakeStartT; 272 | return 10 * Math.exp ( -t / 150 ) * Math.cos ( t / 10 ); 273 | } 274 | 275 | var gameOver = function () { 276 | pauseGame(); 277 | _gameOver = 1; 278 | 279 | s = "hiscore_" + mainLevel.spawner.id; 280 | if ( !localStorage[s] || mainLevel.score() > localStorage[s] ) 281 | localStorage[s] = mainLevel.score(); 282 | } 283 | 284 | var draw = function () { 285 | context.save(); 286 | context.translate ( getScreenShake(), 0 ); 287 | 288 | context.fillStyle = gfx_bgColor; 289 | context.fillRect ( 0, 0, canvas.width, canvas.height ); 290 | 291 | mainLevel.draw ( context ); 292 | 293 | context.restore(); 294 | } 295 | 296 | var update = function () { 297 | for ( var i = 0; i < gameSpeed; i++ ) 298 | mainLevel.update ( (pause && !_gameOver) / fps ); 299 | } 300 | 301 | var Level = function () { 302 | this.cannonAngle = -Math.PI / 4; 303 | this.targetAngle = -Math.PI / 4; 304 | this.cannonHP = cannonMaxHP; 305 | this.shootReload = 1; 306 | this.repairReload = 1; 307 | 308 | // Enemy format: [ x, y, speed ] 309 | this.enemies = new Array(); 310 | 311 | this.controller = 0; 312 | 313 | this.time = 0; 314 | this.kills = 0; 315 | this.shots = 0; 316 | this.repairs = 0; 317 | 318 | this.cannonRecoil = 0; 319 | 320 | this.marked = 0; 321 | 322 | this.lastSpawn = -1; 323 | this.lastController = -1; 324 | 325 | // Projectile format: [ x, y, speedx, speedy ] 326 | this.projectiles = new Array(); 327 | 328 | this.spawner = spawners[0]; 329 | 330 | this.applyController = function ( c ) { 331 | var sand = createSandbox ( "controller = {" + c + "}", Object.create(null), { window: {}, document: {} } ); 332 | sand(); 333 | for ( var v in controllerContext ) controller[v] = controllerContext[v]; 334 | this.controller = controller; 335 | 336 | this.controller.setup(); 337 | } 338 | 339 | this.draw = function ( context ) { 340 | var R = cannonShootSpeed * cannonShootSpeed / g; 341 | var r = cannonShootSpeed * cannonShootSpeed / g * Math.sin ( 2 * cannonMinAngle ); 342 | var col = gradientCol ( gfx_lvlColor_alive, gfx_lvlColor_dead, this.cannonHP / cannonMaxHP ); 343 | 344 | context.fillStyle = gfx_rangeColor; 345 | context.beginPath(); 346 | context.arc ( cannonX + cannonR + cannonL*1.414, levelY, R, Math.PI/2, Math.PI/4 ); 347 | context.fill(); 348 | 349 | context.fillStyle = gfx_bgColor; 350 | context.beginPath(); 351 | context.arc ( cannonX + cannonR + cannonL*1.414, levelY, r, Math.PI/2, Math.PI/4 ); 352 | context.fill(); 353 | 354 | context.beginPath(); 355 | context.moveTo ( 0, levelY ); 356 | 357 | // Cannon base 358 | context.lineTo ( 20, levelY ); 359 | context.arc ( cannonX + cannonR, levelY, cannonR, Math.PI, 0 ); 360 | 361 | // Ground enemies 362 | for ( var i = 0; i < this.enemies.length; i++ ) { 363 | if ( this.enemies[i][1] == 0 ) { 364 | if ( i == 0 || this.enemies[i][0] - this.enemies[i-1][0] > 2*gfx_enemyR || this.enemies[i-1][1] != this.enemies[i][1] ) 365 | context.lineTo ( this.enemies[i][0] - gfx_enemyR, levelY ); 366 | 367 | context.lineTo ( this.enemies[i][0], levelY - gfx_enemyH ); 368 | 369 | if ( i < this.enemies.length - 1 && this.enemies[i+1][0] - this.enemies[i][0] < 2*gfx_enemyR && this.enemies[i+1][1] == this.enemies[i][1] ) { 370 | var x = 0.5 * ( this.enemies[i][0] + this.enemies[i+1][0] ); 371 | var y = gfx_enemyH - 2 * ( x - this.enemies[i][0] ); 372 | context.lineTo ( x, levelY - y ); 373 | } 374 | else 375 | context.lineTo ( this.enemies[i][0] + gfx_enemyR, levelY ); 376 | } 377 | } 378 | 379 | context.lineTo ( canvas.width, levelY ); 380 | 381 | // Cannon 382 | context.moveTo ( cannonX + cannonR + cannonR * Math.cos ( this.cannonAngle ), levelY + cannonR * Math.sin ( this.cannonAngle ) ); 383 | context.lineTo ( cannonX + cannonR + (cannonR + cannonL - this.cannonRecoil) * Math.cos ( this.cannonAngle ), levelY + (cannonR + cannonL - this.cannonRecoil) * Math.sin ( this.cannonAngle ) ); 384 | 385 | context.lineWidth = gfx_linew; 386 | context.strokeStyle = col; 387 | context.fillStyle = col; 388 | 389 | context.stroke(); 390 | 391 | // Air enemies 392 | for ( var i = 0; i < this.enemies.length; i++ ) { 393 | if ( this.enemies[i][1] > 0 ) { 394 | context.beginPath(); 395 | context.moveTo ( this.enemies[i][0] - gfx_enemyR, levelY - this.enemies[i][1] ); 396 | context.lineTo ( this.enemies[i][0] + gfx_enemyR, levelY - this.enemies[i][1] - gfx_enemyH / 2 ); 397 | context.lineTo ( this.enemies[i][0] + gfx_enemyR, levelY - this.enemies[i][1] + gfx_enemyH / 2 ); 398 | context.closePath(); 399 | context.stroke(); 400 | } 401 | } 402 | 403 | // Marked enemy 404 | if ( this.marked ) { 405 | context.beginPath(); 406 | context.arc ( this.marked[0], levelY - gfx_enemyH - this.marked[1] - 8, 3, 0, 2*Math.PI ); 407 | context.fill(); 408 | } 409 | 410 | // Projectiles 411 | for ( var i = 0; i < this.projectiles.length; i++ ) { 412 | context.beginPath(); 413 | context.arc ( this.projectiles[i][0], this.projectiles[i][1], gfx_linew * 0.75, 0, 2 * Math.PI ); 414 | context.fill(); 415 | } 416 | 417 | // Cannon HP 418 | context.beginPath(); 419 | context.moveTo ( cannonX + 4, levelY - cannonR - 10 ); 420 | context.lineTo ( cannonX + 4 + (2 * cannonR - 8) * this.cannonHP / cannonMaxHP, levelY - cannonR - 10 ); 421 | context.stroke(); 422 | 423 | // HUD 424 | context.textAlign = "left"; 425 | context.textBaseline = "alphabetic"; 426 | context.font = "20px Consolas"; 427 | 428 | context.fillText ( "Time: " + Math.floor ( this.time ), 10, 23 ); 429 | context.fillText ( "HP: " + this.cannonHP + "/" + cannonMaxHP, 10, 43 ); 430 | context.fillText ( "Kills: " + this.kills, 10, 63 ); 431 | 432 | var accuracy = Math.round(this.kills / this.shots * 100) 433 | context.fillText ( "Shots: " + this.shots + " (" + (this.shots != 0 ? accuracy + "%" : "-") + ")", 10, 83 ); 434 | context.fillText ( "Repairs: " + this.repairs, 10, 103 ); 435 | 436 | context.strokeRect ( canvas.width - 100, 17, 90, 10 ); 437 | context.fillRect ( canvas.width - 100, 17, 90 * Math.min (this.shootReload, this.repairReload), 10 ); 438 | context.fillText ( "Ready: ", canvas.width - 175, 28 ); 439 | 440 | context.fillText ( "TEST: " + this.spawner.name, canvas.width - 175, 48 ); 441 | context.fillText ( "SCORE: " + this.score(), canvas.width - 175, 68 ); 442 | context.fillText ( "BEST: " + ( localStorage["hiscore_" + this.spawner.id] ? localStorage["hiscore_" + this.spawner.id] : '-' ), canvas.width - 175, 88 ); 443 | 444 | context.textAlign = "center"; 445 | context.textBaseline = "middle"; 446 | context.font = "30px Consolas"; 447 | 448 | if ( !pause && !_gameOver ) { 449 | context.fillText ( "--- PAUSED ---", canvas.width / 2, canvas.height / 2 ); 450 | context.font = "15px Consolas"; 451 | context.fillText ( "press apply or play to unpause", canvas.width / 2, canvas.height / 2 + 20 ); 452 | } 453 | else if ( _gameOver ) { 454 | context.fillText ( "--- GAME OVER ---", canvas.width / 2, canvas.height / 2 ); 455 | context.font = "15px Consolas"; 456 | context.fillText ( "press apply to restart", canvas.width / 2, canvas.height / 2 + 20 ); 457 | } 458 | } 459 | 460 | this.killEnemy = function ( i ) { 461 | if ( this.enemies[i] == this.marked ) 462 | this.marked = 0; 463 | 464 | this.enemies.splice ( i, 1 ); 465 | } 466 | 467 | this.damage = function () { 468 | this.cannonHP--; 469 | screenShake(); 470 | if ( this.cannonHP <= 0 ) { 471 | this.cannonHP = 0; 472 | gameOver(); 473 | } 474 | } 475 | 476 | this.update = function ( t ) { 477 | if ( t == 0 ) return; 478 | 479 | this.time += t; 480 | 481 | // Adjusts cannon position 482 | var d = cannonSpeed * t; 483 | if ( Math.abs(this.targetAngle - this.cannonAngle) < d ) 484 | this.cannonAngle = this.targetAngle; 485 | else 486 | this.cannonAngle += Math.sign ( this.targetAngle - this.cannonAngle ) * d; 487 | 488 | // Projectile movement and collision checks 489 | for ( var i = 0; i < this.projectiles.length; i++ ) { 490 | this.projectiles[i][0] += this.projectiles[i][2] * t; 491 | this.projectiles[i][1] += this.projectiles[i][3] * t + 0.5 * g * t * t; 492 | this.projectiles[i][3] += g * t; 493 | 494 | var hit = 0; 495 | 496 | // Check collisions with enemies 497 | for ( var j = 0; j < this.enemies.length; j++ ) { 498 | if ( Math.abs ( levelY - this.enemies[j][1] - this.projectiles[i][1] ) < 4 && Math.abs ( this.enemies[j][0] - this.projectiles[i][0] ) <= boomRange ) { 499 | this.killEnemy ( j ); j--; 500 | this.kills++; 501 | hit = 1; 502 | } 503 | } 504 | 505 | // Check if hit something or the ground 506 | if ( hit || this.projectiles[i][1] >= levelY ) { 507 | this.projectiles.splice ( i, 1 ); 508 | i--; 509 | } 510 | } 511 | 512 | // Enemy movement 513 | for ( var i = 0; i < this.enemies.length; i++ ) { 514 | this.enemies[i][0] -= this.enemies[i][2] * t; 515 | 516 | if ( this.enemies[i][1] > 0 && this.enemies[i][0] < 150 ) 517 | this.enemies[i][1] -= this.enemies[i][1] * 0.5 * t; 518 | 519 | if ( this.enemies[i][0] - 10 < 20 + 2 * cannonR ) { 520 | this.killEnemy(i); 521 | this.damage(); 522 | i--; 523 | } 524 | } 525 | 526 | // Enemy spawning 527 | if ( this.spawner && this.spawner.condition( this ) ) { 528 | this.lastSpawn = this.time; 529 | this.enemies.push ( this.spawner.spawn ( this ) ); 530 | } 531 | 532 | // Sorts enemy: first along x, then along y 533 | this.enemies.sort ( function(a,b) { if ( a[0] == b[0] ) return a[1] - b[1]; else return a[0] - b[0]; } ); 534 | 535 | // Reload 536 | this.shootReload += shootReloadSpeed * t; 537 | if ( this.shootReload >= 1 ) this.shootReload = 1; 538 | this.repairReload += repairReloadSpeed * t; 539 | if ( this.repairReload >= 1 ) this.repairReload = 1; 540 | 541 | // Misc 542 | this.cannonRecoil -= this.cannonRecoil * t * 5; 543 | 544 | // Controller 545 | if ( this.controller && this.time - this.lastController > controllerT ) { 546 | this.controller.update(); 547 | } 548 | } 549 | 550 | this.shoot = function () { 551 | if ( Math.min( this.shootReload, this.repairReload ) >= 1 ) { 552 | this.projectiles.push ( [ 20 + cannonR + (cannonR + cannonL) * Math.cos ( this.cannonAngle ), 553 | levelY + (cannonR + cannonL) * Math.sin ( this.cannonAngle ), 554 | cannonShootSpeed * Math.cos ( this.cannonAngle ), 555 | cannonShootSpeed * Math.sin ( this.cannonAngle ) 556 | ] ); 557 | this.shots++; 558 | this.shootReload = 0; 559 | this.cannonRecoil = cannonL * 0.5; 560 | return 1; 561 | } 562 | 563 | else return 0; 564 | } 565 | 566 | this.repair = function () { 567 | if ( Math.min( this.shootReload, this.repairReload ) >= 1 ) { 568 | this.cannonHP++; 569 | if ( this.cannonHP > cannonMaxHP ) this.cannonHP = cannonMaxHP; 570 | this.repairReload = 0; 571 | this.repairs++; 572 | return 1; 573 | } 574 | else return 0; 575 | } 576 | 577 | this.reset = function () { 578 | this.time = 0; 579 | this.shots = 0; 580 | this.kills = 0; 581 | this.repairs = 0; 582 | this.enemies.splice ( 0, this.enemies.length ); 583 | this.projectiles.splice ( 0, this.projectiles.length ); 584 | this.cannonHP = cannonMaxHP; 585 | this.lastSpawn = -1; 586 | this.lastController = -1; 587 | this.cannonAngle = -Math.PI / 4; 588 | this.targetAngle = -Math.PI / 4; 589 | this.shootReload = 1; 590 | this.repairReload = 1; 591 | this.marked = 0; 592 | } 593 | 594 | this.score = function () { 595 | var t = Math.floor(this.time); 596 | var k = this.kills * 5; 597 | var a = k != 0 ? this.kills / this.shots * 20 : 0; 598 | var r = this.repairs; 599 | 600 | return Math.round ( t + k + a - r ); 601 | } 602 | } 603 | 604 | var mainLevel = new Level(); 605 | 606 | var controllerContext = { 607 | shoot: function () { return mainLevel.shoot(); }, 608 | repair: function () { if ( mainLevel.cannonHP < cannonMaxHP ) return mainLevel.repair(); else return 0; }, 609 | setCannonAngle: function ( t ) { 610 | if ( t >= cannonMinAngle && t <= cannonMaxAngle ) 611 | mainLevel.targetAngle = -t; 612 | else if ( t < -cannonMinAngle ) { 613 | this.log ( "WARNING: you're trying to set an angle lower than the minimum valid angle" ); 614 | mainLevel.targetAngle = -cannonMinAngle; 615 | } 616 | else if ( t > cannonMaxAngle ) { 617 | this.log ( "WARNING: you're trying to set an angle greater than the maximum valid angle" ); 618 | mainLevel.targetAngle = -cannonMaxAngle; 619 | } 620 | }, 621 | getCannonAngle: function () { return -mainLevel.cannonAngle; }, 622 | getEnemies: function () { 623 | result = new Array(); 624 | for ( var i = 0; i < mainLevel.enemies.length; i++ ) 625 | result.push ( [ mainLevel.enemies[i][0] - cannonX, mainLevel.enemies[i][1] ] ); 626 | return result; 627 | }, 628 | ready: function () { return Math.min ( mainLevel.shootReload, mainLevel.repairReload ) >= 1; }, 629 | hp: function () { return mainLevel.cannonHP; }, 630 | mark: function ( i ) { mainLevel.marked = mainLevel.enemies[i]; }, 631 | unmark: function ( i ) { mainLevel.marked = 0; }, 632 | log: function ( s ) { console.log(s); } 633 | } 634 | 635 | var createSandbox = function ( code, that, local ) { 636 | var params = []; var args = []; 637 | for ( var p in local ) { 638 | if ( local.hasOwnProperty(p) ) { 639 | params.push(p); 640 | args.push(local[p]); 641 | } 642 | } 643 | 644 | var context = Array.prototype.concat.call ( that, params, code ); 645 | var sandbox = new ( Function.prototype.bind.apply ( Function, context) ); 646 | context = Array.prototype.concat.call ( that, args ); 647 | 648 | return Function.prototype.bind.apply ( sandbox, context ); 649 | } 650 | -------------------------------------------------------------------------------- /javascript.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | (function(mod) { 5 | if (typeof exports == "object" && typeof module == "object") // CommonJS 6 | mod(require("../../lib/codemirror")); 7 | else if (typeof define == "function" && define.amd) // AMD 8 | define(["../../lib/codemirror"], mod); 9 | else // Plain browser env 10 | mod(CodeMirror); 11 | })(function(CodeMirror) { 12 | "use strict"; 13 | 14 | function expressionAllowed(stream, state, backUp) { 15 | return /^(?:operator|sof|keyword c|case|new|[\[{}\(,;:]|=>)$/.test(state.lastType) || 16 | (state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0)))) 17 | } 18 | 19 | CodeMirror.defineMode("javascript", function(config, parserConfig) { 20 | var indentUnit = config.indentUnit; 21 | var statementIndent = parserConfig.statementIndent; 22 | var jsonldMode = parserConfig.jsonld; 23 | var jsonMode = parserConfig.json || jsonldMode; 24 | var isTS = parserConfig.typescript; 25 | var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/; 26 | 27 | // Tokenizer 28 | 29 | var keywords = function(){ 30 | function kw(type) {return {type: type, style: "keyword"};} 31 | var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"); 32 | var operator = kw("operator"), atom = {type: "atom", style: "atom"}; 33 | 34 | var jsKeywords = { 35 | "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B, 36 | "return": C, "break": C, "continue": C, "new": kw("new"), "delete": C, "throw": C, "debugger": C, 37 | "var": kw("var"), "const": kw("var"), "let": kw("var"), 38 | "function": kw("function"), "catch": kw("catch"), 39 | "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"), 40 | "in": operator, "typeof": operator, "instanceof": operator, 41 | "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom, 42 | "this": kw("this"), "class": kw("class"), "super": kw("atom"), 43 | "yield": C, "export": kw("export"), "import": kw("import"), "extends": C, 44 | "await": C, "async": kw("async") 45 | }; 46 | 47 | // Extend the 'normal' keywords with the TypeScript language extensions 48 | if (isTS) { 49 | var type = {type: "variable", style: "variable-3"}; 50 | var tsKeywords = { 51 | // object-like things 52 | "interface": kw("class"), 53 | "implements": C, 54 | "namespace": C, 55 | "module": kw("module"), 56 | "enum": kw("module"), 57 | "type": kw("type"), 58 | 59 | // scope modifiers 60 | "public": kw("modifier"), 61 | "private": kw("modifier"), 62 | "protected": kw("modifier"), 63 | "abstract": kw("modifier"), 64 | 65 | // operators 66 | "as": operator, 67 | 68 | // types 69 | "string": type, "number": type, "boolean": type, "any": type 70 | }; 71 | 72 | for (var attr in tsKeywords) { 73 | jsKeywords[attr] = tsKeywords[attr]; 74 | } 75 | } 76 | 77 | return jsKeywords; 78 | }(); 79 | 80 | var isOperatorChar = /[+\-*&%=<>!?|~^]/; 81 | var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/; 82 | 83 | function readRegexp(stream) { 84 | var escaped = false, next, inSet = false; 85 | while ((next = stream.next()) != null) { 86 | if (!escaped) { 87 | if (next == "/" && !inSet) return; 88 | if (next == "[") inSet = true; 89 | else if (inSet && next == "]") inSet = false; 90 | } 91 | escaped = !escaped && next == "\\"; 92 | } 93 | } 94 | 95 | // Used as scratch variables to communicate multiple values without 96 | // consing up tons of objects. 97 | var type, content; 98 | function ret(tp, style, cont) { 99 | type = tp; content = cont; 100 | return style; 101 | } 102 | function tokenBase(stream, state) { 103 | var ch = stream.next(); 104 | if (ch == '"' || ch == "'") { 105 | state.tokenize = tokenString(ch); 106 | return state.tokenize(stream, state); 107 | } else if (ch == "." && stream.match(/^\d+(?:[eE][+\-]?\d+)?/)) { 108 | return ret("number", "number"); 109 | } else if (ch == "." && stream.match("..")) { 110 | return ret("spread", "meta"); 111 | } else if (/[\[\]{}\(\),;\:\.]/.test(ch)) { 112 | return ret(ch); 113 | } else if (ch == "=" && stream.eat(">")) { 114 | return ret("=>", "operator"); 115 | } else if (ch == "0" && stream.eat(/x/i)) { 116 | stream.eatWhile(/[\da-f]/i); 117 | return ret("number", "number"); 118 | } else if (ch == "0" && stream.eat(/o/i)) { 119 | stream.eatWhile(/[0-7]/i); 120 | return ret("number", "number"); 121 | } else if (ch == "0" && stream.eat(/b/i)) { 122 | stream.eatWhile(/[01]/i); 123 | return ret("number", "number"); 124 | } else if (/\d/.test(ch)) { 125 | stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/); 126 | return ret("number", "number"); 127 | } else if (ch == "/") { 128 | if (stream.eat("*")) { 129 | state.tokenize = tokenComment; 130 | return tokenComment(stream, state); 131 | } else if (stream.eat("/")) { 132 | stream.skipToEnd(); 133 | return ret("comment", "comment"); 134 | } else if (expressionAllowed(stream, state, 1)) { 135 | readRegexp(stream); 136 | stream.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/); 137 | return ret("regexp", "string-2"); 138 | } else { 139 | stream.eatWhile(isOperatorChar); 140 | return ret("operator", "operator", stream.current()); 141 | } 142 | } else if (ch == "`") { 143 | state.tokenize = tokenQuasi; 144 | return tokenQuasi(stream, state); 145 | } else if (ch == "#") { 146 | stream.skipToEnd(); 147 | return ret("error", "error"); 148 | } else if (isOperatorChar.test(ch)) { 149 | stream.eatWhile(isOperatorChar); 150 | return ret("operator", "operator", stream.current()); 151 | } else if (wordRE.test(ch)) { 152 | stream.eatWhile(wordRE); 153 | var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word]; 154 | return (known && state.lastType != ".") ? ret(known.type, known.style, word) : 155 | ret("variable", "variable", word); 156 | } 157 | } 158 | 159 | function tokenString(quote) { 160 | return function(stream, state) { 161 | var escaped = false, next; 162 | if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){ 163 | state.tokenize = tokenBase; 164 | return ret("jsonld-keyword", "meta"); 165 | } 166 | while ((next = stream.next()) != null) { 167 | if (next == quote && !escaped) break; 168 | escaped = !escaped && next == "\\"; 169 | } 170 | if (!escaped) state.tokenize = tokenBase; 171 | return ret("string", "string"); 172 | }; 173 | } 174 | 175 | function tokenComment(stream, state) { 176 | var maybeEnd = false, ch; 177 | while (ch = stream.next()) { 178 | if (ch == "/" && maybeEnd) { 179 | state.tokenize = tokenBase; 180 | break; 181 | } 182 | maybeEnd = (ch == "*"); 183 | } 184 | return ret("comment", "comment"); 185 | } 186 | 187 | function tokenQuasi(stream, state) { 188 | var escaped = false, next; 189 | while ((next = stream.next()) != null) { 190 | if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) { 191 | state.tokenize = tokenBase; 192 | break; 193 | } 194 | escaped = !escaped && next == "\\"; 195 | } 196 | return ret("quasi", "string-2", stream.current()); 197 | } 198 | 199 | var brackets = "([{}])"; 200 | // This is a crude lookahead trick to try and notice that we're 201 | // parsing the argument patterns for a fat-arrow function before we 202 | // actually hit the arrow token. It only works if the arrow is on 203 | // the same line as the arguments and there's no strange noise 204 | // (comments) in between. Fallback is to only notice when we hit the 205 | // arrow, and not declare the arguments as locals for the arrow 206 | // body. 207 | function findFatArrow(stream, state) { 208 | if (state.fatArrowAt) state.fatArrowAt = null; 209 | var arrow = stream.string.indexOf("=>", stream.start); 210 | if (arrow < 0) return; 211 | 212 | if (isTS) { // Try to skip TypeScript return type declarations after the arguments 213 | var m = /:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(stream.string.slice(stream.start, arrow)) 214 | if (m) arrow = m.index 215 | } 216 | 217 | var depth = 0, sawSomething = false; 218 | for (var pos = arrow - 1; pos >= 0; --pos) { 219 | var ch = stream.string.charAt(pos); 220 | var bracket = brackets.indexOf(ch); 221 | if (bracket >= 0 && bracket < 3) { 222 | if (!depth) { ++pos; break; } 223 | if (--depth == 0) { if (ch == "(") sawSomething = true; break; } 224 | } else if (bracket >= 3 && bracket < 6) { 225 | ++depth; 226 | } else if (wordRE.test(ch)) { 227 | sawSomething = true; 228 | } else if (/["'\/]/.test(ch)) { 229 | return; 230 | } else if (sawSomething && !depth) { 231 | ++pos; 232 | break; 233 | } 234 | } 235 | if (sawSomething && !depth) state.fatArrowAt = pos; 236 | } 237 | 238 | // Parser 239 | 240 | var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true, "jsonld-keyword": true}; 241 | 242 | function JSLexical(indented, column, type, align, prev, info) { 243 | this.indented = indented; 244 | this.column = column; 245 | this.type = type; 246 | this.prev = prev; 247 | this.info = info; 248 | if (align != null) this.align = align; 249 | } 250 | 251 | function inScope(state, varname) { 252 | for (var v = state.localVars; v; v = v.next) 253 | if (v.name == varname) return true; 254 | for (var cx = state.context; cx; cx = cx.prev) { 255 | for (var v = cx.vars; v; v = v.next) 256 | if (v.name == varname) return true; 257 | } 258 | } 259 | 260 | function parseJS(state, style, type, content, stream) { 261 | var cc = state.cc; 262 | // Communicate our context to the combinators. 263 | // (Less wasteful than consing up a hundred closures on every call.) 264 | cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style; 265 | 266 | if (!state.lexical.hasOwnProperty("align")) 267 | state.lexical.align = true; 268 | 269 | while(true) { 270 | var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement; 271 | if (combinator(type, content)) { 272 | while(cc.length && cc[cc.length - 1].lex) 273 | cc.pop()(); 274 | if (cx.marked) return cx.marked; 275 | if (type == "variable" && inScope(state, content)) return "variable-2"; 276 | return style; 277 | } 278 | } 279 | } 280 | 281 | // Combinator utils 282 | 283 | var cx = {state: null, column: null, marked: null, cc: null}; 284 | function pass() { 285 | for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]); 286 | } 287 | function cont() { 288 | pass.apply(null, arguments); 289 | return true; 290 | } 291 | function register(varname) { 292 | function inList(list) { 293 | for (var v = list; v; v = v.next) 294 | if (v.name == varname) return true; 295 | return false; 296 | } 297 | var state = cx.state; 298 | cx.marked = "def"; 299 | if (state.context) { 300 | if (inList(state.localVars)) return; 301 | state.localVars = {name: varname, next: state.localVars}; 302 | } else { 303 | if (inList(state.globalVars)) return; 304 | if (parserConfig.globalVars) 305 | state.globalVars = {name: varname, next: state.globalVars}; 306 | } 307 | } 308 | 309 | // Combinators 310 | 311 | var defaultVars = {name: "this", next: {name: "arguments"}}; 312 | function pushcontext() { 313 | cx.state.context = {prev: cx.state.context, vars: cx.state.localVars}; 314 | cx.state.localVars = defaultVars; 315 | } 316 | function popcontext() { 317 | cx.state.localVars = cx.state.context.vars; 318 | cx.state.context = cx.state.context.prev; 319 | } 320 | function pushlex(type, info) { 321 | var result = function() { 322 | var state = cx.state, indent = state.indented; 323 | if (state.lexical.type == "stat") indent = state.lexical.indented; 324 | else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev) 325 | indent = outer.indented; 326 | state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info); 327 | }; 328 | result.lex = true; 329 | return result; 330 | } 331 | function poplex() { 332 | var state = cx.state; 333 | if (state.lexical.prev) { 334 | if (state.lexical.type == ")") 335 | state.indented = state.lexical.indented; 336 | state.lexical = state.lexical.prev; 337 | } 338 | } 339 | poplex.lex = true; 340 | 341 | function expect(wanted) { 342 | function exp(type) { 343 | if (type == wanted) return cont(); 344 | else if (wanted == ";") return pass(); 345 | else return cont(exp); 346 | }; 347 | return exp; 348 | } 349 | 350 | function statement(type, value) { 351 | if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex); 352 | if (type == "keyword a") return cont(pushlex("form"), parenExpr, statement, poplex); 353 | if (type == "keyword b") return cont(pushlex("form"), statement, poplex); 354 | if (type == "{") return cont(pushlex("}"), block, poplex); 355 | if (type == ";") return cont(); 356 | if (type == "if") { 357 | if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex) 358 | cx.state.cc.pop()(); 359 | return cont(pushlex("form"), parenExpr, statement, poplex, maybeelse); 360 | } 361 | if (type == "function") return cont(functiondef); 362 | if (type == "for") return cont(pushlex("form"), forspec, statement, poplex); 363 | if (type == "variable") return cont(pushlex("stat"), maybelabel); 364 | if (type == "switch") return cont(pushlex("form"), parenExpr, pushlex("}", "switch"), expect("{"), 365 | block, poplex, poplex); 366 | if (type == "case") return cont(expression, expect(":")); 367 | if (type == "default") return cont(expect(":")); 368 | if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), 369 | statement, poplex, popcontext); 370 | if (type == "class") return cont(pushlex("form"), className, poplex); 371 | if (type == "export") return cont(pushlex("stat"), afterExport, poplex); 372 | if (type == "import") return cont(pushlex("stat"), afterImport, poplex); 373 | if (type == "module") return cont(pushlex("form"), pattern, pushlex("}"), expect("{"), block, poplex, poplex) 374 | if (type == "type") return cont(typeexpr, expect("operator"), typeexpr, expect(";")); 375 | if (type == "async") return cont(statement) 376 | return pass(pushlex("stat"), expression, expect(";"), poplex); 377 | } 378 | function expression(type) { 379 | return expressionInner(type, false); 380 | } 381 | function expressionNoComma(type) { 382 | return expressionInner(type, true); 383 | } 384 | function parenExpr(type) { 385 | if (type != "(") return pass() 386 | return cont(pushlex(")"), expression, expect(")"), poplex) 387 | } 388 | function expressionInner(type, noComma) { 389 | if (cx.state.fatArrowAt == cx.stream.start) { 390 | var body = noComma ? arrowBodyNoComma : arrowBody; 391 | if (type == "(") return cont(pushcontext, pushlex(")"), commasep(pattern, ")"), poplex, expect("=>"), body, popcontext); 392 | else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext); 393 | } 394 | 395 | var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma; 396 | if (atomicTypes.hasOwnProperty(type)) return cont(maybeop); 397 | if (type == "function") return cont(functiondef, maybeop); 398 | if (type == "class") return cont(pushlex("form"), classExpression, poplex); 399 | if (type == "keyword c" || type == "async") return cont(noComma ? maybeexpressionNoComma : maybeexpression); 400 | if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop); 401 | if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression); 402 | if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop); 403 | if (type == "{") return contCommasep(objprop, "}", null, maybeop); 404 | if (type == "quasi") return pass(quasi, maybeop); 405 | if (type == "new") return cont(maybeTarget(noComma)); 406 | return cont(); 407 | } 408 | function maybeexpression(type) { 409 | if (type.match(/[;\}\)\],]/)) return pass(); 410 | return pass(expression); 411 | } 412 | function maybeexpressionNoComma(type) { 413 | if (type.match(/[;\}\)\],]/)) return pass(); 414 | return pass(expressionNoComma); 415 | } 416 | 417 | function maybeoperatorComma(type, value) { 418 | if (type == ",") return cont(expression); 419 | return maybeoperatorNoComma(type, value, false); 420 | } 421 | function maybeoperatorNoComma(type, value, noComma) { 422 | var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma; 423 | var expr = noComma == false ? expression : expressionNoComma; 424 | if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext); 425 | if (type == "operator") { 426 | if (/\+\+|--/.test(value)) return cont(me); 427 | if (value == "?") return cont(expression, expect(":"), expr); 428 | return cont(expr); 429 | } 430 | if (type == "quasi") { return pass(quasi, me); } 431 | if (type == ";") return; 432 | if (type == "(") return contCommasep(expressionNoComma, ")", "call", me); 433 | if (type == ".") return cont(property, me); 434 | if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me); 435 | } 436 | function quasi(type, value) { 437 | if (type != "quasi") return pass(); 438 | if (value.slice(value.length - 2) != "${") return cont(quasi); 439 | return cont(expression, continueQuasi); 440 | } 441 | function continueQuasi(type) { 442 | if (type == "}") { 443 | cx.marked = "string-2"; 444 | cx.state.tokenize = tokenQuasi; 445 | return cont(quasi); 446 | } 447 | } 448 | function arrowBody(type) { 449 | findFatArrow(cx.stream, cx.state); 450 | return pass(type == "{" ? statement : expression); 451 | } 452 | function arrowBodyNoComma(type) { 453 | findFatArrow(cx.stream, cx.state); 454 | return pass(type == "{" ? statement : expressionNoComma); 455 | } 456 | function maybeTarget(noComma) { 457 | return function(type) { 458 | if (type == ".") return cont(noComma ? targetNoComma : target); 459 | else return pass(noComma ? expressionNoComma : expression); 460 | }; 461 | } 462 | function target(_, value) { 463 | if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorComma); } 464 | } 465 | function targetNoComma(_, value) { 466 | if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorNoComma); } 467 | } 468 | function maybelabel(type) { 469 | if (type == ":") return cont(poplex, statement); 470 | return pass(maybeoperatorComma, expect(";"), poplex); 471 | } 472 | function property(type) { 473 | if (type == "variable") {cx.marked = "property"; return cont();} 474 | } 475 | function objprop(type, value) { 476 | if (type == "async") { 477 | cx.marked = "property"; 478 | return cont(objprop); 479 | } else if (type == "variable" || cx.style == "keyword") { 480 | cx.marked = "property"; 481 | if (value == "get" || value == "set") return cont(getterSetter); 482 | return cont(afterprop); 483 | } else if (type == "number" || type == "string") { 484 | cx.marked = jsonldMode ? "property" : (cx.style + " property"); 485 | return cont(afterprop); 486 | } else if (type == "jsonld-keyword") { 487 | return cont(afterprop); 488 | } else if (type == "modifier") { 489 | return cont(objprop) 490 | } else if (type == "[") { 491 | return cont(expression, expect("]"), afterprop); 492 | } else if (type == "spread") { 493 | return cont(expression); 494 | } else if (type == ":") { 495 | return pass(afterprop) 496 | } 497 | } 498 | function getterSetter(type) { 499 | if (type != "variable") return pass(afterprop); 500 | cx.marked = "property"; 501 | return cont(functiondef); 502 | } 503 | function afterprop(type) { 504 | if (type == ":") return cont(expressionNoComma); 505 | if (type == "(") return pass(functiondef); 506 | } 507 | function commasep(what, end) { 508 | function proceed(type, value) { 509 | if (type == ",") { 510 | var lex = cx.state.lexical; 511 | if (lex.info == "call") lex.pos = (lex.pos || 0) + 1; 512 | return cont(function(type, value) { 513 | if (type == end || value == end) return pass() 514 | return pass(what) 515 | }, proceed); 516 | } 517 | if (type == end || value == end) return cont(); 518 | return cont(expect(end)); 519 | } 520 | return function(type, value) { 521 | if (type == end || value == end) return cont(); 522 | return pass(what, proceed); 523 | }; 524 | } 525 | function contCommasep(what, end, info) { 526 | for (var i = 3; i < arguments.length; i++) 527 | cx.cc.push(arguments[i]); 528 | return cont(pushlex(end, info), commasep(what, end), poplex); 529 | } 530 | function block(type) { 531 | if (type == "}") return cont(); 532 | return pass(statement, block); 533 | } 534 | function maybetype(type, value) { 535 | if (isTS) { 536 | if (type == ":") return cont(typeexpr); 537 | if (value == "?") return cont(maybetype); 538 | } 539 | } 540 | function typeexpr(type) { 541 | if (type == "variable") {cx.marked = "variable-3"; return cont(afterType);} 542 | if (type == "{") return cont(commasep(typeprop, "}")) 543 | if (type == "(") return cont(commasep(typearg, ")"), maybeReturnType) 544 | } 545 | function maybeReturnType(type) { 546 | if (type == "=>") return cont(typeexpr) 547 | } 548 | function typeprop(type) { 549 | if (type == "variable" || cx.style == "keyword") { 550 | cx.marked = "property" 551 | return cont(typeprop) 552 | } else if (type == ":") { 553 | return cont(typeexpr) 554 | } 555 | } 556 | function typearg(type) { 557 | if (type == "variable") return cont(typearg) 558 | else if (type == ":") return cont(typeexpr) 559 | } 560 | function afterType(type, value) { 561 | if (value == "<") return cont(commasep(typeexpr, ">"), afterType) 562 | if (type == "[") return cont(expect("]"), afterType) 563 | } 564 | function vardef() { 565 | return pass(pattern, maybetype, maybeAssign, vardefCont); 566 | } 567 | function pattern(type, value) { 568 | if (type == "modifier") return cont(pattern) 569 | if (type == "variable") { register(value); return cont(); } 570 | if (type == "spread") return cont(pattern); 571 | if (type == "[") return contCommasep(pattern, "]"); 572 | if (type == "{") return contCommasep(proppattern, "}"); 573 | } 574 | function proppattern(type, value) { 575 | if (type == "variable" && !cx.stream.match(/^\s*:/, false)) { 576 | register(value); 577 | return cont(maybeAssign); 578 | } 579 | if (type == "variable") cx.marked = "property"; 580 | if (type == "spread") return cont(pattern); 581 | if (type == "}") return pass(); 582 | return cont(expect(":"), pattern, maybeAssign); 583 | } 584 | function maybeAssign(_type, value) { 585 | if (value == "=") return cont(expressionNoComma); 586 | } 587 | function vardefCont(type) { 588 | if (type == ",") return cont(vardef); 589 | } 590 | function maybeelse(type, value) { 591 | if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex); 592 | } 593 | function forspec(type) { 594 | if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex); 595 | } 596 | function forspec1(type) { 597 | if (type == "var") return cont(vardef, expect(";"), forspec2); 598 | if (type == ";") return cont(forspec2); 599 | if (type == "variable") return cont(formaybeinof); 600 | return pass(expression, expect(";"), forspec2); 601 | } 602 | function formaybeinof(_type, value) { 603 | if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); } 604 | return cont(maybeoperatorComma, forspec2); 605 | } 606 | function forspec2(type, value) { 607 | if (type == ";") return cont(forspec3); 608 | if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); } 609 | return pass(expression, expect(";"), forspec3); 610 | } 611 | function forspec3(type) { 612 | if (type != ")") cont(expression); 613 | } 614 | function functiondef(type, value) { 615 | if (value == "*") {cx.marked = "keyword"; return cont(functiondef);} 616 | if (type == "variable") {register(value); return cont(functiondef);} 617 | if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, maybetype, statement, popcontext); 618 | } 619 | function funarg(type) { 620 | if (type == "spread") return cont(funarg); 621 | return pass(pattern, maybetype, maybeAssign); 622 | } 623 | function classExpression(type, value) { 624 | // Class expressions may have an optional name. 625 | if (type == "variable") return className(type, value); 626 | return classNameAfter(type, value); 627 | } 628 | function className(type, value) { 629 | if (type == "variable") {register(value); return cont(classNameAfter);} 630 | } 631 | function classNameAfter(type, value) { 632 | if (value == "extends" || value == "implements") return cont(isTS ? typeexpr : expression, classNameAfter); 633 | if (type == "{") return cont(pushlex("}"), classBody, poplex); 634 | } 635 | function classBody(type, value) { 636 | if (type == "variable" || cx.style == "keyword") { 637 | if ((value == "static" || value == "get" || value == "set" || 638 | (isTS && (value == "public" || value == "private" || value == "protected" || value == "readonly" || value == "abstract"))) && 639 | cx.stream.match(/^\s+[\w$\xa1-\uffff]/, false)) { 640 | cx.marked = "keyword"; 641 | return cont(classBody); 642 | } 643 | cx.marked = "property"; 644 | return cont(isTS ? classfield : functiondef, classBody); 645 | } 646 | if (value == "*") { 647 | cx.marked = "keyword"; 648 | return cont(classBody); 649 | } 650 | if (type == ";") return cont(classBody); 651 | if (type == "}") return cont(); 652 | } 653 | function classfield(type, value) { 654 | if (value == "?") return cont(classfield) 655 | if (type == ":") return cont(typeexpr, maybeAssign) 656 | return pass(functiondef) 657 | } 658 | function afterExport(_type, value) { 659 | if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); } 660 | if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); } 661 | return pass(statement); 662 | } 663 | function afterImport(type) { 664 | if (type == "string") return cont(); 665 | return pass(importSpec, maybeFrom); 666 | } 667 | function importSpec(type, value) { 668 | if (type == "{") return contCommasep(importSpec, "}"); 669 | if (type == "variable") register(value); 670 | if (value == "*") cx.marked = "keyword"; 671 | return cont(maybeAs); 672 | } 673 | function maybeAs(_type, value) { 674 | if (value == "as") { cx.marked = "keyword"; return cont(importSpec); } 675 | } 676 | function maybeFrom(_type, value) { 677 | if (value == "from") { cx.marked = "keyword"; return cont(expression); } 678 | } 679 | function arrayLiteral(type) { 680 | if (type == "]") return cont(); 681 | return pass(commasep(expressionNoComma, "]")); 682 | } 683 | 684 | function isContinuedStatement(state, textAfter) { 685 | return state.lastType == "operator" || state.lastType == "," || 686 | isOperatorChar.test(textAfter.charAt(0)) || 687 | /[,.]/.test(textAfter.charAt(0)); 688 | } 689 | 690 | // Interface 691 | 692 | return { 693 | startState: function(basecolumn) { 694 | var state = { 695 | tokenize: tokenBase, 696 | lastType: "sof", 697 | cc: [], 698 | lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false), 699 | localVars: parserConfig.localVars, 700 | context: parserConfig.localVars && {vars: parserConfig.localVars}, 701 | indented: basecolumn || 0 702 | }; 703 | if (parserConfig.globalVars && typeof parserConfig.globalVars == "object") 704 | state.globalVars = parserConfig.globalVars; 705 | return state; 706 | }, 707 | 708 | token: function(stream, state) { 709 | if (stream.sol()) { 710 | if (!state.lexical.hasOwnProperty("align")) 711 | state.lexical.align = false; 712 | state.indented = stream.indentation(); 713 | findFatArrow(stream, state); 714 | } 715 | if (state.tokenize != tokenComment && stream.eatSpace()) return null; 716 | var style = state.tokenize(stream, state); 717 | if (type == "comment") return style; 718 | state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type; 719 | return parseJS(state, style, type, content, stream); 720 | }, 721 | 722 | indent: function(state, textAfter) { 723 | if (state.tokenize == tokenComment) return CodeMirror.Pass; 724 | if (state.tokenize != tokenBase) return 0; 725 | var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, top 726 | // Kludge to prevent 'maybelse' from blocking lexical scope pops 727 | if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) { 728 | var c = state.cc[i]; 729 | if (c == poplex) lexical = lexical.prev; 730 | else if (c != maybeelse) break; 731 | } 732 | while ((lexical.type == "stat" || lexical.type == "form") && 733 | (firstChar == "}" || ((top = state.cc[state.cc.length - 1]) && 734 | (top == maybeoperatorComma || top == maybeoperatorNoComma) && 735 | !/^[,\.=+\-*:?[\(]/.test(textAfter)))) 736 | lexical = lexical.prev; 737 | if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat") 738 | lexical = lexical.prev; 739 | var type = lexical.type, closing = firstChar == type; 740 | 741 | if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info + 1 : 0); 742 | else if (type == "form" && firstChar == "{") return lexical.indented; 743 | else if (type == "form") return lexical.indented + indentUnit; 744 | else if (type == "stat") 745 | return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0); 746 | else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false) 747 | return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit); 748 | else if (lexical.align) return lexical.column + (closing ? 0 : 1); 749 | else return lexical.indented + (closing ? 0 : indentUnit); 750 | }, 751 | 752 | electricInput: /^\s*(?:case .*?:|default:|\{|\})$/, 753 | blockCommentStart: jsonMode ? null : "/*", 754 | blockCommentEnd: jsonMode ? null : "*/", 755 | lineComment: jsonMode ? null : "//", 756 | fold: "brace", 757 | closeBrackets: "()[]{}''\"\"``", 758 | 759 | helperType: jsonMode ? "json" : "javascript", 760 | jsonldMode: jsonldMode, 761 | jsonMode: jsonMode, 762 | 763 | expressionAllowed: expressionAllowed, 764 | skipExpression: function(state) { 765 | var top = state.cc[state.cc.length - 1] 766 | if (top == expression || top == expressionNoComma) state.cc.pop() 767 | } 768 | }; 769 | }); 770 | 771 | CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/); 772 | 773 | CodeMirror.defineMIME("text/javascript", "javascript"); 774 | CodeMirror.defineMIME("text/ecmascript", "javascript"); 775 | CodeMirror.defineMIME("application/javascript", "javascript"); 776 | CodeMirror.defineMIME("application/x-javascript", "javascript"); 777 | CodeMirror.defineMIME("application/ecmascript", "javascript"); 778 | CodeMirror.defineMIME("application/json", {name: "javascript", json: true}); 779 | CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true}); 780 | CodeMirror.defineMIME("application/ld+json", {name: "javascript", jsonld: true}); 781 | CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true }); 782 | CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true }); 783 | 784 | }); 785 | --------------------------------------------------------------------------------