├── .gitignore ├── README.md ├── docs ├── ecmarkup.css ├── ecmarkup.js └── index.html ├── package.json ├── spec.html └── spec.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | package-lock.json 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Numeric Separators 2 | 3 | ## Stage 4 4 | 5 | This is a [proposal](https://tc39.github.io/process-document/), the result of a merge between an earlier draft of itself and Christophe Porteneuve's [proposal-numeric-underscores](https://github.com/tdd/proposal-numeric-underscores), to extend the existing [_NumericLiteral_](https://tc39.github.io/ecma262/#prod-NumericLiteral) to allow a separator character between digits. 6 | 7 | ## Acknowledgements 8 | 9 | This proposal is currently championed by @samuelgoto, @rwaldron, and @leobalter. 10 | 11 | This proposal was originally developed by @samuelgoto, @ajklein, @domenic, @rwaldron and @tdd. 12 | 13 | ## Motivation 14 | 15 | This feature enables developers to make their numeric literals more readable by creating a visual separation between groups of digits. Large numeric literals are difficult for the human eye to parse quickly, especially when there are long digit repetitions. This impairs both the ability to get the correct value / order of magnitude... 16 | 17 | ```js 18 | 1000000000 // Is this a billion? a hundred millions? Ten millions? 19 | 101475938.38 // what scale is this? what power of 10? 20 | ``` 21 | 22 | ...but also fails to convey some use-case information, such as fixed-point arithmetic using integers. For instance, financial computations often work in 4- to 6-digit fixed-point arithmetics, but even storing amounts as cents is not immediately obvious without separators in literals: 23 | 24 | ```js 25 | const FEE = 12300; 26 | // is this 12,300? Or 123, because it's in cents? 27 | 28 | const AMOUNT = 1234500; 29 | // is this 1,234,500? Or cents, hence 12,345? Or financial, 4-fixed 123.45? 30 | ``` 31 | Using underscores (`_`, U+005F) as separators helps improve readability for numeric literals, both integers and floating-point (and in JS, it's all floating-point anyway): 32 | ```js 33 | 1_000_000_000 // Ah, so a billion 34 | 101_475_938.38 // And this is hundreds of millions 35 | 36 | let fee = 123_00; // $123 (12300 cents, apparently) 37 | let fee = 12_300; // $12,300 (woah, that fee!) 38 | let amount = 12345_00; // 12,345 (1234500 cents, apparently) 39 | let amount = 123_4500; // 123.45 (4-fixed financial) 40 | let amount = 1_234_500; // 1,234,500 41 | ``` 42 | 43 | Also, this works on the fractional and exponent parts, too: 44 | 45 | ```js 46 | 0.000_001 // 1 millionth 47 | 1e10_000 // 10^10000 -- granted, far less useful / in-range... 48 | ``` 49 | 50 | ## Examples 51 | 52 | (The following examples also appear in the README.md of Babel transform plugin for this proposal.) 53 | 54 | ### Regular Number Literals 55 | 56 | ```js 57 | let budget = 1_000_000_000_000; 58 | 59 | // What is the value of `budget`? It's 1 trillion! 60 | // 61 | // Let's confirm: 62 | console.log(budget === 10 ** 12); // true 63 | ``` 64 | 65 | ### Binary Literals 66 | 67 | ```js 68 | let nibbles = 0b1010_0001_1000_0101; 69 | 70 | // Is bit 7 on? It sure is! 71 | // 0b1010_0001_1000_0101 72 | // ^ 73 | // 74 | // We can double check: 75 | console.log(!!(nibbles & (1 << 7))); // true 76 | ``` 77 | 78 | ### Hex Literal 79 | 80 | ```js 81 | // Messages are sent as 24 bit values, but should be 82 | // treated as 3 distinct bytes: 83 | let message = 0xA0_B0_C0; 84 | 85 | // What's the value of the upper most byte? It's A0, or 160. 86 | // We can confirm that: 87 | let a = (message >> 16) & 0xFF; 88 | console.log(a.toString(16), a); // a0, 160 89 | 90 | // What's the value of the middle byte? It's B0, or 176. 91 | // Let's just make sure... 92 | let b = (message >> 8) & 0xFF; 93 | console.log(b.toString(16), b); // b0, 176 94 | 95 | // What's the value of the lower most byte? It's C0, or 192. 96 | // Again, let's prove that: 97 | let c = message & 0xFF; 98 | console.log(c.toString(16), b); // c0, 192 99 | ``` 100 | 101 | ### BigInt Literal 102 | 103 | Numeric Separators are also available within BigInt literals. 104 | 105 | ```js 106 | // Verifying max signed 64 bit numbers: 107 | const max = 2n ** (64n - 1n) - 1n; 108 | console.log(max === 9_223_372_036_854_775_807n); 109 | ``` 110 | 111 | It can also be used similarly to Number literals 112 | 113 | ```js 114 | let budget = 1_000_000_000_000n; 115 | 116 | // What is the value of `budget`? It's 1 trillion! 117 | // 118 | // Let's confirm: 119 | console.log(budget === BigInt(10 ** 12)); // true 120 | ``` 121 | 122 | Numeric Separators are only allowed between digits of BigInt literals, and not immediately before the BigInt `n` suffix. 123 | 124 | ```js 125 | // Valid 126 | 1_1n; 127 | 1_000n; 128 | 99999999_111111111_00000000n; 129 | 130 | // Invalid: SyntaxError! 131 | 1_n; 132 | 0_n; 133 | 1000000_n; 134 | 1_000_000_n; 135 | ``` 136 | 137 | 138 | 139 | ### Octal Literal 140 | 141 | While there isn't much of a benefit, numeric separators are available in the Octal Literal productions out of conventially being generally available in non-legacy productions. In other words, the intent for feature is to be broad available in non-legacy numeric literal types. 142 | 143 | ```js 144 | let x = 0o1234_5670; 145 | let partA = (x & 0o7777_0000) >> 12; // 3 bits per digit 146 | let partB = x & 0o0000_7777; 147 | console.log(partA.toString(8)); // 1234 148 | console.log(partB.toString(8)); // 5670 149 | ``` 150 | 151 | ## Specification 152 | 153 | You can see what the specification design looks like [here](spec.md) and a more detailed version [here](https://tc39.github.io/proposal-numeric-separator). 154 | 155 | ## Background 156 | 157 | ### Alternative Syntax 158 | 159 | Our strawnman strategy is to **start with** a more restrictive rule (i.e. disallow both idioms) and losen it upon later if needed (as opposed to starting more broadly and worrying about backwards compatibility trying to tighten it up later). 160 | 161 | In addition to that, we couldn't find good/practical evicence where (a) multiple consecutive underscores or (b) underscores before/after numbers are used effectively, so we chose to leave that addition to a later stage if needed/desired. 162 | 163 | ### Character 164 | 165 | The `_` was agreed to as part of Stage 1 acceptance. 166 | The following examples show numeric separators as they appear in other programming languages: 167 | 168 | - **`_` (Java, Python, Perl, Ruby, Rust, Julia, Ada, C#)** 169 | - `'` (C++) 170 | 171 | ## Building the spec 172 | 173 | ```sh 174 | npm i 175 | npm run build 176 | ``` 177 | 178 | ## References 179 | 180 | ### Prior art 181 | 182 | * [Java7](https://docs.oracle.com/javase/7/docs/technotes/guides/language/underscores-literals.html): multiple, only between digits. 183 | 184 | ```java 185 | float pi = 3.14_15F; 186 | long hexBytes = 0xFF_EC_DE_5E; 187 | long hexWords = 0xCAFE_F00D; 188 | long maxLong = 0x7fff_ffff_ffff_ffffL; 189 | byte nybbles = 0b0010_0101; 190 | long bytes = 0b11010010_01101001_10010100_10010010; 191 | ``` 192 | 193 | > Note that the first two examples are actually unlikely to be correct in any circumstance. 194 | Trade-offs: 195 | 196 | ```java 197 | float pi1 = 3_.1415F; // Invalid; cannot put underscores adjacent to a decimal point 198 | float pi2 = 3._1415F; // Invalid; cannot put underscores adjacent to a decimal point 199 | 200 | int x1 = _52; // This is an identifier, not a numeric literal 201 | int x2 = 5_2; // OK (decimal literal) 202 | int x3 = 52_; // Invalid; cannot put underscores at the end of a literal 203 | int x4 = 5_______2; // OK (decimal literal) 204 | 205 | int x5 = 0_x52; // Invalid; cannot put underscores in the 0x radix prefix 206 | int x6 = 0x_52; // Invalid; cannot put underscores at the beginning of a number 207 | int x7 = 0x5_2; // OK (hexadecimal literal) 208 | int x8 = 0x52_; // Invalid; cannot put underscores at the end of a number 209 | 210 | int x9 = 0_52; // OK (octal literal) 211 | int x10 = 05_2; // OK (octal literal) 212 | int x11 = 052_; // Invalid; cannot put underscores at the end of a number 213 | ``` 214 | 215 | * [C++](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3499.html): single, between digits (different separator chosen `'`). 216 | 217 | ```c++ 218 | int m = 36'000'000 // digit separators make large values more readable 219 | ``` 220 | 221 | * [Swift](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html) 222 | 223 | ```swift 224 | let m = 36_000_000 // Underscores (_) are allowed between digits for readability 225 | ``` 226 | 227 | * [Perl](http://perldoc.perl.org/perldata.html#Scalar-value-constructors): multiple, anywhere 228 | 229 | ```perl 230 | 3.14_15_92 # a very important number 231 | 4_294_967_296 # underscore for legibility 232 | 0xff # hex 233 | 0xdead_beef # more hex 234 | ``` 235 | 236 | * [Ruby](http://ruby-doc.org/core-2.3.0/doc/syntax/literals_rdoc.html#label-Numbers): single, only between digits. 237 | 238 | ```ruby 239 | 1_234 240 | ``` 241 | 242 | * [Rust](https://doc.rust-lang.org/reference.html#number-literals): multiple, anywhere. 243 | 244 | ```rust 245 | 0b1111_1111_1001_0000_i32; // type i32 246 | 1_234.0E+18f64 247 | ``` 248 | 249 | * [Julia](https://docs.julialang.org/en/release-0.4/manual/integers-and-floating-point-numbers/): single, only between digits. 250 | 251 | ```julia 252 | julia> 10_000, 0.000_000_005, 0xdead_beef, 0b1011_0010 253 | (10000,5.0e-9,0xdeadbeef,0xb2) 254 | ``` 255 | 256 | * [Ada](http://archive.adaic.com/standards/83lrm/html/lrm-02-04.html#2.4): single, only between digits. 257 | 258 | ```ada 259 | 123_456 260 | 3.14159_26 261 | 262 | ``` 263 | 264 | * [Kotlin](https://kotlinlang.org/docs/reference/basic-types.html#underscores-in-numeric-literals-since-11) 265 | 266 | ```kotlin 267 | val oneMillion = 1_000_000 268 | val creditCardNumber = 1234_5678_9012_3456L 269 | val socialSecurityNumber = 999_99_9999L 270 | val hexBytes = 0xFF_EC_DE_5E 271 | val bytes = 0b11010010_01101001_10010100_10010010 272 | ``` 273 | 274 | ### Ongoing Proposals 275 | 276 | * [Python Proposal: Underscore in Numeric Literals](https://www.python.org/dev/peps/pep-0515/#id19): single, only between digits. 277 | 278 | ```python 279 | # grouping decimal numbers by thousands 280 | amount = 10_000_000.0 281 | 282 | # grouping hexadecimal addresses by words 283 | addr = 0xCAFE_F00D 284 | 285 | # grouping bits into nibbles in a binary literal 286 | flags = 0b_0011_1111_0100_1110 287 | 288 | # same, for string conversions 289 | flags = int('0b_1111_0000', 2) 290 | ``` 291 | 292 | * [C# Proposal: Digit Separators](https://github.com/dotnet/roslyn/issues/216): multiple, only between digits. 293 | 294 | ``` 295 | int bin = 0b1001_1010_0001_0100; 296 | int hex = 0x1b_a0_44_fe; 297 | int dec = 33_554_432; 298 | int weird = 1_2__3___4____5_____6______7_______8________9; 299 | double real = 1_000.111_1e-1_000; 300 | ``` 301 | 302 | ### Related Work 303 | 304 | * [Format Specifier For Thousands Separator](https://www.python.org/dev/peps/pep-0378/) 305 | 306 | ### Implementations 307 | 308 | * [Shipping in V8 v7.5 / Chrome 75](https://v8.dev/blog/v8-release-75#numeric-separators) 309 | * [Shipping in SpiderMonkey / Firefox 70](https://bugzilla.mozilla.org/show_bug.cgi?id=1435818) 310 | * [Shipping in JavaScriptCore / Safari 13](https://bugs.webkit.org/show_bug.cgi?id=196351) 311 | -------------------------------------------------------------------------------- /docs/ecmarkup.css: -------------------------------------------------------------------------------- 1 | body { 2 | display: flex; 3 | font-size: 18px; 4 | line-height: 1.5; 5 | font-family: Cambria, Palatino Linotype, Palatino, Liberation Serif, serif; 6 | padding: 0; 7 | margin: 0; 8 | color: #111; 9 | } 10 | 11 | #spec-container { 12 | padding: 0 20px; 13 | flex-grow: 1; 14 | flex-basis: 66%; 15 | box-sizing: border-box; 16 | overflow: hidden; 17 | } 18 | 19 | body.oldtoc { 20 | margin: 0 auto; 21 | } 22 | 23 | a { 24 | text-decoration: none; 25 | color: #206ca7; 26 | } 27 | 28 | a:visited { 29 | color: #206ca7; 30 | } 31 | 32 | a:hover { 33 | text-decoration: underline; 34 | color: #239dee; 35 | } 36 | 37 | 38 | code { 39 | font-weight: bold; 40 | font-family: Consolas, Monaco, monospace; 41 | white-space: pre; 42 | } 43 | 44 | pre code { 45 | font-weight: inherit; 46 | } 47 | 48 | pre code.hljs { 49 | background-color: #fff; 50 | margin: 0; 51 | padding: 0; 52 | } 53 | 54 | ol.toc { 55 | list-style: none; 56 | padding-left: 0; 57 | } 58 | 59 | ol.toc ol.toc { 60 | padding-left: 2ex; 61 | list-style: none; 62 | } 63 | 64 | var { 65 | color: #2aa198; 66 | transition: background-color 0.25s ease; 67 | cursor: pointer; 68 | } 69 | 70 | var.referenced { 71 | background-color: #ffff33; 72 | } 73 | 74 | emu-const { 75 | font-family: sans-serif; 76 | } 77 | 78 | emu-val { 79 | font-weight: bold; 80 | } 81 | 82 | /* depth 1 */ 83 | emu-alg ol, 84 | /* depth 4 */ 85 | emu-alg ol ol ol ol, 86 | emu-alg ol.nested-thrice, 87 | emu-alg ol.nested-twice ol, 88 | emu-alg ol.nested-once ol ol { 89 | list-style-type: decimal; 90 | } 91 | 92 | /* depth 2 */ 93 | emu-alg ol ol, 94 | emu-alg ol.nested-once, 95 | /* depth 5 */ 96 | emu-alg ol ol ol ol ol, 97 | emu-alg ol.nested-four-times, 98 | emu-alg ol.nested-thrice ol, 99 | emu-alg ol.nested-twice ol ol, 100 | emu-alg ol.nested-once ol ol ol { 101 | list-style-type: lower-alpha; 102 | } 103 | 104 | /* depth 3 */ 105 | emu-alg ol ol ol, 106 | emu-alg ol.nested-twice, 107 | emu-alg ol.nested-once ol, 108 | /* depth 6 */ 109 | emu-alg ol ol ol ol ol ol, 110 | emu-alg ol.nested-lots, 111 | emu-alg ol.nested-four-times ol, 112 | emu-alg ol.nested-thrice ol ol, 113 | emu-alg ol.nested-twice ol ol ol, 114 | emu-alg ol.nested-once ol ol ol ol, 115 | /* depth 7+ */ 116 | emu-alg ol.nested-lots ol { 117 | list-style-type: lower-roman; 118 | } 119 | 120 | emu-eqn { 121 | display: block; 122 | margin-left: 4em; 123 | } 124 | 125 | emu-eqn.inline { 126 | display: inline; 127 | margin: 0; 128 | } 129 | 130 | emu-eqn div:first-child { 131 | margin-left: -2em; 132 | } 133 | 134 | emu-note { 135 | margin: 1em 0; 136 | color: #666; 137 | border-left: 5px solid #ccc; 138 | display: flex; 139 | flex-direction: row; 140 | } 141 | 142 | emu-note > span.note { 143 | flex-basis: 100px; 144 | min-width: 100px; 145 | flex-grow: 0; 146 | flex-shrink: 1; 147 | text-transform: uppercase; 148 | padding-left: 5px; 149 | } 150 | 151 | emu-note[type=editor] { 152 | border-left-color: #faa; 153 | } 154 | 155 | emu-note > div.note-contents { 156 | flex-grow: 1; 157 | flex-shrink: 1; 158 | } 159 | 160 | emu-note > div.note-contents > p:first-of-type { 161 | margin-top: 0; 162 | } 163 | 164 | emu-note > div.note-contents > p:last-of-type { 165 | margin-bottom: 0; 166 | } 167 | 168 | emu-table td code { 169 | white-space: normal; 170 | } 171 | 172 | emu-figure { 173 | display: block; 174 | } 175 | 176 | emu-example { 177 | display: block; 178 | margin: 1em 3em; 179 | } 180 | 181 | emu-example figure figcaption { 182 | margin-top: 0.5em; 183 | text-align: left; 184 | } 185 | 186 | emu-figure figure, 187 | emu-example figure, 188 | emu-table figure { 189 | display: flex; 190 | flex-direction: column; 191 | align-items: center; 192 | } 193 | 194 | emu-production { 195 | display: block; 196 | } 197 | 198 | emu-grammar[type="example"] emu-production, 199 | emu-grammar[type="definition"] emu-production { 200 | margin-top: 1em; 201 | margin-bottom: 1em; 202 | margin-left: 5ex; 203 | } 204 | 205 | emu-grammar.inline, emu-production.inline, 206 | emu-grammar.inline emu-production emu-rhs, emu-production.inline emu-rhs, 207 | emu-grammar[collapsed] emu-production emu-rhs, emu-production[collapsed] emu-rhs { 208 | display: inline; 209 | padding-left: 1ex; 210 | margin-left: 0; 211 | } 212 | 213 | emu-grammar[collapsed] emu-production, emu-production[collapsed] { 214 | margin: 0; 215 | } 216 | 217 | emu-constraints { 218 | font-size: .75em; 219 | margin-right: 1ex; 220 | } 221 | 222 | emu-gann { 223 | margin-right: 1ex; 224 | } 225 | 226 | emu-gann emu-t:last-child, 227 | emu-gann emu-gprose:last-child, 228 | emu-gann emu-nt:last-child { 229 | margin-right: 0; 230 | } 231 | 232 | emu-geq { 233 | margin-left: 1ex; 234 | font-weight: bold; 235 | } 236 | 237 | emu-oneof { 238 | font-weight: bold; 239 | margin-left: 1ex; 240 | } 241 | 242 | emu-nt { 243 | display: inline-block; 244 | font-style: italic; 245 | white-space: nowrap; 246 | text-indent: 0; 247 | } 248 | 249 | emu-nt a, emu-nt a:visited { 250 | color: #333; 251 | } 252 | 253 | emu-rhs emu-nt { 254 | margin-right: 1ex; 255 | } 256 | 257 | emu-t { 258 | display: inline-block; 259 | font-family: monospace; 260 | font-weight: bold; 261 | white-space: nowrap; 262 | text-indent: 0; 263 | } 264 | 265 | emu-production emu-t { 266 | margin-right: 1ex; 267 | } 268 | 269 | emu-rhs { 270 | display: block; 271 | padding-left: 75px; 272 | text-indent: -25px; 273 | } 274 | 275 | emu-mods { 276 | font-size: .85em; 277 | vertical-align: sub; 278 | font-style: normal; 279 | font-weight: normal; 280 | } 281 | 282 | emu-params, emu-opt { 283 | margin-right: 1ex; 284 | font-family: monospace; 285 | } 286 | 287 | emu-params, emu-constraints { 288 | color: #2aa198; 289 | } 290 | 291 | emu-opt { 292 | color: #b58900; 293 | } 294 | 295 | emu-gprose { 296 | font-size: 0.9em; 297 | font-family: Helvetica, Arial, sans-serif; 298 | } 299 | 300 | emu-production emu-gprose { 301 | margin-right: 1ex; 302 | } 303 | 304 | h1.shortname { 305 | color: #f60; 306 | font-size: 1.5em; 307 | margin: 0; 308 | } 309 | 310 | h1.version { 311 | color: #f60; 312 | font-size: 1.5em; 313 | margin: 0; 314 | } 315 | 316 | h1.title { 317 | margin-top: 0; 318 | color: #f60; 319 | } 320 | 321 | h1.first { 322 | margin-top: 0; 323 | } 324 | 325 | h1, h2, h3, h4, h5, h6 { 326 | position: relative; 327 | } 328 | 329 | h1 .secnum { 330 | text-decoration: none; 331 | margin-right: 5px; 332 | } 333 | 334 | h1 span.title { 335 | order: 2; 336 | } 337 | 338 | 339 | h1 { font-size: 2.67em; margin-top: 2em; margin-bottom: 0; line-height: 1em;} 340 | h2 { font-size: 2em; } 341 | h3 { font-size: 1.56em; } 342 | h4 { font-size: 1.25em; } 343 | h5 { font-size: 1.11em; } 344 | h6 { font-size: 1em; } 345 | 346 | h1:hover span.utils { 347 | display: block; 348 | } 349 | 350 | span.utils { 351 | font-size: 18px; 352 | line-height: 18px; 353 | display: none; 354 | position: absolute; 355 | top: 100%; 356 | left: 0; 357 | right: 0; 358 | font-weight: normal; 359 | } 360 | 361 | span.utils:before { 362 | content: "⤷"; 363 | display: inline-block; 364 | padding: 0 5px; 365 | } 366 | 367 | span.utils > * { 368 | display: inline-block; 369 | margin-right: 20px; 370 | } 371 | 372 | h1 span.utils span.anchor a, 373 | h2 span.utils span.anchor a, 374 | h3 span.utils span.anchor a, 375 | h4 span.utils span.anchor a, 376 | h5 span.utils span.anchor a, 377 | h6 span.utils span.anchor a { 378 | text-decoration: none; 379 | font-variant: small-caps; 380 | } 381 | 382 | h1 span.utils span.anchor a:hover, 383 | h2 span.utils span.anchor a:hover, 384 | h3 span.utils span.anchor a:hover, 385 | h4 span.utils span.anchor a:hover, 386 | h5 span.utils span.anchor a:hover, 387 | h6 span.utils span.anchor a:hover { 388 | color: #333; 389 | } 390 | 391 | emu-intro h1, emu-clause h1, emu-annex h1 { font-size: 2em; } 392 | emu-intro h2, emu-clause h2, emu-annex h2 { font-size: 1.56em; } 393 | emu-intro h3, emu-clause h3, emu-annex h3 { font-size: 1.25em; } 394 | emu-intro h4, emu-clause h4, emu-annex h4 { font-size: 1.11em; } 395 | emu-intro h5, emu-clause h5, emu-annex h5 { font-size: 1em; } 396 | emu-intro h6, emu-clause h6, emu-annex h6 { font-size: 0.9em; } 397 | emu-intro emu-intro h1, emu-clause emu-clause h1, emu-annex emu-annex h1 { font-size: 1.56em; } 398 | emu-intro emu-intro h2, emu-clause emu-clause h2, emu-annex emu-annex h2 { font-size: 1.25em; } 399 | emu-intro emu-intro h3, emu-clause emu-clause h3, emu-annex emu-annex h3 { font-size: 1.11em; } 400 | emu-intro emu-intro h4, emu-clause emu-clause h4, emu-annex emu-annex h4 { font-size: 1em; } 401 | emu-intro emu-intro h5, emu-clause emu-clause h5, emu-annex emu-annex h5 { font-size: 0.9em; } 402 | emu-intro emu-intro emu-intro h1, emu-clause emu-clause emu-clause h1, emu-annex emu-annex emu-annex h1 { font-size: 1.25em; } 403 | emu-intro emu-intro emu-intro h2, emu-clause emu-clause emu-clause h2, emu-annex emu-annex emu-annex h2 { font-size: 1.11em; } 404 | emu-intro emu-intro emu-intro h3, emu-clause emu-clause emu-clause h3, emu-annex emu-annex emu-annex h3 { font-size: 1em; } 405 | emu-intro emu-intro emu-intro h4, emu-clause emu-clause emu-clause h4, emu-annex emu-annex emu-annex h4 { font-size: 0.9em; } 406 | emu-intro emu-intro emu-intro emu-intro h1, emu-clause emu-clause emu-clause emu-clause h1, emu-annex emu-annex emu-annex emu-annex h1 { font-size: 1.11em; } 407 | emu-intro emu-intro emu-intro emu-intro h2, emu-clause emu-clause emu-clause emu-clause h2, emu-annex emu-annex emu-annex emu-annex h2 { font-size: 1em; } 408 | emu-intro emu-intro emu-intro emu-intro h3, emu-clause emu-clause emu-clause emu-clause h3, emu-annex emu-annex emu-annex emu-annex h3 { font-size: 0.9em; } 409 | emu-intro emu-intro emu-intro emu-intro emu-intro h1, emu-clause emu-clause emu-clause emu-clause emu-clause h1, emu-annex emu-annex emu-annex emu-annex emu-annex h1 { font-size: 1em; } 410 | emu-intro emu-intro emu-intro emu-intro emu-intro h2, emu-clause emu-clause emu-clause emu-clause emu-clause h2, emu-annex emu-annex emu-annex emu-annex emu-annex h2 { font-size: 0.9em; } 411 | emu-intro emu-intro emu-intro emu-intro emu-intro emu-intro h1, emu-clause emu-clause emu-clause emu-clause emu-clause emu-clause h1, emu-annex emu-annex emu-annex emu-annex emu-annex emu-annex h1 { font-size: 0.9em } 412 | 413 | emu-clause, emu-intro, emu-annex { 414 | display: block; 415 | } 416 | 417 | /* Figures and tables */ 418 | figure { display: block; margin: 1em 0 3em 0; } 419 | figure object { display: block; margin: 0 auto; } 420 | figure table.real-table { margin: 0 auto; } 421 | figure figcaption { 422 | display: block; 423 | color: #555555; 424 | font-weight: bold; 425 | text-align: center; 426 | } 427 | 428 | emu-table table { 429 | margin: 0 auto; 430 | } 431 | 432 | emu-table table, table.real-table { 433 | border-collapse: collapse; 434 | } 435 | 436 | emu-table td, emu-table th, table.real-table td, table.real-table th { 437 | border: 1px solid black; 438 | padding: 0.4em; 439 | vertical-align: baseline; 440 | } 441 | emu-table th, emu-table thead td, table.real-table th { 442 | background-color: #eeeeee; 443 | } 444 | 445 | /* Note: the left content edges of table.lightweight-table >tbody >tr >td 446 | and div.display line up. */ 447 | table.lightweight-table { 448 | border-collapse: collapse; 449 | margin: 0 0 0 1.5em; 450 | } 451 | table.lightweight-table td, table.lightweight-table th { 452 | border: none; 453 | padding: 0 0.5em; 454 | vertical-align: baseline; 455 | } 456 | 457 | /* diff styles */ 458 | ins { 459 | background-color: #e0f8e0; 460 | text-decoration: none; 461 | border-bottom: 1px solid #396; 462 | } 463 | 464 | del { 465 | background-color: #fee; 466 | } 467 | 468 | ins.block, del.block, 469 | emu-production > ins, emu-production > del, 470 | emu-grammar > ins, emu-grammar > del { 471 | display: block; 472 | } 473 | emu-rhs > ins, emu-rhs > del { 474 | display: inline; 475 | } 476 | 477 | tr.ins > td > ins { 478 | border-bottom: none; 479 | } 480 | 481 | tr.ins > td { 482 | background-color: #e0f8e0; 483 | } 484 | 485 | tr.del > td { 486 | background-color: #fee; 487 | } 488 | 489 | /* Menu Styles */ 490 | #menu-toggle { 491 | font-size: 2em; 492 | 493 | position: fixed; 494 | top: 0; 495 | left: 0; 496 | width: 1.5em; 497 | height: 1.5em; 498 | z-index: 3; 499 | visibility: hidden; 500 | color: #1567a2; 501 | background-color: #fff; 502 | 503 | line-height: 1.5em; 504 | text-align: center; 505 | -webkit-touch-callout: none; 506 | -webkit-user-select: none; 507 | -khtml-user-select: none; 508 | -moz-user-select: none; 509 | -ms-user-select: none; 510 | user-select: none;; 511 | 512 | cursor: pointer; 513 | } 514 | 515 | #menu { 516 | display: flex; 517 | flex-direction: column; 518 | width: 33%; height: 100vh; 519 | max-width: 500px; 520 | box-sizing: border-box; 521 | background-color: #ddd; 522 | overflow: hidden; 523 | transition: opacity 0.1s linear; 524 | padding: 0 5px; 525 | position: fixed; 526 | left: 0; top: 0; 527 | border-right: 2px solid #bbb; 528 | 529 | z-index: 2; 530 | } 531 | 532 | #menu-spacer { 533 | flex-basis: 33%; 534 | max-width: 500px; 535 | flex-grow: 0; 536 | flex-shrink: 0; 537 | } 538 | 539 | #menu a { 540 | color: #1567a2; 541 | } 542 | 543 | #menu.active { 544 | display: flex; 545 | opacity: 1; 546 | z-index: 2; 547 | } 548 | 549 | #menu-pins { 550 | flex-grow: 1; 551 | display: none; 552 | } 553 | 554 | #menu-pins.active { 555 | display: block; 556 | } 557 | 558 | #menu-pins-list { 559 | margin: 0; 560 | padding: 0; 561 | counter-reset: pins-counter; 562 | } 563 | 564 | #menu-pins-list > li:before { 565 | content: counter(pins-counter); 566 | counter-increment: pins-counter; 567 | display: inline-block; 568 | width: 25px; 569 | text-align: center; 570 | border: 1px solid #bbb; 571 | padding: 2px; 572 | margin: 4px; 573 | box-sizing: border-box; 574 | line-height: 1em; 575 | background-color: #ccc; 576 | border-radius: 4px; 577 | } 578 | #menu-toc > ol { 579 | padding: 0; 580 | flex-grow: 1; 581 | } 582 | 583 | #menu-toc > ol li { 584 | padding: 0; 585 | } 586 | 587 | #menu-toc > ol , #menu-toc > ol ol { 588 | list-style-type: none; 589 | margin: 0; 590 | padding: 0; 591 | } 592 | 593 | #menu-toc > ol ol { 594 | padding-left: 0.75em; 595 | } 596 | 597 | #menu-toc li { 598 | text-overflow: ellipsis; 599 | overflow: hidden; 600 | white-space: nowrap; 601 | } 602 | 603 | #menu-toc .item-toggle { 604 | display: inline-block; 605 | transform: rotate(-45deg) translate(-5px, -5px); 606 | transition: transform 0.1s ease; 607 | text-align: center; 608 | width: 20px; 609 | 610 | color: #aab; 611 | 612 | -webkit-touch-callout: none; 613 | -webkit-user-select: none; 614 | -khtml-user-select: none; 615 | -moz-user-select: none; 616 | -ms-user-select: none; 617 | user-select: none;; 618 | 619 | cursor: pointer; 620 | } 621 | 622 | #menu-toc .item-toggle-none { 623 | display: inline-block; 624 | width: 20px; 625 | } 626 | 627 | #menu-toc li.active > .item-toggle { 628 | transform: rotate(45deg) translate(-5px, -5px); 629 | } 630 | 631 | #menu-toc li > ol { 632 | display: none; 633 | } 634 | 635 | #menu-toc li.active > ol { 636 | display: block; 637 | } 638 | 639 | #menu-toc li.revealed > a { 640 | background-color: #bbb; 641 | font-weight: bold; 642 | /* 643 | background-color: #222; 644 | color: #c6d8e4; 645 | */ 646 | } 647 | 648 | #menu-toc li.revealed-leaf> a { 649 | color: #206ca7; 650 | /* 651 | background-color: #222; 652 | color: #c6d8e4; 653 | */ 654 | } 655 | 656 | #menu-toc li.revealed > .item-toggle { 657 | transform: rotate(45deg) translate(-5px, -5px); 658 | } 659 | 660 | #menu-toc li.revealed > ol { 661 | display: block; 662 | } 663 | 664 | #menu-toc li > a { 665 | padding: 2px 5px; 666 | } 667 | 668 | #menu > * { 669 | margin-bottom: 5px; 670 | } 671 | 672 | .menu-pane-header { 673 | padding: 0 5px; 674 | text-transform: uppercase; 675 | background-color: #aaa; 676 | color: #335; 677 | font-weight: bold; 678 | letter-spacing: 2px; 679 | flex-grow: 0; 680 | flex-shrink: 0; 681 | font-size: 0.8em; 682 | } 683 | 684 | #menu-toc { 685 | display: flex; 686 | flex-direction: column; 687 | width: 100%; 688 | overflow: hidden; 689 | flex-grow: 1; 690 | } 691 | 692 | #menu-toc ol.toc { 693 | overflow-x: hidden; 694 | overflow-y: auto; 695 | } 696 | 697 | #menu-search { 698 | position: relative; 699 | flex-grow: 0; 700 | flex-shrink: 0; 701 | width: 100%; 702 | 703 | display: flex; 704 | flex-direction: column; 705 | 706 | max-height: 300px; 707 | } 708 | 709 | #menu-trace-list { 710 | display: none; 711 | } 712 | 713 | #menu-search-box { 714 | box-sizing: border-box; 715 | display: block; 716 | width: 100%; 717 | margin: 5px 0 0 0; 718 | font-size: 1em; 719 | padding: 2px; 720 | background-color: #bbb; 721 | border: 1px solid #999; 722 | } 723 | 724 | #menu-search-results { 725 | overflow-x: hidden; 726 | overflow-y: auto; 727 | } 728 | 729 | li.menu-search-result-clause:before { 730 | content: 'clause'; 731 | width: 40px; 732 | display: inline-block; 733 | text-align: right; 734 | padding-right: 1ex; 735 | color: #666; 736 | font-size: 75%; 737 | } 738 | li.menu-search-result-op:before { 739 | content: 'op'; 740 | width: 40px; 741 | display: inline-block; 742 | text-align: right; 743 | padding-right: 1ex; 744 | color: #666; 745 | font-size: 75%; 746 | } 747 | 748 | li.menu-search-result-prod:before { 749 | content: 'prod'; 750 | width: 40px; 751 | display: inline-block; 752 | text-align: right; 753 | padding-right: 1ex; 754 | color: #666; 755 | font-size: 75% 756 | } 757 | 758 | 759 | li.menu-search-result-term:before { 760 | content: 'term'; 761 | width: 40px; 762 | display: inline-block; 763 | text-align: right; 764 | padding-right: 1ex; 765 | color: #666; 766 | font-size: 75% 767 | } 768 | 769 | #menu-search-results ul { 770 | padding: 0 5px; 771 | margin: 0; 772 | } 773 | 774 | #menu-search-results li { 775 | white-space: nowrap; 776 | text-overflow: ellipsis; 777 | } 778 | 779 | 780 | #menu-trace-list { 781 | counter-reset: item; 782 | margin: 0 0 0 20px; 783 | padding: 0; 784 | } 785 | #menu-trace-list li { 786 | display: block; 787 | white-space: nowrap; 788 | } 789 | 790 | #menu-trace-list li .secnum:after { 791 | content: " "; 792 | } 793 | #menu-trace-list li:before { 794 | content: counter(item) " "; 795 | background-color: #222; 796 | counter-increment: item; 797 | color: #999; 798 | width: 20px; 799 | height: 20px; 800 | line-height: 20px; 801 | display: inline-block; 802 | text-align: center; 803 | margin: 2px 4px 2px 0; 804 | } 805 | 806 | @media (max-width: 1000px) { 807 | body { 808 | margin: 0; 809 | display: block; 810 | } 811 | 812 | #menu { 813 | display: none; 814 | padding-top: 3em; 815 | width: 450px; 816 | } 817 | 818 | #menu.active { 819 | position: fixed; 820 | height: 100%; 821 | left: 0; 822 | top: 0; 823 | right: 300px; 824 | } 825 | 826 | #menu-toggle { 827 | visibility: visible; 828 | } 829 | 830 | #spec-container { 831 | padding: 0 5px; 832 | } 833 | 834 | #references-pane-spacer { 835 | display: none; 836 | } 837 | } 838 | 839 | @media only screen and (max-width: 800px) { 840 | #menu { 841 | width: 100%; 842 | } 843 | 844 | h1 .secnum:empty { 845 | margin: 0; padding: 0; 846 | } 847 | } 848 | 849 | 850 | /* Toolbox */ 851 | .toolbox { 852 | position: absolute; 853 | background: #ddd; 854 | border: 1px solid #aaa; 855 | display: none; 856 | color: #eee; 857 | padding: 5px; 858 | border-radius: 3px; 859 | } 860 | 861 | .toolbox.active { 862 | display: inline-block; 863 | } 864 | 865 | .toolbox a { 866 | text-decoration: none; 867 | padding: 0 5px; 868 | } 869 | 870 | .toolbox a:hover { 871 | text-decoration: underline; 872 | } 873 | 874 | .toolbox:after, .toolbox:before { 875 | top: 100%; 876 | left: 15px; 877 | border: solid transparent; 878 | content: " "; 879 | height: 0; 880 | width: 0; 881 | position: absolute; 882 | pointer-events: none; 883 | } 884 | 885 | .toolbox:after { 886 | border-color: rgba(0, 0, 0, 0); 887 | border-top-color: #ddd; 888 | border-width: 10px; 889 | margin-left: -10px; 890 | } 891 | .toolbox:before { 892 | border-color: rgba(204, 204, 204, 0); 893 | border-top-color: #aaa; 894 | border-width: 12px; 895 | margin-left: -12px; 896 | } 897 | 898 | #references-pane-container { 899 | position: fixed; 900 | bottom: 0; 901 | left: 0; 902 | right: 0; 903 | height: 250px; 904 | display: none; 905 | background-color: #ddd; 906 | z-index: 1; 907 | } 908 | 909 | #references-pane-table-container { 910 | overflow-x: hidden; 911 | overflow-y: auto; 912 | } 913 | 914 | #references-pane-spacer { 915 | flex-basis: 33%; 916 | max-width: 500px; 917 | } 918 | 919 | #references-pane { 920 | flex-grow: 1; 921 | overflow: hidden; 922 | display: flex; 923 | flex-direction: column; 924 | } 925 | 926 | #references-pane-container.active { 927 | display: flex; 928 | } 929 | 930 | #references-pane-close:after { 931 | content: '✖'; 932 | float: right; 933 | cursor: pointer; 934 | } 935 | 936 | #references-pane table tr td:first-child { 937 | text-align: right; 938 | padding-right: 5px; 939 | } 940 | 941 | @media print { 942 | #menu-toggle { 943 | display: none; 944 | } 945 | } 946 | -------------------------------------------------------------------------------- /docs/ecmarkup.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function Search(menu) { 4 | this.menu = menu; 5 | this.$search = document.getElementById('menu-search'); 6 | this.$searchBox = document.getElementById('menu-search-box'); 7 | this.$searchResults = document.getElementById('menu-search-results'); 8 | 9 | this.loadBiblio(); 10 | 11 | document.addEventListener('keydown', this.documentKeydown.bind(this)); 12 | 13 | this.$searchBox.addEventListener('keydown', debounce(this.searchBoxKeydown.bind(this), { stopPropagation: true })); 14 | this.$searchBox.addEventListener('keyup', debounce(this.searchBoxKeyup.bind(this), { stopPropagation: true })); 15 | 16 | // Perform an initial search if the box is not empty. 17 | if (this.$searchBox.value) { 18 | this.search(this.$searchBox.value); 19 | } 20 | } 21 | 22 | Search.prototype.loadBiblio = function () { 23 | var $biblio = document.getElementById('menu-search-biblio'); 24 | if (!$biblio) { 25 | this.biblio = []; 26 | } else { 27 | this.biblio = JSON.parse($biblio.textContent); 28 | this.biblio.clauses = this.biblio.filter(function (e) { return e.type === 'clause' }); 29 | this.biblio.byId = this.biblio.reduce(function (map, entry) { 30 | map[entry.id] = entry; 31 | return map; 32 | }, {}); 33 | } 34 | } 35 | 36 | Search.prototype.documentKeydown = function (e) { 37 | if (e.keyCode === 191) { 38 | e.preventDefault(); 39 | e.stopPropagation(); 40 | this.triggerSearch(); 41 | } 42 | } 43 | 44 | Search.prototype.searchBoxKeydown = function (e) { 45 | e.stopPropagation(); 46 | e.preventDefault(); 47 | if (e.keyCode === 191 && e.target.value.length === 0) { 48 | e.preventDefault(); 49 | } else if (e.keyCode === 13) { 50 | e.preventDefault(); 51 | this.selectResult(); 52 | } 53 | } 54 | 55 | Search.prototype.searchBoxKeyup = function (e) { 56 | if (e.keyCode === 13 || e.keyCode === 9) { 57 | return; 58 | } 59 | 60 | this.search(e.target.value); 61 | } 62 | 63 | 64 | Search.prototype.triggerSearch = function (e) { 65 | if (this.menu.isVisible()) { 66 | this._closeAfterSearch = false; 67 | } else { 68 | this._closeAfterSearch = true; 69 | this.menu.show(); 70 | } 71 | 72 | this.$searchBox.focus(); 73 | this.$searchBox.select(); 74 | } 75 | // bit 12 - Set if the result starts with searchString 76 | // bits 8-11: 8 - number of chunks multiplied by 2 if cases match, otherwise 1. 77 | // bits 1-7: 127 - length of the entry 78 | // General scheme: prefer case sensitive matches with fewer chunks, and otherwise 79 | // prefer shorter matches. 80 | function relevance(result, searchString) { 81 | var relevance = 0; 82 | 83 | relevance = Math.max(0, 8 - result.match.chunks) << 7; 84 | 85 | if (result.match.caseMatch) { 86 | relevance *= 2; 87 | } 88 | 89 | if (result.match.prefix) { 90 | relevance += 2048 91 | } 92 | 93 | relevance += Math.max(0, 255 - result.entry.key.length); 94 | 95 | return relevance; 96 | } 97 | 98 | Search.prototype.search = function (searchString) { 99 | if (searchString === '') { 100 | this.displayResults([]); 101 | this.hideSearch(); 102 | return; 103 | } else { 104 | this.showSearch(); 105 | } 106 | 107 | if (searchString.length === 1) { 108 | this.displayResults([]); 109 | return; 110 | } 111 | 112 | var results; 113 | 114 | if (/^[\d\.]*$/.test(searchString)) { 115 | results = this.biblio.clauses.filter(function (clause) { 116 | return clause.number.substring(0, searchString.length) === searchString; 117 | }).map(function (clause) { 118 | return { entry: clause }; 119 | }); 120 | } else { 121 | results = []; 122 | 123 | for (var i = 0; i < this.biblio.length; i++) { 124 | var entry = this.biblio[i]; 125 | if (!entry.key) { 126 | // biblio entries without a key aren't searchable 127 | continue; 128 | } 129 | 130 | var match = fuzzysearch(searchString, entry.key); 131 | if (match) { 132 | results.push({ entry: entry, match: match }); 133 | } 134 | } 135 | 136 | results.forEach(function (result) { 137 | result.relevance = relevance(result, searchString); 138 | }); 139 | 140 | results = results.sort(function (a, b) { return b.relevance - a.relevance }); 141 | 142 | } 143 | 144 | if (results.length > 50) { 145 | results = results.slice(0, 50); 146 | } 147 | 148 | this.displayResults(results); 149 | } 150 | Search.prototype.hideSearch = function () { 151 | this.$search.classList.remove('active'); 152 | } 153 | 154 | Search.prototype.showSearch = function () { 155 | this.$search.classList.add('active'); 156 | } 157 | 158 | Search.prototype.selectResult = function () { 159 | var $first = this.$searchResults.querySelector('li:first-child a'); 160 | 161 | if ($first) { 162 | document.location = $first.getAttribute('href'); 163 | } 164 | 165 | this.$searchBox.value = ''; 166 | this.$searchBox.blur(); 167 | this.displayResults([]); 168 | this.hideSearch(); 169 | 170 | if (this._closeAfterSearch) { 171 | this.menu.hide(); 172 | } 173 | } 174 | 175 | Search.prototype.displayResults = function (results) { 176 | if (results.length > 0) { 177 | this.$searchResults.classList.remove('no-results'); 178 | 179 | var html = '' 212 | 213 | this.$searchResults.innerHTML = html; 214 | } else { 215 | this.$searchResults.innerHTML = ''; 216 | this.$searchResults.classList.add('no-results'); 217 | } 218 | } 219 | 220 | 221 | function Menu() { 222 | this.$toggle = document.getElementById('menu-toggle'); 223 | this.$menu = document.getElementById('menu'); 224 | this.$toc = document.querySelector('menu-toc > ol'); 225 | this.$pins = document.querySelector('#menu-pins'); 226 | this.$pinList = document.getElementById('menu-pins-list'); 227 | this.$toc = document.querySelector('#menu-toc > ol'); 228 | this.$specContainer = document.getElementById('spec-container'); 229 | this.search = new Search(this); 230 | 231 | this._pinnedIds = {}; 232 | this.loadPinEntries(); 233 | 234 | // toggle menu 235 | this.$toggle.addEventListener('click', this.toggle.bind(this)); 236 | 237 | // keydown events for pinned clauses 238 | document.addEventListener('keydown', this.documentKeydown.bind(this)); 239 | 240 | // toc expansion 241 | var tocItems = this.$menu.querySelectorAll('#menu-toc li'); 242 | for (var i = 0; i < tocItems.length; i++) { 243 | var $item = tocItems[i]; 244 | $item.addEventListener('click', function($item, event) { 245 | $item.classList.toggle('active'); 246 | event.stopPropagation(); 247 | }.bind(null, $item)); 248 | } 249 | 250 | // close toc on toc item selection 251 | var tocLinks = this.$menu.querySelectorAll('#menu-toc li > a'); 252 | for (var i = 0; i < tocLinks.length; i++) { 253 | var $link = tocLinks[i]; 254 | $link.addEventListener('click', function(event) { 255 | this.toggle(); 256 | event.stopPropagation(); 257 | }.bind(this)); 258 | } 259 | 260 | // update active clause on scroll 261 | window.addEventListener('scroll', debounce(this.updateActiveClause.bind(this))); 262 | this.updateActiveClause(); 263 | 264 | // prevent menu scrolling from scrolling the body 265 | this.$toc.addEventListener('wheel', function (e) { 266 | var target = e.currentTarget; 267 | var offTop = e.deltaY < 0 && target.scrollTop === 0; 268 | if (offTop) { 269 | e.preventDefault(); 270 | } 271 | var offBottom = e.deltaY > 0 272 | && target.offsetHeight + target.scrollTop >= target.scrollHeight; 273 | 274 | if (offBottom) { 275 | e.preventDefault(); 276 | } 277 | }); 278 | } 279 | 280 | Menu.prototype.documentKeydown = function (e) { 281 | e.stopPropagation(); 282 | if (e.keyCode === 80) { 283 | this.togglePinEntry(); 284 | } else if (e.keyCode > 48 && e.keyCode < 58) { 285 | this.selectPin(e.keyCode - 49); 286 | } 287 | } 288 | 289 | Menu.prototype.updateActiveClause = function () { 290 | this.setActiveClause(findActiveClause(this.$specContainer)) 291 | } 292 | 293 | Menu.prototype.setActiveClause = function (clause) { 294 | this.$activeClause = clause; 295 | this.revealInToc(this.$activeClause); 296 | } 297 | 298 | Menu.prototype.revealInToc = function (path) { 299 | var current = this.$toc.querySelectorAll('li.revealed'); 300 | for (var i = 0; i < current.length; i++) { 301 | current[i].classList.remove('revealed'); 302 | current[i].classList.remove('revealed-leaf'); 303 | } 304 | 305 | var current = this.$toc; 306 | var index = 0; 307 | while (index < path.length) { 308 | var children = current.children; 309 | for (var i = 0; i < children.length; i++) { 310 | if ('#' + path[index].id === children[i].children[1].getAttribute('href') ) { 311 | children[i].classList.add('revealed'); 312 | if (index === path.length - 1) { 313 | children[i].classList.add('revealed-leaf'); 314 | var rect = children[i].getBoundingClientRect(); 315 | // this.$toc.getBoundingClientRect().top; 316 | var tocRect = this.$toc.getBoundingClientRect(); 317 | if (rect.top + 10 > tocRect.bottom) { 318 | this.$toc.scrollTop = this.$toc.scrollTop + (rect.top - tocRect.bottom) + (rect.bottom - rect.top); 319 | } else if (rect.top < tocRect.top) { 320 | this.$toc.scrollTop = this.$toc.scrollTop - (tocRect.top - rect.top); 321 | } 322 | } 323 | current = children[i].querySelector('ol'); 324 | index++; 325 | break; 326 | } 327 | } 328 | 329 | } 330 | } 331 | 332 | function findActiveClause(root, path) { 333 | var clauses = new ClauseWalker(root); 334 | var $clause; 335 | var path = path || []; 336 | 337 | while ($clause = clauses.nextNode()) { 338 | var rect = $clause.getBoundingClientRect(); 339 | var $header = $clause.querySelector("h1"); 340 | var marginTop = parseInt(getComputedStyle($header)["margin-top"]); 341 | 342 | if ((rect.top - marginTop) <= 0 && rect.bottom > 0) { 343 | return findActiveClause($clause, path.concat($clause)) || path; 344 | } 345 | } 346 | 347 | return path; 348 | } 349 | 350 | function ClauseWalker(root) { 351 | var previous; 352 | var treeWalker = document.createTreeWalker( 353 | root, 354 | NodeFilter.SHOW_ELEMENT, 355 | { 356 | acceptNode: function (node) { 357 | if (previous === node.parentNode) { 358 | return NodeFilter.FILTER_REJECT; 359 | } else { 360 | previous = node; 361 | } 362 | if (node.nodeName === 'EMU-CLAUSE' || node.nodeName === 'EMU-INTRO' || node.nodeName === 'EMU-ANNEX') { 363 | return NodeFilter.FILTER_ACCEPT; 364 | } else { 365 | return NodeFilter.FILTER_SKIP; 366 | } 367 | } 368 | }, 369 | false 370 | ); 371 | 372 | return treeWalker; 373 | } 374 | 375 | Menu.prototype.toggle = function () { 376 | this.$menu.classList.toggle('active'); 377 | } 378 | 379 | Menu.prototype.show = function () { 380 | this.$menu.classList.add('active'); 381 | } 382 | 383 | Menu.prototype.hide = function () { 384 | this.$menu.classList.remove('active'); 385 | } 386 | 387 | Menu.prototype.isVisible = function() { 388 | return this.$menu.classList.contains('active'); 389 | } 390 | 391 | Menu.prototype.showPins = function () { 392 | this.$pins.classList.add('active'); 393 | } 394 | 395 | Menu.prototype.hidePins = function () { 396 | this.$pins.classList.remove('active'); 397 | } 398 | 399 | Menu.prototype.addPinEntry = function (id) { 400 | var entry = this.search.biblio.byId[id]; 401 | if (!entry) { 402 | // id was deleted after pin (or something) so remove it 403 | delete this._pinnedIds[id]; 404 | this.persistPinEntries(); 405 | return; 406 | } 407 | 408 | if (entry.type === 'clause') { 409 | var prefix; 410 | if (entry.number) { 411 | prefix = entry.number + ' '; 412 | } else { 413 | prefix = ''; 414 | } 415 | this.$pinList.innerHTML += '
  • ' + prefix + entry.titleHTML + '
  • '; 416 | } else { 417 | this.$pinList.innerHTML += '
  • ' + entry.key + '
  • '; 418 | } 419 | 420 | if (Object.keys(this._pinnedIds).length === 0) { 421 | this.showPins(); 422 | } 423 | this._pinnedIds[id] = true; 424 | this.persistPinEntries(); 425 | } 426 | 427 | Menu.prototype.removePinEntry = function (id) { 428 | var item = this.$pinList.querySelector('a[href="#' + id + '"]').parentNode; 429 | this.$pinList.removeChild(item); 430 | delete this._pinnedIds[id]; 431 | if (Object.keys(this._pinnedIds).length === 0) { 432 | this.hidePins(); 433 | } 434 | 435 | this.persistPinEntries(); 436 | } 437 | 438 | Menu.prototype.persistPinEntries = function () { 439 | try { 440 | if (!window.localStorage) return; 441 | } catch (e) { 442 | return; 443 | } 444 | 445 | localStorage.pinEntries = JSON.stringify(Object.keys(this._pinnedIds)); 446 | } 447 | 448 | Menu.prototype.loadPinEntries = function () { 449 | try { 450 | if (!window.localStorage) return; 451 | } catch (e) { 452 | return; 453 | } 454 | 455 | var pinsString = window.localStorage.pinEntries; 456 | if (!pinsString) return; 457 | var pins = JSON.parse(pinsString); 458 | for(var i = 0; i < pins.length; i++) { 459 | this.addPinEntry(pins[i]); 460 | } 461 | } 462 | 463 | Menu.prototype.togglePinEntry = function (id) { 464 | if (!id) { 465 | id = this.$activeClause[this.$activeClause.length - 1].id; 466 | } 467 | 468 | if (this._pinnedIds[id]) { 469 | this.removePinEntry(id); 470 | } else { 471 | this.addPinEntry(id); 472 | } 473 | } 474 | 475 | Menu.prototype.selectPin = function (num) { 476 | document.location = this.$pinList.children[num].children[0].href; 477 | } 478 | 479 | var menu; 480 | function init() { 481 | menu = new Menu(); 482 | var $container = document.getElementById('spec-container'); 483 | $container.addEventListener('mouseover', debounce(function (e) { 484 | Toolbox.activateIfMouseOver(e); 485 | })); 486 | document.addEventListener('keydown', debounce(function (e) { 487 | if (e.code === "Escape" && Toolbox.active) { 488 | Toolbox.deactivate(); 489 | } 490 | })); 491 | } 492 | 493 | document.addEventListener('DOMContentLoaded', init); 494 | 495 | function debounce(fn, opts) { 496 | opts = opts || {}; 497 | var timeout; 498 | return function(e) { 499 | if (opts.stopPropagation) { 500 | e.stopPropagation(); 501 | } 502 | var args = arguments; 503 | if (timeout) { 504 | clearTimeout(timeout); 505 | } 506 | timeout = setTimeout(function() { 507 | timeout = null; 508 | fn.apply(this, args); 509 | }.bind(this), 150); 510 | } 511 | } 512 | 513 | var CLAUSE_NODES = ['EMU-CLAUSE', 'EMU-INTRO', 'EMU-ANNEX']; 514 | function findLocalReferences ($elem) { 515 | var name = $elem.innerHTML; 516 | var references = []; 517 | 518 | var parentClause = $elem.parentNode; 519 | while (parentClause && CLAUSE_NODES.indexOf(parentClause.nodeName) === -1) { 520 | parentClause = parentClause.parentNode; 521 | } 522 | 523 | if(!parentClause) return; 524 | 525 | var vars = parentClause.querySelectorAll('var'); 526 | 527 | for (var i = 0; i < vars.length; i++) { 528 | var $var = vars[i]; 529 | 530 | if ($var.innerHTML === name) { 531 | references.push($var); 532 | } 533 | } 534 | 535 | return references; 536 | } 537 | 538 | function toggleFindLocalReferences($elem) { 539 | var references = findLocalReferences($elem); 540 | if ($elem.classList.contains('referenced')) { 541 | references.forEach(function ($reference) { 542 | $reference.classList.remove('referenced'); 543 | }); 544 | } else { 545 | references.forEach(function ($reference) { 546 | $reference.classList.add('referenced'); 547 | }); 548 | } 549 | } 550 | 551 | function installFindLocalReferences () { 552 | document.addEventListener('click', function (e) { 553 | if (e.target.nodeName === 'VAR') { 554 | toggleFindLocalReferences(e.target); 555 | } 556 | }); 557 | } 558 | 559 | document.addEventListener('DOMContentLoaded', installFindLocalReferences); 560 | 561 | 562 | 563 | 564 | // The following license applies to the fuzzysearch function 565 | // The MIT License (MIT) 566 | // Copyright © 2015 Nicolas Bevacqua 567 | // Copyright © 2016 Brian Terlson 568 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 569 | // this software and associated documentation files (the "Software"), to deal in 570 | // the Software without restriction, including without limitation the rights to 571 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 572 | // the Software, and to permit persons to whom the Software is furnished to do so, 573 | // subject to the following conditions: 574 | 575 | // The above copyright notice and this permission notice shall be included in all 576 | // copies or substantial portions of the Software. 577 | 578 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 579 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 580 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 581 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 582 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 583 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 584 | function fuzzysearch (searchString, haystack, caseInsensitive) { 585 | var tlen = haystack.length; 586 | var qlen = searchString.length; 587 | var chunks = 1; 588 | var finding = false; 589 | 590 | if (qlen > tlen) { 591 | return false; 592 | } 593 | 594 | if (qlen === tlen) { 595 | if (searchString === haystack) { 596 | return { caseMatch: true, chunks: 1, prefix: true }; 597 | } else if (searchString.toLowerCase() === haystack.toLowerCase()) { 598 | return { caseMatch: false, chunks: 1, prefix: true }; 599 | } else { 600 | return false; 601 | } 602 | } 603 | 604 | outer: for (var i = 0, j = 0; i < qlen; i++) { 605 | var nch = searchString[i]; 606 | while (j < tlen) { 607 | var targetChar = haystack[j++]; 608 | if (targetChar === nch) { 609 | finding = true; 610 | continue outer; 611 | } 612 | if (finding) { 613 | chunks++; 614 | finding = false; 615 | } 616 | } 617 | 618 | if (caseInsensitive) { return false; } 619 | 620 | return fuzzysearch(searchString.toLowerCase(), haystack.toLowerCase(), true); 621 | } 622 | 623 | return { caseMatch: !caseInsensitive, chunks: chunks, prefix: j <= qlen }; 624 | } 625 | 626 | var Toolbox = { 627 | init: function () { 628 | this.$container = document.createElement('div'); 629 | this.$container.classList.add('toolbox'); 630 | this.$permalink = document.createElement('a'); 631 | this.$permalink.textContent = 'Permalink'; 632 | this.$pinLink = document.createElement('a'); 633 | this.$pinLink.textContent = 'Pin'; 634 | this.$pinLink.setAttribute('href', '#'); 635 | this.$pinLink.addEventListener('click', function (e) { 636 | e.preventDefault(); 637 | e.stopPropagation(); 638 | menu.togglePinEntry(this.entry.id); 639 | }.bind(this)); 640 | 641 | this.$refsLink = document.createElement('a'); 642 | this.$refsLink.setAttribute('href', '#'); 643 | this.$refsLink.addEventListener('click', function (e) { 644 | e.preventDefault(); 645 | e.stopPropagation(); 646 | referencePane.showReferencesFor(this.entry); 647 | }.bind(this)); 648 | this.$container.appendChild(this.$permalink); 649 | this.$container.appendChild(this.$pinLink); 650 | this.$container.appendChild(this.$refsLink); 651 | document.body.appendChild(this.$container); 652 | }, 653 | 654 | activate: function (el, entry, target) { 655 | if (el === this._activeEl) return; 656 | this.active = true; 657 | this.entry = entry; 658 | this.$container.classList.add('active'); 659 | this.top = el.offsetTop - this.$container.offsetHeight - 10; 660 | this.left = el.offsetLeft; 661 | this.$container.setAttribute('style', 'left: ' + this.left + 'px; top: ' + this.top + 'px'); 662 | this.updatePermalink(); 663 | this.updateReferences(); 664 | this._activeEl = el; 665 | if (this.top < document.body.scrollTop && el === target) { 666 | // don't scroll unless it's a small thing (< 200px) 667 | this.$container.scrollIntoView(); 668 | } 669 | }, 670 | 671 | updatePermalink: function () { 672 | this.$permalink.setAttribute('href', '#' + this.entry.id); 673 | }, 674 | 675 | updateReferences: function () { 676 | this.$refsLink.textContent = 'References (' + this.entry.referencingIds.length + ')'; 677 | }, 678 | 679 | activateIfMouseOver: function (e) { 680 | var ref = this.findReferenceUnder(e.target); 681 | if (ref && (!this.active || e.pageY > this._activeEl.offsetTop)) { 682 | var entry = menu.search.biblio.byId[ref.id]; 683 | this.activate(ref.element, entry, e.target); 684 | } else if (this.active && ((e.pageY < this.top) || e.pageY > (this._activeEl.offsetTop + this._activeEl.offsetHeight))) { 685 | this.deactivate(); 686 | } 687 | }, 688 | 689 | findReferenceUnder: function (el) { 690 | while (el) { 691 | var parent = el.parentNode; 692 | if (el.nodeName === 'H1' && parent.nodeName.match(/EMU-CLAUSE|EMU-ANNEX|EMU-INTRO/) && parent.id) { 693 | return { element: el, id: parent.id }; 694 | } else if (el.nodeName.match(/EMU-(?!CLAUSE|XREF|ANNEX|INTRO)|DFN/) && 695 | el.id && el.id[0] !== '_') { 696 | if (el.nodeName === 'EMU-FIGURE' || el.nodeName === 'EMU-TABLE' || el.nodeName === 'EMU-EXAMPLE') { 697 | // return the figcaption element 698 | return { element: el.children[0].children[0], id: el.id }; 699 | } else if (el.nodeName === 'EMU-PRODUCTION') { 700 | // return the LHS non-terminal element 701 | return { element: el.children[0], id: el.id }; 702 | } else { 703 | return { element: el, id: el.id }; 704 | } 705 | } 706 | el = parent; 707 | } 708 | }, 709 | 710 | deactivate: function () { 711 | this.$container.classList.remove('active'); 712 | this._activeEl = null; 713 | this.activeElBounds = null; 714 | this.active = false; 715 | } 716 | } 717 | 718 | var referencePane = { 719 | init: function() { 720 | this.$container = document.createElement('div'); 721 | this.$container.setAttribute('id', 'references-pane-container'); 722 | 723 | var $spacer = document.createElement('div'); 724 | $spacer.setAttribute('id', 'references-pane-spacer'); 725 | 726 | this.$pane = document.createElement('div'); 727 | this.$pane.setAttribute('id', 'references-pane'); 728 | 729 | this.$container.appendChild($spacer); 730 | this.$container.appendChild(this.$pane); 731 | 732 | this.$header = document.createElement('div'); 733 | this.$header.classList.add('menu-pane-header'); 734 | this.$header.textContent = 'References to '; 735 | this.$headerRefId = document.createElement('a'); 736 | this.$header.appendChild(this.$headerRefId); 737 | this.$closeButton = document.createElement('span'); 738 | this.$closeButton.setAttribute('id', 'references-pane-close'); 739 | this.$closeButton.addEventListener('click', function (e) { 740 | this.deactivate(); 741 | }.bind(this)); 742 | this.$header.appendChild(this.$closeButton); 743 | 744 | this.$pane.appendChild(this.$header); 745 | var tableContainer = document.createElement('div'); 746 | tableContainer.setAttribute('id', 'references-pane-table-container'); 747 | 748 | this.$table = document.createElement('table'); 749 | this.$table.setAttribute('id', 'references-pane-table'); 750 | 751 | this.$tableBody = this.$table.createTBody(); 752 | 753 | tableContainer.appendChild(this.$table); 754 | this.$pane.appendChild(tableContainer); 755 | 756 | menu.$specContainer.appendChild(this.$container); 757 | }, 758 | 759 | activate: function () { 760 | this.$container.classList.add('active'); 761 | }, 762 | 763 | deactivate: function () { 764 | this.$container.classList.remove('active'); 765 | }, 766 | 767 | showReferencesFor(entry) { 768 | this.activate(); 769 | var newBody = document.createElement('tbody'); 770 | var previousId; 771 | var previousCell; 772 | var dupCount = 0; 773 | this.$headerRefId.textContent = '#' + entry.id; 774 | this.$headerRefId.setAttribute('href', '#' + entry.id); 775 | entry.referencingIds.map(function (id) { 776 | var target = document.getElementById(id); 777 | var cid = findParentClauseId(target); 778 | var clause = menu.search.biblio.byId[cid]; 779 | return { id: id, clause: clause } 780 | }).sort(function (a, b) { 781 | return sortByClauseNumber(a.clause, b.clause); 782 | }).forEach(function (record, i) { 783 | if (previousId === record.clause.id) { 784 | previousCell.innerHTML += ' (' + (dupCount + 2) + ')'; 785 | dupCount++; 786 | } else { 787 | var row = newBody.insertRow(); 788 | var cell = row.insertCell(); 789 | cell.innerHTML = record.clause.number; 790 | cell = row.insertCell(); 791 | cell.innerHTML = '' + record.clause.titleHTML + ''; 792 | previousCell = cell; 793 | previousId = record.clause.id; 794 | dupCount = 0; 795 | } 796 | }, this); 797 | this.$table.removeChild(this.$tableBody); 798 | this.$tableBody = newBody; 799 | this.$table.appendChild(this.$tableBody); 800 | } 801 | } 802 | function findParentClauseId(node) { 803 | while (node && node.nodeName !== 'EMU-CLAUSE' && node.nodeName !== 'EMU-INTRO' && node.nodeName !== 'EMU-ANNEX') { 804 | node = node.parentNode; 805 | } 806 | if (!node) return null; 807 | return node.getAttribute('id'); 808 | } 809 | 810 | function sortByClauseNumber(c1, c2) { 811 | var c1c = c1.number.split('.'); 812 | var c2c = c2.number.split('.'); 813 | 814 | for (var i = 0; i < c1c.length; i++) { 815 | if (i >= c2c.length) { 816 | return 1; 817 | } 818 | 819 | var c1 = c1c[i]; 820 | var c2 = c2c[i]; 821 | var c1cn = Number(c1); 822 | var c2cn = Number(c2); 823 | 824 | if (Number.isNaN(c1cn) && Number.isNaN(c2cn)) { 825 | if (c1 > c2) { 826 | return 1; 827 | } else if (c1 < c2) { 828 | return -1; 829 | } 830 | } else if (!Number.isNaN(c1cn) && Number.isNaN(c2cn)) { 831 | return -1; 832 | } else if (Number.isNaN(c1cn) && !Number.isNaN(c2cn)) { 833 | return 1; 834 | } else if(c1cn > c2cn) { 835 | return 1; 836 | } else if (c1cn < c2cn) { 837 | return -1; 838 | } 839 | } 840 | 841 | if (c1c.length === c2c.length) { 842 | return 0; 843 | } 844 | return -1; 845 | } 846 | 847 | document.addEventListener('DOMContentLoaded', function () { 848 | Toolbox.init(); 849 | referencePane.init(); 850 | }) 851 | var CLAUSE_NODES = ['EMU-CLAUSE', 'EMU-INTRO', 'EMU-ANNEX']; 852 | function findLocalReferences ($elem) { 853 | var name = $elem.innerHTML; 854 | var references = []; 855 | 856 | var parentClause = $elem.parentNode; 857 | while (parentClause && CLAUSE_NODES.indexOf(parentClause.nodeName) === -1) { 858 | parentClause = parentClause.parentNode; 859 | } 860 | 861 | if(!parentClause) return; 862 | 863 | var vars = parentClause.querySelectorAll('var'); 864 | 865 | for (var i = 0; i < vars.length; i++) { 866 | var $var = vars[i]; 867 | 868 | if ($var.innerHTML === name) { 869 | references.push($var); 870 | } 871 | } 872 | 873 | return references; 874 | } 875 | 876 | function toggleFindLocalReferences($elem) { 877 | var references = findLocalReferences($elem); 878 | if ($elem.classList.contains('referenced')) { 879 | references.forEach(function ($reference) { 880 | $reference.classList.remove('referenced'); 881 | }); 882 | } else { 883 | references.forEach(function ($reference) { 884 | $reference.classList.add('referenced'); 885 | }); 886 | } 887 | } 888 | 889 | function installFindLocalReferences () { 890 | document.addEventListener('click', function (e) { 891 | if (e.target.nodeName === 'VAR') { 892 | toggleFindLocalReferences(e.target); 893 | } 894 | }); 895 | } 896 | 897 | document.addEventListener('DOMContentLoaded', installFindLocalReferences); 898 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Numeric separators

    Stage 3 Draft / July 9, 2020

    Numeric separators

    4 | 5 | 6 | 21 | 22 |

    The grammar listed in 11.8.3 is modified as follows.

    23 | 24 | 25 | 26 |

    1 Numeric Literals

    27 |

    Syntax

    28 | 29 | NumericLiteralSeparator::_ 30 | 31 | 32 | NumericLiteral::DecimalLiteral 33 | DecimalBigIntegerLiteral 34 | NonDecimalIntegerLiteral 35 | NonDecimalIntegerLiteralBigIntLiteralSuffix 36 | 37 | 38 | DecimalBigIntegerLiteral::0BigIntLiteralSuffix 39 | NonZeroDigitDecimalDigitsoptBigIntLiteralSuffix 40 | NonZeroDigitNumericLiteralSeparatorDecimalDigitsBigIntLiteralSuffix 41 | 42 | 43 | NonDecimalIntegerLiteral::BinaryIntegerLiteral 44 | OctalIntegerLiteral 45 | HexIntegerLiteral 46 | 47 | 48 | BigIntLiteralSuffix::n 49 | 50 | 51 | DecimalLiteral::DecimalIntegerLiteral.DecimalDigitsoptExponentPartopt 52 | .DecimalDigitsExponentPartopt 53 | DecimalIntegerLiteralExponentPartopt 54 | 55 | 56 | DecimalIntegerLiteral::0 57 | NonZeroDigitDecimalDigitsopt 58 | NonZeroDigit 59 | NonZeroDigitNumericLiteralSeparatoroptDecimalDigits 60 | 61 | 62 | DecimalDigits::DecimalDigit 63 | DecimalDigitsNumericLiteralSeparatoroptDecimalDigit 64 | 65 | 66 | DecimalDigit::one of0123456789 67 | 68 | 69 | NonZeroDigit::one of123456789 70 | 71 | 72 | ExponentPart::ExponentIndicatorSignedInteger 73 | 74 | 75 | ExponentIndicator::one ofeE 76 | 77 | 78 | SignedInteger::DecimalDigits 79 | +DecimalDigits 80 | -DecimalDigits 81 | 82 | 83 | BinaryIntegerLiteral::0bBinaryDigits 84 | 0BBinaryDigits 85 | 86 | 87 | BinaryDigits::BinaryDigit 88 | BinaryDigitsNumericLiteralSeparatoroptBinaryDigit 89 | 90 | 91 | BinaryDigit::one of01 92 | 93 | 94 | OctalIntegerLiteral::0oOctalDigits 95 | 0OOctalDigits 96 | 97 | 98 | OctalDigits::OctalDigit 99 | OctalDigitsNumericLiteralSeparatoroptOctalDigit 100 | 101 | 102 | OctalDigit::one of01234567 103 | 104 | 105 | HexIntegerLiteral::0xHexDigits 106 | 0XHexDigits 107 | 108 | 109 | HexDigits::HexDigit 110 | HexDigitsNumericLiteralSeparatoroptHexDigit 111 | 112 | 113 | HexDigit::one of0123456789abcdefABCDEF 114 | 115 |

    The SourceCharacter immediately following a NumericLiteral must not be an IdentifierStart or DecimalDigit.

    116 | Note
    117 |

    For example: 3in is an error and not the two input elements 3 and in.

    118 |
    119 |

    A conforming implementation, when processing strict mode code, must not extend, as described in C.1.1, the syntax of NumericLiteral to include LegacyOctalIntegerLiteral, nor extend the syntax of DecimalIntegerLiteral to include NonOctalDecimalIntegerLiteral.

    120 | 121 | 122 | 123 |

    1.1 Static Semantics: MV

    124 |

    A numeric literal stands for a value of the Number type or the BigInt type.

    125 | 467 |
    468 | 469 | 470 |

    1.2 Static Semantics: NumericValue

    471 |

    ...

    472 | 473 | 474 | DecimalBigIntegerLiteral::NonZeroDigitDecimalDigitsBigIntLiteralSuffix 475 | 476 | 477 | 478 | 479 | DecimalBigIntegerLiteral::NonZeroDigitDecimalDigitsBigIntLiteralSuffix 480 | NonZeroDigitNumericLiteralSeparatorDecimalDigitsBigIntLiteralSuffix 481 | 482 | 483 |
    1. Let n be the mathematical integer number of code points in DecimalDigits.
    2. Let mv be (the MV of NonZeroDigit × 10n) plus the MV of DecimalDigits.
    3. Return the BigInt value that represents mv.
    484 |
    485 |
    486 | 487 | 488 | 489 |

    2 ToNumber Applied to the String Type

    490 |

    ToNumber applied to Strings applies the following grammar to the input String interpreted as a sequence of UTF-16 encoded code points (6.1.4). If the grammar cannot interpret the String as an expansion of StringNumericLiteral, then the result of ToNumber is NaN.

    491 | Note
    492 |

    The terminal symbols of this grammar are all composed of characters in the Unicode Basic Multilingual Plane (BMP). Therefore, the result of ToNumber will be NaN if the string contains any leading surrogate or trailing surrogate code units, whether paired or unpaired.

    493 |
    494 |

    Syntax

    495 | 496 | 497 | StringNumericLiteral:::StrWhiteSpaceopt 498 | StrWhiteSpaceoptStrNumericLiteralStrWhiteSpaceopt 499 | 500 | 501 | StrWhiteSpace:::StrWhiteSpaceCharStrWhiteSpaceopt 502 | 503 | 504 | StrWhiteSpaceChar:::WhiteSpace 505 | LineTerminator 506 | 507 | 508 | StrNumericLiteral:::StrDecimalLiteral 509 | NonDecimalIntegerLiteral 510 | StrBinaryLiteral 511 | StrOctalLiteral 512 | StrHexLiteral 513 | 514 | 515 | StrDecimalLiteral:::StrUnsignedDecimalLiteral 516 | +StrUnsignedDecimalLiteral 517 | -StrUnsignedDecimalLiteral 518 | 519 | 520 | StrUnsignedDecimalLiteral:::Infinity 521 | DecimalDigits.DecimalDigitsoptExponentPartopt 522 | .DecimalDigitsExponentPartopt 523 | DecimalDigitsExponentPartopt 524 | StrDecimalDigits.StrDecimalDigitsoptStrExponentPartopt 525 | .StrDecimalDigitsStrExponentPartopt 526 | StrDecimalDigitsStrExponentPartopt 527 | 528 | 529 | StrDecimalDigits:::DecimalDigit 530 | StrDecimalDigitsDecimalDigit 531 | 532 | 533 | StrExponentPart:::ExponentIndicatorStrSignedInteger 534 | 535 | 536 | StrSignedInteger:::StrDecimalDigits 537 | +StrDecimalDigits 538 | -StrDecimalDigits 539 | 540 | 541 | StrBinaryLiteral:::0bStrBinaryDigits 542 | 0BStrBinaryDigits 543 | 544 | 545 | StrBinaryDigits:::BinaryDigit 546 | StrBinaryDigitsBinaryDigit 547 | 548 | 549 | StrOctalLiteral:::0oStrOctalDigits 550 | 0OStrOctalDigits 551 | 552 | 553 | StrOctalDigits:::OctalDigit 554 | StrOctalDigitsOctalDigit 555 | 556 | 557 | StrHexLiteral:::0xStrHexDigits 558 | 0XStrHexDigits 559 | 560 | 561 | StrHexDigits:::HexDigit 562 | StrHexDigitsHexDigit 563 | 564 | 565 | 566 | 567 |

    2.1 Runtime Semantics: MV

    568 |

    The conversion of a String to a Number value is similar overall to the determination of the Number value for a numeric literal (see 1), but some of the details are different, so the process for converting a String numeric literal to a value of Number type is given here. This value is determined in two steps: first, a mathematical value (MV) is derived from the String numeric literal; second, this mathematical value is rounded as described below. The MV on any grammar symbol, not provided below, is the MV for that symbol defined in 1.1.

    569 | 813 |

    Once the exact MV for a String numeric literal has been determined, it is then rounded to a value of the Number type. If the MV is 0, then the rounded value is +0 unless the first non white space code point in the String numeric literal is -, in which case the rounded value is -0. Otherwise, the rounded value must be the Number value for the MV (in the sense defined in 6.1.6.1), unless the literal includes a StrUnsignedDecimalLiteral and the literal has more than 20 significant digits, in which case the Number value may be either the Number value for the MV of a literal produced by replacing each significant digit after the 20th with a 0 digit or the Number value for the MV of a literal produced by replacing each significant digit after the 20th with a 0 digit and then incrementing the literal at the 20th digit position. A digit is significant if it is not part of an ExponentPart and

    814 |
      815 |
    • 816 | it is not 0; or 817 |
    • 818 |
    • 819 | there is a nonzero digit to its left and there is a nonzero digit, not in the ExponentPart, to its right. 820 |
    • 821 |
    822 |
    823 |
    824 | 825 | 826 |

    A Template Literal Lexical Components

    827 |

    Syntax

    828 | 829 | CodePoint::HexDigitsbut only if MV of HexDigits ≤ 0x10FFFF 830 | CodePointDigitsbut only if MV of HexDigits ≤ 0x10FFFF 831 | 832 | 833 | CodePointDigits::HexDigit 834 | CodePointDigitsHexDigit 835 | 836 |
    837 | 838 | 839 | 840 |

    B Grammar Summary

    841 |

    Placeholder for Annex A

    842 |
    843 | 844 | 845 |

    C Additional ECMAScript Features for Web Browsers

    846 |

    ...

    847 | 848 | 849 |

    C.1 Additional Syntax

    850 | 851 | 852 |

    C.1.1 Numeric Literals

    853 |

    The syntax and semantics of 1 is extended as follows except that this extension is not allowed for strict mode code:

    854 |

    Syntax

    855 | 856 | NumericLiteral::DecimalLiteral 857 | BinaryIntegerLiteral 858 | OctalIntegerLiteral 859 | HexIntegerLiteral 860 | LegacyOctalIntegerLiteral 861 | 862 | 863 | LegacyOctalIntegerLiteral::0OctalDigit 864 | LegacyOctalIntegerLiteralOctalDigit 865 | 866 | 867 | DecimalIntegerLiteral::0 868 | NonZeroDigitDecimalDigitsopt 869 | NonZeroDigit 870 | NonZeroDigitNumericLiteralSeparatoroptDecimalDigits 871 | NonOctalDecimalIntegerLiteral 872 | 873 | 874 | NonOctalDecimalIntegerLiteral::0NonOctalDigit 875 | LegacyOctalLikeDecimalIntegerLiteralNonOctalDigit 876 | NonOctalDecimalIntegerLiteralDecimalDigit 877 | 878 | 879 | LegacyOctalLikeDecimalIntegerLiteral::0OctalDigit 880 | LegacyOctalLikeDecimalIntegerLiteralOctalDigit 881 | 882 | 883 | NonOctalDigit::one of89 884 | 885 |
    886 |
    887 |
    888 |
    -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "test": ":", 5 | "build": "mkdir -p docs; ecmarkup --verbose spec.html docs/index.html --css docs/ecmarkup.css --js docs/ecmarkup.js" 6 | }, 7 | "devDependencies": { 8 | "@alrra/travis-scripts": "^3.0.1", 9 | "ecmarkup": "^3.25.3" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /spec.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
      4 | title: Numeric separators
      5 | status: proposal
      6 | stage: 3
      7 | location: https://github.com/tc39/proposal-numeric-separator
      8 | copyright: false
      9 | contributors: Sam Goto, Rick Waldron, Leo Balter, Christophe Porteneuve, Adam Klein, Domenic Denicola
     10 | 
    11 | 12 | 13 | 28 | 29 |

    The grammar listed in 11.8.3 is modified as follows.

    30 | 31 | 32 | 33 |

    Numeric Literals

    34 |

    Syntax

    35 | 36 | 37 | 38 | NumericLiteralSeparator :: 39 | `_` 40 | 41 | 42 | NumericLiteral :: 43 | DecimalLiteral 44 | DecimalBigIntegerLiteral 45 | NonDecimalIntegerLiteral 46 | NonDecimalIntegerLiteral BigIntLiteralSuffix 47 | 48 | DecimalBigIntegerLiteral :: 49 | `0` BigIntLiteralSuffix 50 | NonZeroDigit DecimalDigits? BigIntLiteralSuffix 51 | NonZeroDigit NumericLiteralSeparator DecimalDigits BigIntLiteralSuffix 52 | 53 | NonDecimalIntegerLiteral :: 54 | BinaryIntegerLiteral 55 | OctalIntegerLiteral 56 | HexIntegerLiteral 57 | 58 | BigIntLiteralSuffix :: 59 | `n` 60 | 61 | DecimalLiteral :: 62 | DecimalIntegerLiteral `.` DecimalDigits? ExponentPart? 63 | `.` DecimalDigits ExponentPart? 64 | DecimalIntegerLiteral ExponentPart? 65 | 66 | DecimalIntegerLiteral :: 67 | `0` 68 | NonZeroDigit DecimalDigits? 69 | 70 | NonZeroDigit 71 | NonZeroDigit NumericLiteralSeparator? DecimalDigits 72 | 73 | 74 | DecimalDigits :: 75 | DecimalDigit 76 | DecimalDigits NumericLiteralSeparator? DecimalDigit 77 | 78 | DecimalDigit :: one of 79 | `0` `1` `2` `3` `4` `5` `6` `7` `8` `9` 80 | 81 | NonZeroDigit :: one of 82 | `1` `2` `3` `4` `5` `6` `7` `8` `9` 83 | 84 | ExponentPart :: 85 | ExponentIndicator SignedInteger 86 | 87 | ExponentIndicator :: one of 88 | `e` `E` 89 | 90 | SignedInteger :: 91 | DecimalDigits 92 | `+` DecimalDigits 93 | `-` DecimalDigits 94 | 95 | BinaryIntegerLiteral :: 96 | `0b` BinaryDigits 97 | `0B` BinaryDigits 98 | 99 | BinaryDigits :: 100 | BinaryDigit 101 | BinaryDigits NumericLiteralSeparator? BinaryDigit 102 | 103 | BinaryDigit :: one of 104 | `0` `1` 105 | 106 | OctalIntegerLiteral :: 107 | `0o` OctalDigits 108 | `0O` OctalDigits 109 | 110 | OctalDigits :: 111 | OctalDigit 112 | OctalDigits NumericLiteralSeparator? OctalDigit 113 | 114 | OctalDigit :: one of 115 | `0` `1` `2` `3` `4` `5` `6` `7` 116 | 117 | HexIntegerLiteral :: 118 | `0x` HexDigits 119 | `0X` HexDigits 120 | 121 | HexDigits :: 122 | HexDigit 123 | HexDigits NumericLiteralSeparator? HexDigit 124 | 125 | HexDigit :: one of 126 | `0` `1` `2` `3` `4` `5` `6` `7` `8` `9` `a` `b` `c` `d` `e` `f` `A` `B` `C` `D` `E` `F` 127 | 128 |

    The |SourceCharacter| immediately following a |NumericLiteral| must not be an |IdentifierStart| or |DecimalDigit|.

    129 | 130 |

    For example: `3in` is an error and not the two input elements `3` and `in`.

    131 |
    132 |

    A conforming implementation, when processing strict mode code, must not extend, as described in , the syntax of |NumericLiteral| to include |LegacyOctalIntegerLiteral|, nor extend the syntax of |DecimalIntegerLiteral| to include |NonOctalDecimalIntegerLiteral|.

    133 | 134 | 135 | 136 |

    Static Semantics: MV

    137 |

    A numeric literal stands for a value of the Number type or the BigInt type.

    138 | 302 |
    303 | 304 | 305 |

    Static Semantics: NumericValue

    306 |

    ...

    307 | 308 | DecimalBigIntegerLiteral :: NonZeroDigit DecimalDigits BigIntLiteralSuffix 309 | 310 | 311 | 312 | DecimalBigIntegerLiteral :: 313 | NonZeroDigit DecimalDigits BigIntLiteralSuffix 314 | NonZeroDigit NumericLiteralSeparator DecimalDigits BigIntLiteralSuffix 315 | 316 | 317 | 318 | 1. Let _n_ be the mathematical integer number of code points in |DecimalDigits|. 319 | 1. Let _mv_ be (the MV of |NonZeroDigit| × 10_n_) plus the MV of |DecimalDigits|. 320 | 1. Return the BigInt value that represents _mv_. 321 | 322 |
    323 |
    324 | 325 | 326 | 327 |

    ToNumber Applied to the String Type

    328 |

    ToNumber applied to Strings applies the following grammar to the input String interpreted as a sequence of UTF-16 encoded code points (). If the grammar cannot interpret the String as an expansion of |StringNumericLiteral|, then the result of ToNumber is *NaN*.

    329 | 330 |

    The terminal symbols of this grammar are all composed of characters in the Unicode Basic Multilingual Plane (BMP). Therefore, the result of ToNumber will be *NaN* if the string contains any or code units, whether paired or unpaired.

    331 |
    332 |

    Syntax

    333 | 334 | 335 | StringNumericLiteral ::: 336 | StrWhiteSpace? 337 | StrWhiteSpace? StrNumericLiteral StrWhiteSpace? 338 | 339 | StrWhiteSpace ::: 340 | StrWhiteSpaceChar StrWhiteSpace? 341 | 342 | StrWhiteSpaceChar ::: 343 | WhiteSpace 344 | LineTerminator 345 | 346 | StrNumericLiteral ::: 347 | StrDecimalLiteral 348 | NonDecimalIntegerLiteral 349 | 350 | StrBinaryLiteral 351 | StrOctalLiteral 352 | StrHexLiteral 353 | 354 | 355 | StrDecimalLiteral ::: 356 | StrUnsignedDecimalLiteral 357 | `+` StrUnsignedDecimalLiteral 358 | `-` StrUnsignedDecimalLiteral 359 | 360 | StrUnsignedDecimalLiteral ::: 361 | `Infinity` 362 | 363 | DecimalDigits `.` DecimalDigits? ExponentPart? 364 | `.` DecimalDigits ExponentPart? 365 | DecimalDigits ExponentPart? 366 | 367 | 368 | StrDecimalDigits `.` StrDecimalDigits? StrExponentPart? 369 | `.` StrDecimalDigits StrExponentPart? 370 | StrDecimalDigits StrExponentPart? 371 | 372 | 373 | 374 | StrDecimalDigits ::: 375 | DecimalDigit 376 | StrDecimalDigits DecimalDigit 377 | 378 | StrExponentPart ::: 379 | ExponentIndicator StrSignedInteger 380 | 381 | StrSignedInteger ::: 382 | StrDecimalDigits 383 | `+` StrDecimalDigits 384 | `-` StrDecimalDigits 385 | 386 | StrBinaryLiteral ::: 387 | `0b` StrBinaryDigits 388 | `0B` StrBinaryDigits 389 | 390 | StrBinaryDigits ::: 391 | BinaryDigit 392 | StrBinaryDigits BinaryDigit 393 | 394 | StrOctalLiteral ::: 395 | `0o` StrOctalDigits 396 | `0O` StrOctalDigits 397 | 398 | StrOctalDigits ::: 399 | OctalDigit 400 | StrOctalDigits OctalDigit 401 | 402 | StrHexLiteral ::: 403 | `0x` StrHexDigits 404 | `0X` StrHexDigits 405 | 406 | StrHexDigits ::: 407 | HexDigit 408 | StrHexDigits HexDigit 409 | 410 | 411 | 412 | 413 | 414 | 415 |

    Runtime Semantics: MV

    416 |

    The conversion of a String to a Number value is similar overall to the determination of the Number value for a numeric literal (see ), but some of the details are different, so the process for converting a String numeric literal to a value of Number type is given here. This value is determined in two steps: first, a mathematical value (MV) is derived from the String numeric literal; second, this mathematical value is rounded as described below. The MV on any grammar symbol, not provided below, is the MV for that symbol defined in .

    417 | 589 |

    Once the exact MV for a String numeric literal has been determined, it is then rounded to a value of the Number type. If the MV is 0, then the rounded value is *+0* unless the first non white space code point in the String numeric literal is `-`, in which case the rounded value is *-0*. Otherwise, the rounded value must be the Number value for the MV (in the sense defined in ), unless the literal includes a |StrUnsignedDecimalLiteral| and the literal has more than 20 significant digits, in which case the Number value may be either the Number value for the MV of a literal produced by replacing each significant digit after the 20th with a 0 digit or the Number value for the MV of a literal produced by replacing each significant digit after the 20th with a 0 digit and then incrementing the literal at the 20th digit position. A digit is significant if it is not part of an |ExponentPart| and

    590 | 598 |
    599 |
    600 | 601 | 602 |

    Template Literal Lexical Components

    603 |

    Syntax

    604 | 605 | CodePoint :: 606 | HexDigits [> but only if MV of |HexDigits| ≤ 0x10FFFF] 607 | CodePointDigits [> but only if MV of |HexDigits| ≤ 0x10FFFF] 608 | 609 | 610 | CodePointDigits :: 611 | HexDigit 612 | CodePointDigits HexDigit 613 | 614 | 615 |
    616 | 617 | 618 | 619 |

    Grammar Summary

    620 |

    Placeholder for Annex A

    621 |
    622 | 623 | 624 |

    Additional ECMAScript Features for Web Browsers

    625 |

    ...

    626 | 627 | 628 |

    Additional Syntax

    629 | 630 | 631 |

    Numeric Literals

    632 |

    The syntax and semantics of is extended as follows except that this extension is not allowed for strict mode code:

    633 |

    Syntax

    634 | 635 | NumericLiteral :: 636 | DecimalLiteral 637 | BinaryIntegerLiteral 638 | OctalIntegerLiteral 639 | HexIntegerLiteral 640 | LegacyOctalIntegerLiteral 641 | 642 | LegacyOctalIntegerLiteral :: 643 | `0` OctalDigit 644 | LegacyOctalIntegerLiteral OctalDigit 645 | 646 | DecimalIntegerLiteral :: 647 | `0` 648 | NonZeroDigit DecimalDigits? 649 | NonZeroDigit 650 | NonZeroDigit NumericLiteralSeparator? DecimalDigits 651 | NonOctalDecimalIntegerLiteral 652 | 653 | NonOctalDecimalIntegerLiteral :: 654 | `0` NonOctalDigit 655 | LegacyOctalLikeDecimalIntegerLiteral NonOctalDigit 656 | NonOctalDecimalIntegerLiteral DecimalDigit 657 | 658 | LegacyOctalLikeDecimalIntegerLiteral :: 659 | `0` OctalDigit 660 | LegacyOctalLikeDecimalIntegerLiteral OctalDigit 661 | 662 | NonOctalDigit :: one of 663 | `8` `9` 664 | 665 |
    666 |
    667 |
    668 | -------------------------------------------------------------------------------- /spec.md: -------------------------------------------------------------------------------- 1 | # Semantics 2 | 3 | This feature is designed to have no impact on the interpretation semantics of numeric literals: `_` are to be ignored by interpreters and should have no effect. They are meant **exclusively** as a visual clue to aid development and have no runtime semantics. 4 | 5 | ## Syntax 6 | 7 | The following grammar represents the Stage 1 criteria, which is: 8 | 9 | 1. No leading or trailing separator. 10 | 2. No multiple adjacent separator. 11 | 3. No separator adjacent to decimal point `.` 12 | 4. No separator adjacent to _ExponentIndicator_. 13 | 5. No separator adjacent to `0b`, `0B`, `0o`, `0O`, `0x`, `0X`. 14 | 15 | Changes to [Numeric Literals](https://tc39.es/ecma262/#prod-NumericLiteral) shown in rendered proposal. 16 | 17 | Changes to [ToNumber Applied to the String Type](https://tc39.es/ecma262/#sec-tonumber-applied-to-the-string-type) & [Runtime Semantics: MV](https://tc39.es/ecma262/#sec-runtime-semantics-mv-s) shown in rendered proposal. 18 | 19 | Changes to [Runtime Semantics: MV](https://tc39.es/ecma262/#sec-runtime-semantics-mv-s) shown in rendered proposal. 20 | 21 | ### The Number Constructor, Number ( value ) 22 | 23 | [**The Number constructor**](https://tc39.es/ecma262/#sec-number-constructor-number-value) and [**Number(value)**](https://tc39.es/ecma262/#sec-number-constructor-number-value) both rely on [**ToNumber**](https://tc39.es/ecma262/#sec-tonumber), so there is no change needed. 24 | 25 | ### ToNumber ( argument ) 26 | 27 | The abstract operation [ToNumber ( argument )](https://tc39.es/ecma262/#sec-tonumber) requires no change since it relies on the BinaryIntegerLiteral, OctalIntegerLiteral, HexIntegerLiteral and DecimalDigits changed above. 28 | 29 | ### ToNumber Applied to the String Type, Runtime Semantics: MV 30 | 31 | The syntax defined in [ToNumber Applied to the String Type](https://tc39.es/ecma262/#sec-tonumber-applied-to-the-string-type) is adjusted to use the newly defined _StrDecimalDigits_, preserving the behavior of [parseFloat( string )](https://tc39.es/ecma262/#sec-parsefloat-string). [Runtime Semantics: MV](https://tc39.es/ecma262/#sec-runtime-semantics-mv-s) are also updated by replacing _DecimalDigits_ with _StrDecimalDigits_. 32 | 33 | ### Global object functions `isFinite` and `isNaN` 34 | 35 | Both rely on *ToNumber* semantics (18.2.2 and 18.2.3), so they adjust automatically. 36 | 37 | ### Global object functions `parseInt` and `parseFloat` 38 | 39 | `parseFloat` semantics are unchanged. The syntax for _StrDecimalLiteral_ is updated to define its own _StrDecimalDigits_, preserving the behavior of "parseFloat applied to the String type". 40 | 41 | `parseInt` semantics are unchanged. 42 | 43 | ### Methods of the `Number` constructor 44 | 45 | All detection methods (`isXx` methods) operate on actual number values, such as number literals, so they do not introduce extra conversion / parsing rules. The `parseFloat` and `parseInt` methods are just convenience aliases over their homonyms in the global object. 46 | --------------------------------------------------------------------------------