├── .github └── workflows │ ├── build.yml │ └── deploy.yml ├── .gitignore ├── .npmrc ├── LICENSE ├── README.md ├── package.json └── spec.emu /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build spec 2 | 3 | on: [pull_request, push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v3 11 | - uses: ljharb/actions/node/install@main 12 | name: 'nvm install lts/* && npm install' 13 | with: 14 | node-version: lts/* 15 | - run: npm run build 16 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy gh-pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: ljharb/actions/node/install@main 15 | name: 'nvm install lts/* && npm install' 16 | with: 17 | node-version: lts/* 18 | - run: npm run build 19 | - uses: JamesIves/github-pages-deploy-action@v4.3.3 20 | with: 21 | branch: gh-pages 22 | folder: build 23 | clean: true 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # Only apps should have lockfiles 40 | yarn.lock 41 | package-lock.json 42 | npm-shrinkwrap.json 43 | pnpm-lock.yaml 44 | 45 | # Build directory 46 | build 47 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 ECMA TC39 and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Improve template literals 2 | 3 | ## TLDR. 4 | 5 | ```javascript 6 | let query = ` 7 | select * 8 | from \`users\` 9 | where \`name\` = ? 10 | ` 11 | ``` 12 | 13 | Escaping is annoying, and `String.raw` or `String.dedent` doesn't help in this case. We need syntax to solve the issue: 14 | 15 | ````c# 16 | // syntax TBD, just use @sken130 's strawperson draft as demo 17 | let query = @`` 18 | select * 19 | from `users` 20 | where `name` = ? 21 | `` 22 | ```` 23 | 24 | ## Status 25 | 26 | - Stage: 1 27 | - Champion: HE Shi-Jun (hax) 28 | - Authors: @hax, @sken130 29 | 30 | ## Motivation 31 | 32 | JavaScript lacks a general way to create simple string literals that can contain effectively any arbitrary text. Using template literals with `String.raw` built-in tag function could avoid most escaping, but unfortunately can't include `` ` `` and `${` verbatimly as they are delimiters of template literals. This prevents easily having literals containing other programming languages (notably, JavaScript itself) and text formats (for example, Markdown) in them. 33 | 34 | All current approaches to form these literals in JavaScript today always force the user to manually escape the contents, or use some other hacks. Editing at that point can be highly annoying as the escaping cannot be avoided and must be dealt with whenever it arises in the contents. This is particularly painful for the text contains both backslashes and backticks, or the multiple lines text contains backticks. 35 | 36 | The crux of the problem is that all our strings have a fixed start/end delimiter. As long as that is the case, we will always have to use an escaping or substitution mechanism as the string contents may need to specify that end delimiter in their contents. This is particularly problematic as that delimiter `` ` `` is common in many languages. 37 | 38 | To address this, this proposal allows for flexible start and end delimiters so that they can always be made in a way that will not conflict with the content of the string. 39 | 40 | ## Core Goals 41 | 42 | 1. Provide a mechanism that will allow *all* string values to be provided by the user without the need for *any* escape-sequences or substitutions whatsoever. Because all strings must be representable without escape-sequences or substitutions, it must always be possible for the user to specify delimiters that will be guaranteed to not collide with any text contents. 43 | 2. Support interpolations in the same fashion. As above, because *all* strings must be representable without escapes or substitutions, it must always be possible for the user to specify an interpolation delimiter that will be guaranteed to not collide with any text contents. 44 | 3. Support tag functions as current tagged template literals do. 45 | 46 | ## Extra Goals (if possible) 47 | 4. Multiline string literals should look pleasant in code and should not make indentation within the compilation unit look strange. Importantly, literal values that themselves have no indentation should not be forced to occupy the first column of the file as that can break up the flow of code and will look unaligned with the rest of the code that surrounds it. This behavior should be easy to override while keeping literals clear and easy to read. 48 | - Note, this goal might also be meet with `String.dedent` proposal, but syntax solution likely has better ergonomics and other benefits. 49 | 5. Current usages of template literals should be easily migrate to the new syntax. Importantly, if a template literal does not need to present `${` characters, it should not be forced to change interpolation syntax. 50 | 6. Nice to have mechnism like [Markdown info string](https://spec.commonmark.org/0.30/#info-string), which can be leavaged by tools (for example, use for syntax highlighting). 51 | 7. Nice to have mechnism to do comments. (though people could abuse interpolations for that purpose) 52 | 8. Nice to have mechnism to enable escaping in specified place. (Swift support that.) 53 | 9. To nest JS code, it will be better if the syntax allow short delimeter outside, long delimeter inside. So when you write nested JS code, you don't need to go back to the start and change the delimiter. This is also help LLM AI to generate JS code, because current LLM can't backtrace and modify the codes they already output. 54 | 55 | ## Possible Solution 56 | 57 | There are many possible syntax options, here document style syntax, Swift/Rust style (```#`raw string`#```) syntax, Markdown style syntax, etc. The champion plan to investigate different syntax options when the proposal approved as stage 1. 58 | 59 | Currently let's just use @sken130 's strawperson draft which heavily inspired by C# 11 raw string literal syntax. 60 | 61 | ### 1\. The string sequence should start with an `@` and at least (can be more than) 2 backtick characters, and close with an equal number of backtick characters without `@`. 62 | 63 | ````````````````js 64 | const str = @`` 65 | I am a string 66 | `` 67 | ```````````````` 68 | 69 | ### 2\. These patterns don't need to be escaped 70 | 71 | Obviously, characters such as @, double quote, single quote, and backslash won't need to be escaped 72 | 73 | ````````````````js 74 | const mysqlQuery = @`` 75 | SELECT * FROM `Strange table name` where `strange column name` = 'abcde'; 76 | `` 77 | ```````````````` 78 | 79 | ````````````````js 80 | const str = @`` 81 | I would like to request "2 leave days" that start from '11/26/2022' and end on '11/27/2022'. 82 | \_/ 83 | If any enquiry, please send email to ken@example.com 84 | `` 85 | ```````````````` 86 | The @, ", ', and \ characters will be stored in str as-is, as visibly shown in source code, no need \", \', \\. 87 | 88 | 1 backtick character doesn't need to be escaped either 89 | 90 | ````````````````js 91 | const str = @`` 92 | I would like to request `2 leave days` that ...... 93 | `` 94 | ```````````````` 95 | And the 96 | ``` 97 | @` 98 | ``` 99 | sequence doesn't need to be escaped either, since it is not equal to the closing delimiter. 100 | 101 | ### 3\. Here comes the problem: what if we want to embed 2 or more backtick characters without escaping? In such case, the raw string literal needs to start and end with more backtick characters: 102 | 103 | Embedding 2 backtick characters would require opening with an @ and 3 backticks and closing with 3 backticks: 104 | ````````````````js 105 | const javaScriptTutorial = @``` 106 | const emptyString = `` // Yay, backtick quotes can be an empty string too 107 | ``` 108 | ```````````````` 109 | (the 2 backtick ``` characters will be represented as-is, without the need of escaping) 110 | 111 | Embedding 3 backtick characters (a common example is embedding markdown in JavaScript) would require opening with an @ and 4 backticks and closing with 4 backticks: 112 | ````````````````js 113 | const markdownExample = @```` 114 | ```json 115 | { 116 | "firstName": "John", 117 | "lastName": "Smith", 118 | "age": 25 119 | } 120 | ``` 121 | ```` 122 | ```````````````` 123 | (the 3 backtick ``` characters will be represented as-is, without the need of escaping) 124 | 125 | If we want to represent 4 backticks in the content without escaping, we need to delimit the raw string by 5 backtick characters, and so on. 126 | In this design, we can put arbitrary number of backticks in the content as long as we delimit the raw string by opening and closing with more backtick characters. 127 | Such syntax should not be clumsy, as such kind of strings are rare (we want to cover all corner cases though). 128 | 129 | ### 4\. Indentation - Any whitespace to the left of the closing delimiter (```, or ````, or `````, ...) will be removed from the string literal 130 | 131 | ````````````````js 132 | const markdownExample = @```` 133 | |```json 134 | |{ 135 | | "firstName": "John", 136 | | "lastName": "Smith", 137 | | "age": 25 138 | |} 139 | |``` 140 | | 141 | ```` 142 | ```````````````` 143 | Note: The | characters are not really in the string, they're for illustrating how the string is indented and that the whitespaces at | and on the left of | are not captured in the string. 144 | 145 | It is equivalent to 146 | ````````````````js 147 | const markdownExample = @```` 148 | ```json 149 | { 150 | "firstName": "John", 151 | "lastName": "Smith", 152 | "age": 25 153 | } 154 | ``` 155 | 156 | ```` 157 | ```````````````` 158 | 159 | Any characters on the left of the closing delimiter will trigger compilation error: 160 | ````````````````js 161 | const markdownExample = @```` 162 | ```json 163 | { 164 | "firstName": "John", 165 | "lastName": "Smith", 166 | "age": 25 167 | } 168 | ``` 169 | a // The character a is illegal here and should give compilation error rather than ignoring it. 170 | b ```` // The character b is also illegal here, the closing delimiter must be on its own line. Should give compilation error rather than ignoring it. 171 | ```````````````` 172 | 173 | ### 5\. Indentation - indentation whitespace must be consistent 174 | 175 | If the closing delimiter has 8 spaces to the left, then all lines in the content must start with 8 spaces, not tab characters. They can have tab or space characters after the initial 8 spaces though. 176 | 177 | If the closing delimiter has 2 tabs to the left, then all lines in the content must start with 2 tabs, not space characters. They can have space or tab characters after the initial 2 tabs though. 178 | 179 | ### 6\. Interpolation - You know it 180 | 181 | ````````````````js 182 | let s = @`` 183 | My name is ${myName} 184 | `` 185 | ```````````````` 186 | 187 | ### 7\. What if I want to include ${myName} as part of the content, not interpolation? 188 | 189 | The answer borrowed from C# 11 raw string literal is, to include more @ characters at the beginning delimiter. 190 | 191 | The number of @ characters at the opening delimiter, will control how many $ characters are needed to start an interpolation: 192 | ````````````````js 193 | let s1 = @@`` 194 | My name is ${myName} 195 | `` // No interpolation, ${myName} is now stored in the string as-is 196 | let s1 = @@`` 197 | My name is $${myName} 198 | `` // Interpolation will happen 199 | ```````````````` 200 | 201 | ````````````````js 202 | const myName = "Ann" 203 | console.log(@@`` 204 | JavaScript tutorial: If you write console.log(`my name is ${myName}`), it will print "my name is $${myName}" in the results (not including "") 205 | ``) 206 | ```````````````` 207 | 208 | will print 209 | ``` 210 | JavaScript tutorial: If you write console.log(`my name is ${myName}`), it will print "my name is Ann" in the results (not including "") 211 | ``` 212 | 213 | More examples 214 | ````````````````js 215 | @@@``` 216 | My name is $${myName} 217 | ``` // No interpolation, ${myName} is now stored in the string as-is 218 | @@@``` 219 | My name is $$${myName} 220 | ``` // Interpolation will happen 221 | ```````````````` 222 | 223 | ### 8\. Tagged string 224 | 225 | ````````````````js 226 | let result = tag@`` 227 | should also support tag function 228 | `` 229 | ```````````````` 230 | 231 | ## 9\. More rules on multi-line raw string literal 232 | 233 | - Both opening and closing quote characters must be on different lines. 234 | - Whitespace following the opening quote on the same line is ignored. 235 | - Any non-whitespace characters (except comments) following the opening quote on the same line are illegal, and will be treated as unterminated single-line raw string literal. 236 | - Whitespace only lines below the opening quote are included in the string literal. 237 | 238 | ### 10. Why this syntax 239 | 240 | #### Why not just open with ``` 241 | 242 | Because ``` is not fully backwards compatible. See https://github.com/tc39/proposal-string-dedent/issues/40, and https://github.com/tc39/proposal-string-dedent/issues/8, and https://gist.github.com/michaelficarra/70ce798feb25fdc91508f387190053a1, and my reply before this proposal https://es.discourse.group/t/raw-string-literals-that-can-contain-any-arbitrary-text-without-the-need-for-special-escape-sequences/1757/2 243 | 244 | #### Why the @ character for opening delimiter 245 | 246 | Because @``, @```, @````, ... are never valid JavaScript syntax before, so no backwards compatibility issue. 247 | 248 | We also can't use $\`\`, $\`\`\`, ..., as $ is a valid variable identifier (jQuery). 249 | 250 | #### Why not just use other delimiter character/sequence than backtick, double quotes or single quote? 251 | 252 | - If the delimiter character/sequence is fixed, no matter what delimiting character/sequence you choose, you cannot embed the closing delimiter character/sequence in the content without escaping. One of the goals of this feature, is to be able to embed arbitrary text without escaping, and to do so, the opening and closing delimiter sequence have to be flexible. 253 | - We may use other flexible sequence than @``, but not sure if this would waste the room of possible syntaxes for other future enhancements. 254 | 255 | ### 11. Use cases of raw string literal 256 | 257 | #### (a) MySQL query 258 | 259 | With MySQL, the following situations are common 260 | 261 | ````js 262 | const searchUser = () => { 263 | const query = `select * 264 | from \`users\` 265 | where \`name\` = ?` 266 | } 267 | ```` 268 | Allowing raw string representation without escaping, and with proper indentation, will make it less troublesome to maintain and less error prone. 269 | ````js 270 | const searchUser = () => { 271 | const query = @`` 272 | select * 273 | from `users` 274 | where `name` = ? 275 | `` 276 | ...... 277 | } 278 | ```` 279 | 280 | #### (b) Markdown embedding or generation 281 | 282 | Without raw string literal feature, we have to do this (have to escape each backtick): 283 | ````````js 284 | const generateMarkdown = () => { 285 | const markdownExample = `\`\`\`json 286 | { 287 | "firstName": "John", 288 | "lastName": "Smith", 289 | "age": 25 290 | } 291 | \`\`\` 292 | ` 293 | doSomething(markdownExample) // Output/process the markdown somewhere 294 | } 295 | ```````` 296 | or this (have to escape each double quote): 297 | ````````js 298 | const generateMarkdown = () => { 299 | const markdownExample = [ 300 | "```json", 301 | "{", 302 | " \"firstName\": \"John\",", 303 | " \"lastName\": \"Smith\",", 304 | " \"age\": 25", 305 | "}", 306 | "```" 307 | ].join("\n") 308 | 309 | doSomething(markdownExample) // Output/process the markdown somewhere 310 | } 311 | ```````` 312 | 313 | With raw string literal feature, we don't need to escape anything: 314 | ````````js 315 | const generateMarkdown = () => { 316 | const markdownExample = @```` 317 | ```json 318 | { 319 | "firstName": "John", 320 | "lastName": "Smith", 321 | "age": 25 322 | } 323 | ``` 324 | ```` 325 | doSomething(markdownExample) // Output/process the markdown somewhere 326 | } 327 | ```````` 328 | #### (c) Regular expressions when string interpolation is also necessary 329 | 330 | Without this proposal, the closest way to represent "raw" regex is this way: 331 | `````js 332 | new RegExp(String.raw`someRegex\b${processedSearchKeyword}\s*(?:\(?HKD\)?):?\s*`, "i") 333 | ````` 334 | Even so, regex itself is already quite backslash heavy. 335 | 336 | If the regex pattern itself contains several backticks and "${xxx}" to search for, it will be a bit error-prone to eyeball which backslashes are really in the regex and which are only for escaping characters at JavaScript side. 337 | 338 | #### (d) XML/HTML/Source code embedding or generation 339 | 340 | If we work on some HTML, XML, or even source code generators (e.g. JavaScript, Linux commands), we often need to include special characters (", ', $, ` at the same time). The ability to write them in unescaped manner with clear indentation will help improvement readability and maintainability. 341 | 342 | If we work on some HTML, XML, or even source code generators (for e.g. JavaScript, Linux commands, PowerShell commands), we often need to include special characters (", ', $, ` at the same time). The ability to write them in unescaped manner with clear indentation will help improvement readability and maintainability. 343 | 344 | One may argue the use of backtick is not frequent. But it is a was. Nowadays, many programming languages are adopting the backtick character just to avoid escaping double quotes, so backtick character is becoming more and more common. 345 | 346 | Raw string literal may not solve all problems, but it can at least end the wild goose chase of choosing escape characters related to open and closing delimiter and interpolation delimiter. 347 | 348 | #### (e) Unit tests involving MySQL query, Markdown, XML 349 | 350 | Unit tests are also common places where we want to embed these contents in source codes instead of separate files. 351 | 352 | As explained in (a) and (b), the raw string literal would improve clarity. 353 | 354 | #### (f) A complex example involving nesting Markdown and JavaScript itself 355 | ````javascript 356 | let promptForLLM = ` 357 | You are a AI assistant to give advice to programmers, 358 | for example, given the code: 359 | \`\`\`js 360 | let s1 = "This is a\\n" 361 | + "string across\\n" 362 | + "multiple lines.\\n" 363 | let a = 1, b = 2 364 | let s2 = "a + b = " + (a + b) 365 | \`\`\` 366 | you would output the advice: 367 | \`\`\`\`markdown 368 | ## Advice 369 | It's more readable to use template literal to replace 370 | the string concatenation. 371 | 372 | ## Original code 373 | \`\`\`js 374 | let s1 = "This is a\\n" 375 | + "string across\\n" 376 | + "multiple lines.\\n" 377 | let a = 1, b = 2 378 | let s2 = "a + b = " + (a + b) 379 | \`\`\` 380 | 381 | ## Improved code 382 | \`\`\`js 383 | let s1 = String.dedent\` 384 | This is a 385 | string across 386 | multiple lines 387 | \` 388 | let a = 1, b = 2 389 | let s2 = \`a + b = \${a + b}\` 390 | \`\`\` 391 | \`\`\`\` 392 | ` 393 | ```` 394 | 395 | With proposed syntax: 396 | ``````text 397 | let promptForLLM = @````` 398 | You are a AI assistant to give advice to programmers, 399 | for example, given the code: 400 | ```js 401 | let s1 = "This is a\n" 402 | + "string across\n" 403 | + "multiple lines.\n" 404 | let a = 1, b = 2 405 | let s2 = "a + b = " + (a + b) 406 | ``` 407 | you would output the advice: 408 | ````markdown 409 | ## Advice 410 | It's more readable to use template literal to replace 411 | the string concatenation. 412 | 413 | ## Original code 414 | ```js 415 | let s1 = "This is a\n" 416 | + "string across\n" 417 | + "multiple lines.\n" 418 | let a = 1, b = 2 419 | let s2 = "a + b = " + (a + b) 420 | ``` 421 | 422 | ## Improved code 423 | ```js 424 | let s1 = @`` 425 | This is a 426 | string across 427 | multiple lines 428 | `` 429 | let a = 1, b = 2 430 | let s2 = `a + b = ${a + b}` 431 | ``` 432 | ```` 433 | ````` 434 | `````` 435 | 436 | ## Relations to other proposals 437 | 438 | ### `String.dedent` proposal 439 | TODO 440 | 441 | ### `String.cooked` proposal 442 | - https://github.com/tc39/proposal-string-cooked/issues/7 443 | 444 | Raw string literals should always give raw string,the current proposed `String.cooked` won't access `raw` property and cook the string, so the result may not as dev expect. 445 | 446 | ## Prior Arts 447 | 448 | - [Here document](https://en.wikipedia.org/wiki/Here_document) (Shells, Perl, Ruby, PHP, D, Racket, etc.) 449 | - C# [raw string literal](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-11.0/raw-string-literal) 450 | - Swift [extended string delimiters](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/stringsandcharacters/#Extended-String-Delimiters) 451 | - Rust [raw string literals](https://doc.rust-lang.org/reference/tokens.html#raw-string-literals) 452 | - C++ [raw string literals](https://en.cppreference.com/w/cpp/language/string_literal#Raw_string_literals) 453 | 454 | ## Previous discussions/ideas 455 | - https://es.discourse.group/t/raw-string-literals-that-can-contain-any-arbitrary-text-without-the-need-for-special-escape-sequences/1757 456 | - https://github.com/tc39/proposal-string-dedent/issues/40 457 | - https://github.com/tc39/proposal-string-dedent/issues/76 458 | - https://github.com/golang/go/issues/32590 459 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "template-for-proposals", 4 | "description": "A repository template for ECMAScript proposals.", 5 | "scripts": { 6 | "start": "npm run build-loose -- --watch", 7 | "build": "npm run build-loose -- --strict", 8 | "build-loose": "node -e 'fs.mkdirSync(\"build\", { recursive: true })' && ecmarkup --load-biblio @tc39/ecma262-biblio --verbose spec.emu build/index.html --lint-spec" 9 | }, 10 | "homepage": "https://github.com/tc39/template-for-proposals#readme", 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/tc39/template-for-proposals.git" 14 | }, 15 | "license": "MIT", 16 | "devDependencies": { 17 | "@tc39/ecma262-biblio": "^2.1.2571", 18 | "ecmarkup": "^17.0.0" 19 | }, 20 | "engines": { 21 | "node": ">= 12" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /spec.emu: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | title: Proposal Title Goes Here 8 | stage: -1 9 | contributors: Your Name(s) Here 10 |11 | 12 |
This is an algorithm:
15 |