├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── Scripts ├── Gemfile ├── Gemfile.lock └── convert_swift_grammar.rb ├── .spi.yml ├── Sources ├── Gramophone │ ├── Gramophone.swift │ ├── Terminal.swift │ ├── BNFTokenSequence.swift │ ├── Rule.swift │ ├── Grammar.swift │ └── BNFParser.swift └── CLITool │ └── main.swift ├── .editorconfig ├── .gitignore ├── Package.resolved ├── Tests └── GramophoneTests │ ├── RuleTests.swift │ ├── GrammarTests.swift │ ├── LexerTests.swift │ └── ParserTests.swift ├── Package.swift ├── Grammars ├── EBNF.ebnf ├── Lua.ebnf └── Swift.ebnf ├── LICENSE ├── CODE_OF_CONDUCT.md └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [mattmassicotte] 2 | -------------------------------------------------------------------------------- /Scripts/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem 'redcarpet' 4 | -------------------------------------------------------------------------------- /.spi.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | builder: 3 | configs: 4 | - documentation_targets: [Gramophone] 5 | -------------------------------------------------------------------------------- /Sources/Gramophone/Gramophone.swift: -------------------------------------------------------------------------------- 1 | public struct Gramophone { 2 | public private(set) var text = "Hello, World!" 3 | 4 | public init() { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | xcuserdata/ 5 | DerivedData/ 6 | .swiftpm/configuration/registries.json 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | .netrc 9 | -------------------------------------------------------------------------------- /Scripts/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | redcarpet (3.6.0) 5 | 6 | PLATFORMS 7 | arm64-darwin-24 8 | ruby 9 | 10 | DEPENDENCIES 11 | redcarpet 12 | 13 | BUNDLED WITH 14 | 2.6.2 15 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "7515983f3649fc7fee58fb882fc624f99281ef8f3e557b3103c2f6951754ba4e", 3 | "pins" : [ 4 | { 5 | "identity" : "flexer", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/ChimeHQ/Flexer", 8 | "state" : { 9 | "branch" : "main", 10 | "revision" : "b8fb79efe3ea65cbb2f7e864042ebce47fada488" 11 | } 12 | }, 13 | { 14 | "identity" : "swift-argument-parser", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/apple/swift-argument-parser", 17 | "state" : { 18 | "revision" : "41982a3656a71c768319979febd796c6fd111d5c", 19 | "version" : "1.5.0" 20 | } 21 | } 22 | ], 23 | "version" : 3 24 | } 25 | -------------------------------------------------------------------------------- /Tests/GramophoneTests/RuleTests.swift: -------------------------------------------------------------------------------- 1 | import Testing 2 | import Gramophone 3 | 4 | struct RuleTests { 5 | @Test 6 | func renderingNesting() async throws { 7 | let ruleA = Rule( 8 | "test", 9 | kind: .alternation([.reference("a"), .concatenation([.reference("b"), .reference("c")])]) 10 | ) 11 | 12 | #expect(ruleA.description == "test = a | (b, c);") 13 | 14 | let ruleB = Rule( 15 | "test", 16 | kind: .concatenation([.alternation([.reference("a"), .reference("b")]), .reference("c")]) 17 | ) 18 | 19 | #expect(ruleB.description == "test = (a | b), c;") 20 | } 21 | 22 | @Test 23 | func suppressingUnnecessaryGrouping() async throws { 24 | let rule = Rule( 25 | "test", 26 | kind: .occurrence(.concatenation(["a", "b"]), frequency: .zeroOrMore) 27 | ) 28 | 29 | #expect(rule.description == "test = {'a', 'b'};") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 6.1 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "Gramophone", 7 | platforms: [ 8 | .macOS(.v10_15), 9 | .macCatalyst(.v13), 10 | .iOS(.v13), 11 | .tvOS(.v13), 12 | .watchOS(.v6), 13 | .visionOS(.v1), 14 | ], 15 | products: [ 16 | .library(name: "Gramophone", targets: ["Gramophone"]), 17 | .executable(name: "gram", targets: ["CLITool"]), 18 | ], 19 | dependencies: [ 20 | .package(url: "https://github.com/ChimeHQ/Flexer", branch: "main"), 21 | .package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0"), 22 | ], 23 | targets: [ 24 | .target( 25 | name: "Gramophone", 26 | dependencies: ["Flexer"]), 27 | .testTarget( 28 | name: "GramophoneTests", 29 | dependencies: ["Gramophone"]), 30 | .executableTarget( 31 | name: "CLITool", 32 | dependencies: [ 33 | "Gramophone", 34 | .product(name: "ArgumentParser", package: "swift-argument-parser"), 35 | ] 36 | ), 37 | ] 38 | ) 39 | -------------------------------------------------------------------------------- /Sources/Gramophone/Terminal.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum Terminal { 4 | case string(String) 5 | case characterSet(CharacterSet) 6 | case endOfInput 7 | 8 | static let all = Terminal.characterSet( 9 | CharacterSet.whitespacesAndNewlines.union(.illegalCharacters).inverted 10 | ) 11 | 12 | static let epsilon = Terminal.string("") 13 | } 14 | 15 | extension Terminal: Hashable {} 16 | extension Terminal: Sendable {} 17 | 18 | extension Terminal: CustomStringConvertible { 19 | public var description: String { 20 | switch self { 21 | case .string(""): 22 | return "ε" 23 | case let .string(value): 24 | return "'\(value)'" 25 | case let .characterSet(set): 26 | return set.description 27 | case .endOfInput: 28 | return "$" 29 | } 30 | } 31 | } 32 | 33 | extension Terminal: ExpressibleByStringLiteral { 34 | public init(stringLiteral value: String) { 35 | if value == "" { 36 | self = .endOfInput 37 | } else { 38 | self = .string(value) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/CLITool/main.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import ArgumentParser 3 | 4 | import Gramophone 5 | 6 | struct GramophoneCommand: ParsableCommand { 7 | static let configuration = CommandConfiguration(commandName: "gramophone") 8 | 9 | @Flag( 10 | name: .shortAndLong, 11 | help: "Print the version and exit." 12 | ) 13 | var version: Bool = false 14 | 15 | @Argument(help: "The path to the input file.") 16 | var inputPath: String 17 | 18 | func run() throws { 19 | if version { 20 | throw CleanExit.message("0.1.0") 21 | } 22 | 23 | let input = try String(contentsOfFile: inputPath, encoding: .utf8) 24 | let parser = BNFParser() 25 | 26 | let rules = try parser.parse(input).get() 27 | 28 | let grammar = Grammar(rules: rules) 29 | 30 | // this garbage preserves the rule name ordering 31 | let ruleNames = rules.map { $0.name } 32 | 33 | var printedSet: Set = [] 34 | 35 | for ruleName in ruleNames { 36 | if printedSet.contains(ruleName) { 37 | continue 38 | } 39 | 40 | 41 | print(grammar.rules[ruleName]!) 42 | 43 | printedSet.insert(ruleName) 44 | } 45 | } 46 | } 47 | 48 | GramophoneCommand.main() 49 | -------------------------------------------------------------------------------- /Scripts/convert_swift_grammar.rb: -------------------------------------------------------------------------------- 1 | require "open-uri" 2 | require "redcarpet" 3 | 4 | class SwiftEBNF < Redcarpet::Render::Base 5 | # Methods where the first argument is the text content 6 | [ 7 | # block-level calls 8 | :block_code, :block_quote, 9 | :block_html, :list, :list_item, 10 | :paragraph, 11 | 12 | # span-level calls 13 | :autolink, :double_emphasis, 14 | :emphasis, :underline, :raw_html, 15 | :triple_emphasis, :strikethrough, 16 | :superscript, :highlight, :quote, 17 | 18 | # footnotes 19 | :footnotes, :footnote_def, :footnote_ref, 20 | 21 | # low level rendering 22 | :entity, :normal_text 23 | ].each do |method| 24 | define_method method do |*args| 25 | args.first 26 | end 27 | end 28 | 29 | # unhandled 30 | # :header, :image, :link, :table, :table_row, :table_cell 31 | 32 | def codespan(text) 33 | if text.include?("'") 34 | "\"#{text}\"" 35 | else 36 | "'#{text}'" 37 | end 38 | end 39 | end 40 | 41 | data = URI.parse("https://raw.githubusercontent.com/swiftlang/swift-book/refs/heads/main/TSPL.docc/ReferenceManual/SummaryOfTheGrammar.md").read 42 | 43 | markdown_rules = data 44 | .lines 45 | .filter { |line| line.include?("→") } 46 | .map { |x| x.gsub(" \\\n", "\n") } # remove any trailing backslashes 47 | .join 48 | 49 | markdown = Redcarpet::Markdown.new(SwiftEBNF) 50 | 51 | puts markdown.render(markdown_rules) -------------------------------------------------------------------------------- /Grammars/EBNF.ebnf: -------------------------------------------------------------------------------- 1 | letter = "A" | "B" | "C" | "D" | "E" | "F" | "G" 2 | | "H" | "I" | "J" | "K" | "L" | "M" | "N" 3 | | "O" | "P" | "Q" | "R" | "S" | "T" | "U" 4 | | "V" | "W" | "X" | "Y" | "Z" | "a" | "b" 5 | | "c" | "d" | "e" | "f" | "g" | "h" | "i" 6 | | "j" | "k" | "l" | "m" | "n" | "o" | "p" 7 | | "q" | "r" | "s" | "t" | "u" | "v" | "w" 8 | | "x" | "y" | "z" ; 9 | 10 | digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; 11 | 12 | symbol = "[" | "]" | "{" | "}" | "(" | ")" | "<" | ">" 13 | | "'" | '"' | "=" | "|" | "." | "," | ";" | "-" 14 | | "+" | "*" | "?" | "\n" | "\t" | "\r" | "\f" | "\b" ; 15 | 16 | character = letter | digit | symbol | "_" | " " ; 17 | identifier = letter , { letter | digit | "_" } ; 18 | 19 | S = { " " | "\n" | "\t" | "\r" | "\f" | "\b" } ; 20 | 21 | terminal = "'" , character - "'" , { character - "'" } , "'" 22 | | '"' , character - '"' , { character - '"' } , '"' ; 23 | 24 | terminator = ";" | "." ; 25 | 26 | term = "(" , S , rhs , S , ")" 27 | | "[" , S , rhs , S , "]" 28 | | "{" , S , rhs , S , "}" 29 | | terminal 30 | | identifier ; 31 | 32 | factor = term , S , "?" 33 | | term , S , "*" 34 | | term , S , "+" 35 | | term , S , "-" , S , term 36 | | term , S ; 37 | 38 | concatenation = ( S , factor , S , "," ? ) + ; 39 | alternation = ( S , concatenation , S , "|" ? ) + ; 40 | 41 | rhs = alternation ; 42 | lhs = identifier ; 43 | 44 | rule = lhs , S , "=" , S , rhs , S , terminator ; 45 | 46 | grammar = ( S , rule , S ) * ; -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths-ignore: 8 | - 'README.md' 9 | - 'CODE_OF_CONDUCT.md' 10 | - '.editorconfig' 11 | - '.spi.yml' 12 | pull_request: 13 | branches: 14 | - main 15 | 16 | concurrency: 17 | group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} 18 | cancel-in-progress: true 19 | 20 | jobs: 21 | test: 22 | name: Test 23 | runs-on: macOS-15 24 | timeout-minutes: 30 25 | env: 26 | DEVELOPER_DIR: /Applications/Xcode_16.4.app 27 | strategy: 28 | matrix: 29 | destination: 30 | - "platform=macOS" 31 | - "platform=macOS,variant=Mac Catalyst" 32 | - "platform=iOS Simulator,name=iPhone 16" 33 | - "platform=tvOS Simulator,name=Apple TV" 34 | - "platform=watchOS Simulator,name=Apple Watch Series 10 (42mm)" 35 | - "platform=visionOS Simulator,name=Apple Vision Pro" 36 | steps: 37 | - uses: actions/checkout@v4 38 | - name: Test platform ${{ matrix.destination }} 39 | run: set -o pipefail && xcodebuild -scheme Gramophone-Package -destination "${{ matrix.destination }}" test | xcbeautify 40 | 41 | linux_test: 42 | name: Test Linux 43 | runs-on: ubuntu-latest 44 | timeout-minutes: 30 45 | steps: 46 | - name: Checkout 47 | uses: actions/checkout@v4 48 | - name: Swiftly 49 | uses: vapor/swiftly-action@v0.2.0 50 | with: 51 | toolchain: latest 52 | - name: Test 53 | run: swift test 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, Chime 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Grammars/Lua.ebnf: -------------------------------------------------------------------------------- 1 | chunk ::= {stat [`;´]} [laststat[`;´]] 2 | 3 | block ::= chunk 4 | 5 | stat ::= varlist1 `=´ explist1 | 6 | functioncall | 7 | do block end | 8 | while exp do block end | 9 | repeat block until exp | 10 | if exp then block {elseif exp then block} [else block] end | 11 | for Name `=´ exp `,´ exp [`,´ exp] do block end | 12 | for namelist in explist1 do block end | 13 | function funcname funcbody | 14 | local function Name funcbody | 15 | local namelist [`=´ explist1] 16 | 17 | laststat ::= return [explist1] | break 18 | 19 | funcname ::= Name {`.´ Name} [`:´ Name] 20 | 21 | varlist1 ::= var {`,´ var} 22 | 23 | var ::= Name | prefixexp `[´ exp `]´ | prefixexp `.´ Name 24 | 25 | namelist ::= Name {`,´ Name} 26 | 27 | explist1 ::= {exp `,´} exp 28 | 29 | exp ::= nil | false | true | Number | String | `...´ | 30 | function | prefixexp | tableconstructor | exp binop exp | unop exp 31 | 32 | prefixexp ::= var | functioncall | `(´ exp `)´ 33 | 34 | functioncall ::= prefixexp args | prefixexp `:´ Name args 35 | 36 | args ::= `(´ [explist1] `)´ | tableconstructor | String 37 | 38 | function ::= function funcbody 39 | 40 | funcbody ::= `(´ [parlist1] `)´ block end 41 | 42 | parlist1 ::= namelist [`,´ `...´] | `...´ 43 | 44 | tableconstructor ::= `{´ [fieldlist] `}´ 45 | 46 | fieldlist ::= field {fieldsep field} [fieldsep] 47 | 48 | field ::= `[´ exp `]´ `=´ exp | Name `=´ exp | exp 49 | 50 | fieldsep ::= `,´ | `;´ 51 | 52 | binop ::= `+´ | `-´ | `*´ | `/´ | `^´ | `%´ | `..´ | 53 | `<´ | `<=´ | `>´ | `>=´ | `==´ | `~=´ | 54 | and | or 55 | 56 | unop ::= `-´ | not | `#´ 57 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at support@chimehq.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | [![Build Status][build status badge]][build status] 4 | [![Platforms][platforms badge]][platforms] 5 | [![Matrix][matrix badge]][matrix] 6 | 7 |
8 | 9 | # Gramophone 10 | 11 | Swift library for working with [Extended Backus–Naur Form][ebnf] (EBNF) notation and the resulting grammars. 12 | 13 | Features: 14 | - Accepts a variety of BNF syntaxes 15 | - Computes FIRST and FOLLOW sets 16 | 17 | > [!WARNING] 18 | > This library is still a work-in-progress. It definitely still has some issues. 19 | 20 | ## Integration 21 | 22 | ### Swift Package Manager 23 | 24 | ```swift 25 | dependencies: [ 26 | .package(url: "https://github.com/ChimeHQ/Gramophone") 27 | ] 28 | ``` 29 | 30 | ## Supported Syntax 31 | 32 | ``` 33 | single_quote_terminal = 'value'; 34 | angled_quote_terminal = `value´; 35 | double_quote_terminal = "value"; 36 | unicode_scalar = U+0000; 37 | unicode_scalar_range = [U+0000-U+0005]; 38 | bnf_nonterminal = ; 39 | 40 | concatenation = a, b, c; 41 | implicit_concatenation = a b c; 42 | alternation = a | b | c; 43 | optional = [a, b]; 44 | tailing_optional = a?; 45 | repetition = {a}; 46 | tailing_plus_repetition = a+; 47 | tailing_star_repetition = a*; 48 | grouping = (a, b, c); 49 | exception = a - b; 50 | multiple_alternation = a; 51 | multiple_alternation = b; 52 | 53 | arrow_assigment → a; 54 | colon_colon_equals_assigment ::= a; 55 | 56 | mult_line_expressions = a | 57 | b | 58 | c; 59 | newline_statement_end = a 60 | 61 | ``` 62 | 63 | There's also a bunch of additional notation used by the W3C's [XQuery specification](https://www.w3.org/TR/xquery-31/#EBNFNotation), which may be work looking into. 64 | 65 | ## Usage 66 | 67 | ```swift 68 | let grammar = try parser.parseGrammar("test = 'a' | 'b';") 69 | 70 | let firstMap = grammar.computeFirstMap() 71 | let followMap = grammar.computeFollowMap() 72 | ``` 73 | 74 | ## Grammar Conversion 75 | 76 | I made a dumb script that converts Swift's [formal grammar](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/summaryofthegrammar) to EBNF because, for some reason, it is not in that format right now. I would have liked to implement that itself in Swift, but it Got Hard. So I fell back to Ruby. If you want to get involved with this, I would recommend [rbenv](https://github.com/rbenv/rbenv). 77 | 78 | ``` 79 | # cd Scripts 80 | # bundle exec ruby convert_swift_grammar.rb > ../Grammars/Swift.ebnf 81 | ``` 82 | 83 | It's pretty hard to eyeball the conversion correctness because the grammar is complex and uses some facilities that are not typical of EBNF (shocker!). This library cannot yet correctly parse or even represent all of the things the grammar needs, but it's getting closer. 84 | 85 | ## Contributing and Collaboration 86 | 87 | I would love to hear from you! Issues or pull requests work great. Both a [Matrix space][matrix] and [Discord][discord] are available for live help, but I have a strong bias towards answering in the form of documentation. You can also find me on [mastodon](https://mastodon.social/@mattiem). 88 | 89 | I prefer collaboration, and would love to find ways to work together if you have a similar project. 90 | 91 | I prefer indentation with tabs for improved accessibility. But, I'd rather you use the system you want and make a PR than hesitate because of whitespace. 92 | 93 | By participating in this project you agree to abide by the [Contributor Code of Conduct](CODE_OF_CONDUCT.md). 94 | 95 | [build status]: https://github.com/ChimeHQ/Gramophone/actions 96 | [build status badge]: https://github.com/ChimeHQ/Gramophone/workflows/CI/badge.svg 97 | [platforms]: https://swiftpackageindex.com/ChimeHQ/Gramophone 98 | [platforms badge]: https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FChimeHQ%2FGramophone%2Fbadge%3Ftype%3Dplatforms 99 | [matrix]: https://matrix.to/#/%23chimehq%3Amatrix.org 100 | [matrix badge]: https://img.shields.io/matrix/chimehq%3Amatrix.org?label=Matrix 101 | [discord]: https://discord.gg/esFpX6sErJ 102 | [ebnf]: https://en.wikipedia.org/wiki/Extended_Backus–Naur_form 103 | -------------------------------------------------------------------------------- /Tests/GramophoneTests/GrammarTests.swift: -------------------------------------------------------------------------------- 1 | import Testing 2 | import Gramophone 3 | 4 | struct GrammarTests { 5 | @Test 6 | func firstsFromSingleTerminal() throws { 7 | let content = """ 8 | a = '1'; 9 | """ 10 | let grammar = try BNFParser().parseGrammar(content) 11 | 12 | let expected: Grammar.FirstMap = [ 13 | "a": ["1"], 14 | ] 15 | 16 | #expect(grammar.computeFirstMap() == expected) 17 | } 18 | 19 | @Test 20 | func firstsFromReference() throws { 21 | let content = """ 22 | a = '1'; 23 | b = '2'; 24 | """ 25 | let grammar = try BNFParser().parseGrammar(content) 26 | 27 | let expected: Grammar.FirstMap = [ 28 | "a": ["1"], 29 | "b": ["2"], 30 | ] 31 | 32 | #expect(grammar.computeFirstMap() == expected) 33 | } 34 | 35 | @Test 36 | func firstsFromDoubleReference() throws { 37 | let content = """ 38 | a = '1'; 39 | b = c; 40 | c = a; 41 | """ 42 | let grammar = try BNFParser().parseGrammar(content) 43 | 44 | let expected: Grammar.FirstMap = [ 45 | "a": ["1"], 46 | "b": ["1"], 47 | "c": ["1"], 48 | ] 49 | 50 | #expect(grammar.computeFirstMap() == expected) 51 | } 52 | } 53 | 54 | extension GrammarTests { 55 | @Test 56 | func followFromSingleTerminal() throws { 57 | let content = """ 58 | a = '1'; 59 | """ 60 | let grammar = try BNFParser().parseGrammar(content) 61 | let expected: Grammar.FollowMap = [ 62 | "a": [.endOfInput], 63 | ] 64 | 65 | #expect(grammar.computeFollowMap() == expected) 66 | } 67 | 68 | @Test 69 | func followFromConcatenation() throws { 70 | let content = """ 71 | a = '1'; 72 | b = '2'; 73 | c = a b; 74 | """ 75 | let grammar = try BNFParser().parseGrammar(content) 76 | 77 | let expected: Grammar.FollowMap = [ 78 | "a": ["2"], 79 | "b": [.endOfInput], 80 | "c": [.endOfInput], 81 | ] 82 | 83 | #expect(grammar.computeFollowMap() == expected) 84 | } 85 | 86 | @Test 87 | func followFromAlternation() throws { 88 | let content = """ 89 | a = '1'; 90 | b = a, ('2' | '3'); 91 | """ 92 | let grammar = try BNFParser().parseGrammar(content) 93 | 94 | let expected: Grammar.FollowMap = [ 95 | "a": ["2", "3"], 96 | "b": [.endOfInput], 97 | ] 98 | 99 | #expect(grammar.computeFollowMap() == expected) 100 | } 101 | 102 | @Test 103 | func followFromRepetition() throws { 104 | let content = """ 105 | a = '1'; 106 | b = a, {'2'}; 107 | """ 108 | let grammar = try BNFParser().parseGrammar(content) 109 | 110 | let expected: Grammar.FollowMap = [ 111 | "a" : ["2"], 112 | "b": [.endOfInput], 113 | ] 114 | 115 | #expect(grammar.computeFollowMap() == expected) 116 | } 117 | 118 | @Test 119 | func followFromAlternationInStart() throws { 120 | let content = """ 121 | a = '1'; 122 | b = a | (a '2'); 123 | """ 124 | let grammar = try BNFParser().parseGrammar(content) 125 | 126 | let expected: Grammar.FollowMap = [ 127 | "a" : ["2", .endOfInput], 128 | "b": [.endOfInput], 129 | ] 130 | 131 | #expect(grammar.computeFollowMap() == expected) 132 | } 133 | 134 | @Test 135 | func followFromGroupedConcatenation() throws { 136 | let content = """ 137 | a = '1'; 138 | b = (a ',' '2'); 139 | """ 140 | let grammar = try BNFParser().parseGrammar(content) 141 | 142 | let expected: Grammar.FollowMap = [ 143 | "a" : [","], 144 | "b": [.endOfInput], 145 | ] 146 | 147 | #expect(grammar.computeFollowMap() == expected) 148 | } 149 | 150 | @Test 151 | func followsFromReference() throws { 152 | let content = """ 153 | a = '1'; 154 | b = a; 155 | c = b, '3'; 156 | """ 157 | let grammar = try BNFParser().parseGrammar(content) 158 | 159 | let expected: Grammar.FollowMap = [ 160 | "a" : ["3"], 161 | "b": ["3"], 162 | "c": [.endOfInput], 163 | ] 164 | 165 | #expect(grammar.computeFollowMap() == expected) 166 | } 167 | 168 | @Test 169 | func followsFromSecondReference() throws { 170 | let content = """ 171 | a = '1'; 172 | b = a; 173 | c = b; 174 | d = c, '3'; 175 | """ 176 | let grammar = try BNFParser().parseGrammar(content) 177 | 178 | let expected: Grammar.FollowMap = [ 179 | "a" : ["3"], 180 | "b": ["3"], 181 | "c": ["3"], 182 | "d": [.endOfInput] 183 | ] 184 | 185 | #expect(grammar.computeFollowMap() == expected) 186 | } 187 | } 188 | 189 | extension GrammarTests { 190 | @Test 191 | func grammerWithDuplicateRules() throws { 192 | let rules = [ 193 | Rule("a", kind: .terminalString("1")), 194 | Rule("a", kind: .terminalString("2")) 195 | ] 196 | 197 | let grammer = Grammar(rules: rules) 198 | let expected = [ 199 | "a": Rule("a", kind: .alternation(["1", "2"])) 200 | ] 201 | 202 | #expect(grammer.rules == expected) 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /Tests/GramophoneTests/LexerTests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Testing 3 | @testable import Gramophone 4 | 5 | struct LexerTests { 6 | @Test 7 | func punctuation() throws { 8 | let string = "=[],|;.→{}()?*'\"+:-`´<>%@&$#~^" 9 | var lexer = BNFLexer(string: string) 10 | 11 | #expect(lexer.next() == BNFToken(kind: .assignment, range: NSRange(0..<1), in: string)) 12 | #expect(lexer.next() == BNFToken(kind: .openBracket, range: NSRange(1..<2), in: string)) 13 | #expect(lexer.next() == BNFToken(kind: .closeBracket, range: NSRange(2..<3), in: string)) 14 | #expect(lexer.next() == BNFToken(kind: .comma, range: NSRange(3..<4), in: string)) 15 | #expect(lexer.next() == BNFToken(kind: .pipe, range: NSRange(4..<5), in: string)) 16 | #expect(lexer.next() == BNFToken(kind: .semicolon, range: NSRange(5..<6), in: string)) 17 | #expect(lexer.next() == BNFToken(kind: .period, range: NSRange(6..<7), in: string)) 18 | #expect(lexer.next() == BNFToken(kind: .assignment, range: NSRange(7..<8), in: string)) 19 | #expect(lexer.next() == BNFToken(kind: .openBrace, range: NSRange(8..<9), in: string)) 20 | #expect(lexer.next() == BNFToken(kind: .closeBrace, range: NSRange(9..<10), in: string)) 21 | #expect(lexer.next() == BNFToken(kind: .openParen, range: NSRange(10..<11), in: string)) 22 | #expect(lexer.next() == BNFToken(kind: .closeParen, range: NSRange(11..<12), in: string)) 23 | #expect(lexer.next() == BNFToken(kind: .question, range: NSRange(12..<13), in: string)) 24 | #expect(lexer.next() == BNFToken(kind: .star, range: NSRange(13..<14), in: string)) 25 | #expect(lexer.next() == BNFToken(kind: .quote, range: NSRange(14..<15), in: string)) 26 | #expect(lexer.next() == BNFToken(kind: .doubleQuote, range: NSRange(15..<16), in: string)) 27 | #expect(lexer.next() == BNFToken(kind: .plus, range: NSRange(16..<17), in: string)) 28 | #expect(lexer.next() == BNFToken(kind: .colon, range: NSRange(17..<18), in: string)) 29 | #expect(lexer.next() == BNFToken(kind: .minus, range: NSRange(18..<19), in: string)) 30 | #expect(lexer.next() == BNFToken(kind: .backtick, range: NSRange(19..<20), in: string)) 31 | #expect(lexer.next() == BNFToken(kind: .tick, range: NSRange(20..<21), in: string)) 32 | #expect(lexer.next() == BNFToken(kind: .lessThan, range: NSRange(21..<22), in: string)) 33 | #expect(lexer.next() == BNFToken(kind: .greaterThan, range: NSRange(22..<23), in: string)) 34 | #expect(lexer.next() == BNFToken(kind: .otherCharacter, range: NSRange(23..<24), in: string)) 35 | #expect(lexer.next() == BNFToken(kind: .otherCharacter, range: NSRange(24..<25), in: string)) 36 | #expect(lexer.next() == BNFToken(kind: .otherCharacter, range: NSRange(25..<26), in: string)) 37 | #expect(lexer.next() == BNFToken(kind: .otherCharacter, range: NSRange(26..<27), in: string)) 38 | #expect(lexer.next() == BNFToken(kind: .otherCharacter, range: NSRange(27..<28), in: string)) 39 | #expect(lexer.next() == BNFToken(kind: .otherCharacter, range: NSRange(28..<29), in: string)) 40 | #expect(lexer.next() == BNFToken(kind: .otherCharacter, range: NSRange(29..<30), in: string)) 41 | } 42 | 43 | @Test 44 | func name() throws { 45 | let string = "word CapsInWord under_score a b 123 a111" 46 | var lexer = BNFLexer(string: string) 47 | 48 | #expect(lexer.next() == BNFToken(kind: .name, range: NSRange(0..<4), in: string)) 49 | #expect(lexer.next() == BNFToken(kind: .name, range: NSRange(5..<15), in: string)) 50 | #expect(lexer.next() == BNFToken(kind: .name, range: NSRange(16..<27), in: string)) 51 | #expect(lexer.next() == BNFToken(kind: .name, range: NSRange(28..<29), in: string)) 52 | #expect(lexer.next() == BNFToken(kind: .name, range: NSRange(30..<31), in: string)) 53 | #expect(lexer.next() == BNFToken(kind: .name, range: NSRange(32..<35), in: string)) 54 | #expect(lexer.next() == BNFToken(kind: .name, range: NSRange(36..<40), in: string)) 55 | } 56 | 57 | @Test 58 | func minus() throws { 59 | let string = "a - b a-b 0-a" 60 | var lexer = BNFLexer(string: string) 61 | 62 | #expect(lexer.next() == BNFToken(kind: .name, range: NSRange(0..<1), in: string)) 63 | #expect(lexer.next() == BNFToken(kind: .minus, range: NSRange(2..<3), in: string)) 64 | #expect(lexer.next() == BNFToken(kind: .name, range: NSRange(4..<5), in: string)) 65 | #expect(lexer.next() == BNFToken(kind: .name, range: NSRange(6..<7), in: string)) 66 | #expect(lexer.next() == BNFToken(kind: .minus, range: NSRange(7..<8), in: string)) 67 | #expect(lexer.next() == BNFToken(kind: .name, range: NSRange(8..<9), in: string)) 68 | } 69 | 70 | @Test 71 | func spaceOnly() throws { 72 | let string = " " 73 | var lexer = BNFLexer(string: string) 74 | 75 | #expect(lexer.next() == nil) 76 | } 77 | 78 | @Test 79 | func newlineLetter() throws { 80 | let string = "\nb" 81 | var lexer = BNFLexer(string: string) 82 | 83 | #expect(lexer.next() == BNFToken(kind: .newline, range: NSRange(0..<1), in: string)) 84 | #expect(lexer.next() == BNFToken(kind: .name, range: NSRange(1..<2), in: string)) 85 | } 86 | 87 | @Test 88 | func newlineSpace() throws { 89 | let string = "\n " 90 | var lexer = BNFLexer(string: string) 91 | 92 | #expect(lexer.next() == BNFToken(kind: .newline, range: NSRange(0..<1), in: string)) 93 | #expect(lexer.next() == nil) 94 | } 95 | 96 | @Test 97 | func consolidateNewLinesToSemicolon() throws { 98 | let string = "\n\n \n\n\n" 99 | var lexer = BNFLexer(string: string) 100 | 101 | #expect(lexer.next() == BNFToken(kind: .semicolon, range: NSRange(0..<2), in: string)) 102 | #expect(lexer.next() == BNFToken(kind: .semicolon, range: NSRange(3..<6), in: string)) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Sources/Gramophone/BNFTokenSequence.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Flexer 3 | 4 | public enum BNFTokenKind { 5 | case name 6 | case comma 7 | case period 8 | case semicolon 9 | case colon 10 | case openParen 11 | case closeParen 12 | case openBrace 13 | case closeBrace 14 | case openBracket 15 | case closeBracket 16 | case quote 17 | case doubleQuote 18 | case question 19 | case minus 20 | case pipe 21 | case star 22 | case plus 23 | case lessThan 24 | case greaterThan 25 | case backtick 26 | case tick 27 | 28 | case assignment 29 | case otherCharacter 30 | case newline 31 | } 32 | 33 | typealias BNFToken = Flexer.Token 34 | 35 | extension BNFToken { 36 | public init?(kind: Kind, start: BasicTextCharacter, end: BasicTextCharacter?) { 37 | let endIndex = end?.endIndex ?? start.endIndex 38 | 39 | self.init(kind: kind, range: start.startIndex.. = [ 57 | .lowercaseLetter, 58 | .uppercaseLetter, 59 | .underscore, 60 | .digit, 61 | ] 62 | 63 | private var nameTerminatorComponents: Set = [ 64 | .newline, 65 | .tab, 66 | .space, 67 | .lowercaseLetter, 68 | .uppercaseLetter, 69 | .underscore, 70 | .digit, 71 | .openBrace, 72 | .closeBrace, 73 | .doubleQuote, 74 | .singleQuote 75 | ] 76 | 77 | public mutating func next() -> Element? { 78 | _ = lexer.nextUntil(notIn: [.space, .tab]) 79 | 80 | // advance past a newline, but if we have a second keep going and transform the entire thing into a semi-colon 81 | let start = lexer.nextIf({ $0.kind == .newline }) 82 | 83 | if let start, lexer.peek()?.kind == .newline { 84 | let end = lexer.nextUntil(notIn: [.newline]) 85 | 86 | return BNFToken(kind: .semicolon, start: start, end: end) 87 | } 88 | 89 | // advance past whitespace again 90 | _ = lexer.nextUntil(notIn: [.space, .tab]) 91 | 92 | if let start { 93 | return BNFToken(kind: .newline, range: start.range) 94 | } 95 | 96 | guard let token = lexer.next() else { 97 | return nil 98 | } 99 | 100 | switch token.kind { 101 | case .lowercaseLetter, .uppercaseLetter, .underscore, .digit: 102 | // this is because we have advanced instead of peeking, so a single character name will go too far 103 | if let peek = lexer.peek()?.kind, nameComponents.contains(peek) == false { 104 | return BNFToken(kind: .name, range: token.range) 105 | } 106 | 107 | let endingToken = lexer.nextUntil(notIn: nameComponents) 108 | 109 | return BNFToken(kind: .name, start: token, end: endingToken) 110 | case .singleQuote: 111 | return BNFToken(kind: .quote, range: token.range) 112 | case .doubleQuote: 113 | return BNFToken(kind: .doubleQuote, range: token.range) 114 | case .backtick: 115 | return BNFToken(kind: .backtick, range: token.range) 116 | case .comma: 117 | return BNFToken(kind: .comma, range: token.range) 118 | case .semicolon: 119 | return BNFToken(kind: .semicolon, range: token.range) 120 | case .equals: 121 | return BNFToken(kind: .assignment, range: token.range) 122 | case .openBracket: 123 | return BNFToken(kind: .openBracket, range: token.range) 124 | case .closeBracket: 125 | return BNFToken(kind: .closeBracket, range: token.range) 126 | case .openBrace: 127 | return BNFToken(kind: .openBrace, range: token.range) 128 | case .closeBrace: 129 | return BNFToken(kind: .closeBrace, range: token.range) 130 | case .openParen: 131 | return BNFToken(kind: .openParen, range: token.range) 132 | case .closeParen: 133 | return BNFToken(kind: .closeParen, range: token.range) 134 | case .period: 135 | return BNFToken(kind: .period, range: token.range) 136 | case .pipe: 137 | return BNFToken(kind: .pipe, range: token.range) 138 | case .question: 139 | return BNFToken(kind: .question, range: token.range) 140 | case .star: 141 | return BNFToken(kind: .star, range: token.range) 142 | case .plus: 143 | return BNFToken(kind: .plus, range: token.range) 144 | case .colon: 145 | return BNFToken(kind: .colon, range: token.range) 146 | case .dash: 147 | return BNFToken(kind: .minus, range: token.range) 148 | case .lessThan: 149 | return BNFToken(kind: .lessThan, range: token.range) 150 | case .greaterThan: 151 | return BNFToken(kind: .greaterThan, range: token.range) 152 | case .otherCharacter, .percent, .ampersand, .at, .caret, .dollar, .numberSign, .tilde: 153 | if string[token.range] == "→" { 154 | return BNFToken(kind: .assignment, range: token.range) 155 | } 156 | 157 | if string[token.range] == "´" { 158 | return BNFToken(kind: .tick, range: token.range) 159 | } 160 | 161 | return BNFToken(kind: .otherCharacter, range: token.range) 162 | default: 163 | break 164 | } 165 | 166 | let endingToken = lexer.nextUntil(in: nameTerminatorComponents) 167 | 168 | return BNFToken(kind: .name, start: token, end: endingToken) 169 | } 170 | } 171 | 172 | typealias BNFLexer = LookAheadSequence 173 | typealias BNFLexerReference = LookAheadSequenceReference 174 | -------------------------------------------------------------------------------- /Sources/Gramophone/Rule.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension UnicodeScalar { 4 | var formattedString: String { 5 | let shortString = String(self.value, radix: 16) 6 | let padding = String(repeating: "0", count: max(4 - shortString.count, 0)) 7 | 8 | return "U+" + padding + shortString 9 | } 10 | } 11 | 12 | public struct Rule { 13 | public enum Frequency: Sendable, Hashable { 14 | case zeroOrOne 15 | case zeroOrMore 16 | case oneOrMore 17 | } 18 | 19 | public indirect enum Kind { 20 | case concatenation([Kind]) 21 | case alternation([Kind]) 22 | case occurrence(Kind, frequency: Frequency) 23 | case grouping(Kind) 24 | case terminalString(String) 25 | case terminalCharacter(UnicodeScalar) 26 | case comment 27 | case specialSequence 28 | case exception(Kind, Kind) 29 | case reference(String) 30 | case range(UnicodeScalar, UnicodeScalar) 31 | 32 | public static let epsilon = terminalString("") 33 | 34 | public static func optional(_ kind: Kind) -> Kind { 35 | .occurrence(kind, frequency: .zeroOrOne) 36 | } 37 | 38 | public static func terminalCharacter(_ int: Int) -> Kind { 39 | .terminalCharacter(UnicodeScalar(int)!) 40 | } 41 | 42 | public static func range(_ a: Int, _ b: Int) -> Kind { 43 | .range(UnicodeScalar(a)!, UnicodeScalar(b)!) 44 | } 45 | } 46 | 47 | public var name: String 48 | public var kind: Kind 49 | 50 | public init(name: String, kind: Kind) { 51 | self.name = name 52 | self.kind = kind 53 | } 54 | 55 | public init(_ name: String, kind: Kind) { 56 | self.name = name 57 | self.kind = kind 58 | } 59 | } 60 | 61 | extension Rule.Kind: Sendable, Hashable { 62 | } 63 | 64 | extension Rule: Sendable, Hashable { 65 | } 66 | 67 | extension Rule.Kind: CustomStringConvertible { 68 | public var description: String { 69 | recursivePrint(grouped: true) 70 | } 71 | 72 | private func recursivePrint(grouped: Bool = false) -> String { 73 | switch self { 74 | case let .concatenation(elements): 75 | let value = elements.map { $0.recursivePrint() }.joined(separator: ", ") 76 | 77 | return grouped ? value : "(\(value))" 78 | case let .alternation(elements): 79 | let value = elements.map { $0.recursivePrint() }.joined(separator: " | ") 80 | 81 | return grouped ? value : "(\(value))" 82 | case let .terminalString(value): 83 | return "'\(value)'" 84 | case let .terminalCharacter(char): 85 | return char.formattedString 86 | case let .reference(value): 87 | return value 88 | case let .exception(a, b): 89 | let value = "\(a.recursivePrint()) - \(b.recursivePrint())" 90 | 91 | return grouped ? value : "(\(value))" 92 | case let .occurrence(value, frequency): 93 | switch frequency { 94 | case .zeroOrOne: 95 | return "[\(value.recursivePrint(grouped: true))]" 96 | case .zeroOrMore: 97 | return "{\(value.recursivePrint(grouped: true))}" 98 | case .oneOrMore: 99 | return "(\(value.recursivePrint(grouped: true))) +" 100 | } 101 | case let .grouping(value): 102 | return "(\(value.recursivePrint(grouped: true)))" 103 | case let .range(from, to): 104 | return "[\(from.formattedString)-\(to.formattedString)]" 105 | default: 106 | return "" 107 | } 108 | } 109 | } 110 | 111 | extension Rule: CustomStringConvertible { 112 | public var description: String { 113 | "\(name) = \(kind);" 114 | } 115 | } 116 | 117 | extension Rule.Kind : ExpressibleByStringLiteral { 118 | public init(stringLiteral value: String) { 119 | self = .terminalString(value) 120 | } 121 | } 122 | 123 | extension Rule.Kind { 124 | func traverse(_ block: (Rule.Kind) -> Void) { 125 | block(self) 126 | 127 | switch self { 128 | case let .alternation(elements): 129 | for element in elements { 130 | element.traverse(block) 131 | } 132 | case let .concatenation(elements): 133 | for element in elements { 134 | element.traverse(block) 135 | } 136 | case let .grouping(a): 137 | a.traverse(block) 138 | case let .exception(a, b): 139 | a.traverse(block) 140 | b.traverse(block) 141 | case let .occurrence(a, _): 142 | a.traverse(block) 143 | default: 144 | break 145 | } 146 | } 147 | } 148 | 149 | extension Rule.Kind { 150 | var trailingKinds: Set { 151 | switch self { 152 | case let .alternation(elements): 153 | var set = Set() 154 | 155 | for element in elements { 156 | set.formUnion(element.trailingKinds) 157 | } 158 | 159 | return set 160 | case let .concatenation(elements): 161 | return elements.last!.trailingKinds 162 | case let .occurrence(a, _): 163 | return a.trailingKinds 164 | case let .grouping(a): 165 | return a.trailingKinds 166 | default: 167 | return [self] 168 | } 169 | } 170 | 171 | var leadingKinds: Set { 172 | switch self { 173 | case let .alternation(elements): 174 | var set = Set() 175 | 176 | for element in elements { 177 | set.formUnion(element.leadingKinds) 178 | } 179 | 180 | return set 181 | case let .concatenation(elements): 182 | return elements.first!.leadingKinds 183 | case let .occurrence(a, _): 184 | return a.leadingKinds 185 | case let .grouping(a): 186 | return a.leadingKinds 187 | default: 188 | return [self] 189 | } 190 | } 191 | 192 | var followKinds: [String: Set] { 193 | var map = [String: Set]() 194 | 195 | traverse { value in 196 | switch value { 197 | case let .concatenation(elements): 198 | // this is a tricky calculation 199 | let pairs = zip(elements, elements.dropFirst()) 200 | 201 | for (current, next) in pairs { 202 | for trailing in current.trailingKinds { 203 | if case let .reference(name) = trailing { 204 | map[name, default: Set()].formUnion(next.leadingKinds) 205 | } 206 | } 207 | } 208 | default: 209 | break 210 | } 211 | } 212 | 213 | return map 214 | } 215 | 216 | var references: Set { 217 | var set = Set() 218 | 219 | traverse { kind in 220 | if case let .reference(string) = kind { 221 | set.insert(string) 222 | } 223 | } 224 | 225 | return set 226 | } 227 | 228 | var alternativeNames: Set { 229 | switch self { 230 | case let .alternation(elements): 231 | var set = Set() 232 | 233 | for element in elements { 234 | set.formUnion(element.alternativeNames) 235 | } 236 | 237 | return set 238 | case let .reference(name): 239 | return [name] 240 | case let .grouping(a): 241 | return a.alternativeNames 242 | default: 243 | return [] 244 | } 245 | } 246 | 247 | var trailingReferences: Set { 248 | var set = Set() 249 | 250 | for kind in trailingKinds { 251 | if case let .reference(string) = kind { 252 | set.insert(string) 253 | } 254 | } 255 | 256 | return set 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /Sources/Gramophone/Grammar.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Represent a language grammar. 4 | public struct Grammar { 5 | public typealias RuleMap = [String: Rule] 6 | public typealias FirstMap = [String: Set] 7 | public typealias FollowMap = FirstMap 8 | 9 | public let rules: RuleMap 10 | 11 | public init(rules: [Rule]) { 12 | self.rules = Self.consolidateRules(rules) 13 | } 14 | 15 | /// The set of rule names that are not references by any other rule. 16 | /// 17 | /// For a well-formed grammar, this should contain 1 value. 18 | public var unreferencedNames: Set { 19 | var names = Set(rules.keys) 20 | 21 | for rule in rules.values { 22 | let refs = rule.kind.references 23 | 24 | names.subtract(refs) 25 | } 26 | 27 | return names 28 | } 29 | 30 | static func consolidateRules(_ rules: [Rule]) -> RuleMap { 31 | var map = RuleMap() 32 | 33 | for rule in rules { 34 | let name = rule.name 35 | 36 | guard let existingRule = map[name] else { 37 | map[name] = rule 38 | continue 39 | } 40 | 41 | switch existingRule.kind { 42 | case let .alternation(existing): 43 | map[name] = Rule(name, kind: .alternation(existing + [rule.kind])) 44 | default: 45 | map[name] = Rule(name, kind: .alternation([existingRule.kind, rule.kind])) 46 | } 47 | } 48 | 49 | return map 50 | } 51 | } 52 | 53 | extension Grammar { 54 | private func computeFirsts(of kind: Rule.Kind) -> Set { 55 | var unused = FirstMap() 56 | 57 | let firsts = computeFirsts(of: kind, map: &unused) 58 | 59 | return firsts 60 | } 61 | 62 | private func computeFirsts(of kind: Rule.Kind, map: inout FirstMap) -> Set { 63 | switch kind { 64 | case let .terminalString(value): 65 | return [.string(value)] 66 | case let .concatenation(elements): 67 | return computeFirsts(of: elements.first!, map: &map) 68 | case let .alternation(elements): 69 | var set = Set() 70 | 71 | for element in elements { 72 | set.formUnion(computeFirsts(of: element, map: &map)) 73 | } 74 | 75 | return set 76 | case let .grouping(a): 77 | return computeFirsts(of: a, map: &map) 78 | case .reference("all"): 79 | return [Terminal.all] 80 | case let .reference(name): 81 | if let firsts = map[name] { 82 | return firsts 83 | } 84 | 85 | guard let production = rules[name] else { 86 | preconditionFailure("unbound reference '\(name)'") 87 | } 88 | 89 | let firsts = computeFirsts(of: production.kind, map: &map) 90 | 91 | map[name] = firsts 92 | 93 | return firsts 94 | case let .exception(a, b): 95 | let aFirsts = computeFirsts(of: a, map: &map) 96 | let bFirsts = computeFirsts(of: b, map: &map) 97 | 98 | return aFirsts.subtracting(bFirsts) 99 | case let .occurrence(a, _): 100 | return computeFirsts(of: a, map: &map) 101 | default: 102 | return [] 103 | } 104 | } 105 | 106 | /// Compute the map of rule names to FIRST terminal sets. 107 | public func computeFirstMap() -> FirstMap { 108 | var firsts = FirstMap() 109 | 110 | for rule in rules.values { 111 | if firsts[rule.name]?.isEmpty == false { 112 | continue 113 | } 114 | 115 | let ruleFirsts = computeFirsts(of: rule.kind, map: &firsts) 116 | 117 | firsts[rule.name] = ruleFirsts 118 | } 119 | 120 | return firsts 121 | } 122 | } 123 | 124 | extension Grammar { 125 | /// Find all of the primary follow patterns (.terminalString and .reference). 126 | private var primaryFollowKinds: [String: Set] { 127 | var followKinds = [String: Set]() 128 | 129 | for rule in rules.values { 130 | let rulePrimaries = rule.kind.followKinds 131 | 132 | followKinds.merge(rulePrimaries, uniquingKeysWith: { $0.union($1) }) 133 | } 134 | 135 | return followKinds 136 | } 137 | 138 | private func alternativeNames(of kind: Rule.Kind) -> Set { 139 | var altNames = kind.alternativeNames 140 | 141 | var newNames = altNames 142 | 143 | while newNames.isEmpty == false { 144 | let start = altNames 145 | 146 | for name in newNames { 147 | guard let subrule = rules[name] else { continue } 148 | 149 | let subnames = subrule.kind.alternativeNames 150 | 151 | altNames.formUnion(subnames) 152 | } 153 | 154 | newNames = start.subtracting(altNames) 155 | } 156 | 157 | return altNames 158 | } 159 | 160 | /// Compute the map of rule names to FOLLOW terminal sets. 161 | public func computeFollowMap() -> FollowMap { 162 | var followMap = FollowMap() 163 | 164 | // step 1, initialize all sets empty, except for the starting production 165 | let startNames = unreferencedNames 166 | 167 | precondition(startNames.count == 1) 168 | 169 | let start = unreferencedNames.first! 170 | 171 | for (name, _) in rules { 172 | followMap[name] = Set() 173 | } 174 | 175 | followMap[start] = Set([.endOfInput]) 176 | followMap["all"] = Set() 177 | 178 | // step 2A: A -> αΒβ 179 | // FIRST(β) (except for ε) added to FOLLOW(B) 180 | // 181 | // Find all of the primary follow patterns (.terminalString and .reference), and track any that have a first of epsilon. 182 | let firsts = computeFirstMap() 183 | var epsilonFollows = [String: Set]() 184 | 185 | // step 2B, transform those kinds into terminals 186 | for (name, kindSet) in primaryFollowKinds { 187 | precondition(followMap[name] != nil) 188 | 189 | for kind in kindSet { 190 | switch kind { 191 | case let .terminalString(value): 192 | followMap[name]!.insert(.string(value)) 193 | case let .reference(refName): 194 | guard let firstSet = firsts[refName] else { 195 | preconditionFailure("unbound reference '\(refName)'") 196 | } 197 | 198 | followMap[name]!.formUnion(firstSet) 199 | 200 | // special-case ε, and track it separately for the next step 201 | if firstSet.contains(.epsilon) { 202 | followMap[name]!.remove(.epsilon) 203 | 204 | epsilonFollows[name, default: Set()].insert(refName) 205 | } 206 | default: 207 | preconditionFailure("unsupposed kind") 208 | } 209 | } 210 | } 211 | 212 | // step 3: copy follows 213 | // A -> αΒ 214 | // A -> αΒβ where FIRST(β) contains ε 215 | // FOLLOW(A) added to FOLLOW(B) 216 | // 217 | // This is the trickiest part. 218 | 219 | // track down trailing references 220 | for rule in rules.values { 221 | let trailingRefs = rule.kind.trailingReferences 222 | let follows = epsilonFollows[rule.name] ?? Set() 223 | 224 | // This set represents "B" 225 | let nameSet = trailingRefs.union(follows) 226 | 227 | for name in nameSet { 228 | followMap[name]!.formUnion(followMap[rule.name]!) 229 | } 230 | } 231 | 232 | // with that done, we've covered everything except for equivalent productions. 233 | for rule in rules.values { 234 | let altSet = alternativeNames(of: rule.kind) 235 | 236 | print(rule.name, ":", altSet) 237 | 238 | for altName in altSet { 239 | followMap[altName]!.formUnion(followMap[rule.name]!) 240 | } 241 | } 242 | 243 | followMap["all"] = nil 244 | 245 | return followMap 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /Sources/Gramophone/BNFParser.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Flexer 3 | 4 | public enum BNFParserError: Error { 5 | case emptyInput 6 | case unexpectedToken 7 | case missingCloseQuote 8 | } 9 | 10 | extension BNFLexerReference { 11 | func nextName() throws -> String { 12 | guard peek()?.kind == .name else { throw BNFParserError.unexpectedToken } 13 | 14 | guard let token = next() else { throw BNFParserError.unexpectedToken } 15 | 16 | precondition(token.kind == .name) 17 | 18 | let value = String(substring(for: token)) 19 | 20 | return value 21 | } 22 | } 23 | 24 | public final class BNFParser { 25 | public init() { 26 | } 27 | 28 | /// Parse the input text and return a `Grammar` value. 29 | public func parseGrammar(_ string: String) throws -> Grammar { 30 | let rules = try parse(string).get() 31 | 32 | return Grammar(rules: rules) 33 | } 34 | 35 | /// Parse the input text and return an array of `Rule` values or error. 36 | public func parse(_ string: String) -> Result<[Rule], Error> { 37 | if string.isEmpty { 38 | return .failure(BNFParserError.emptyInput) 39 | } 40 | 41 | let lexer = BNFLexer(string: string).reference 42 | 43 | var rules = [Rule]() 44 | 45 | while let token = lexer.peek() { 46 | switch token.kind { 47 | case .name: 48 | do { 49 | let rule = try parseRuleDefinition(lexer) 50 | 51 | rules.append(rule) 52 | } catch { 53 | return .failure(error) 54 | } 55 | default: 56 | _ = lexer.next() 57 | } 58 | } 59 | 60 | return .success(rules) 61 | } 62 | 63 | private func parseReferenceOrUnicodeScalar(_ lexer: BNFLexerReference) throws -> Rule.Kind { 64 | let name = try lexer.nextName() 65 | 66 | guard name == "U" else { 67 | return Rule.Kind.reference(name) 68 | } 69 | 70 | guard lexer.nextIf({ $0.kind == .plus }) != nil else { 71 | return Rule.Kind.reference(name) 72 | } 73 | 74 | let numericName = try lexer.nextName() 75 | let value = UInt32(numericName, radix: 16)! 76 | let scalar = UnicodeScalar(value)! 77 | 78 | return Rule.Kind.terminalCharacter(scalar) 79 | } 80 | 81 | private func parseBNFReference(_ lexer: BNFLexerReference) throws -> Rule.Kind { 82 | guard let _ = lexer.nextIf({ $0.kind == .lessThan }) else { 83 | throw BNFParserError.unexpectedToken 84 | } 85 | 86 | let name = try lexer.nextName() 87 | 88 | guard let _ = lexer.nextIf({ $0.kind == .greaterThan }) else { 89 | throw BNFParserError.unexpectedToken 90 | } 91 | 92 | return Rule.Kind.reference(name) 93 | } 94 | 95 | private func parseQuotedTerminal(_ lexer: BNFLexerReference) throws -> Rule.Kind { 96 | let terminator: BNFTokenKind 97 | 98 | guard let start = lexer.peek() else { 99 | throw BNFParserError.unexpectedToken 100 | } 101 | 102 | switch start.kind { 103 | case .quote: 104 | terminator = .quote 105 | case .doubleQuote: 106 | terminator = .doubleQuote 107 | case .backtick: 108 | terminator = .tick 109 | default: 110 | throw BNFParserError.unexpectedToken 111 | } 112 | 113 | _ = lexer.next() 114 | 115 | // this is messy and I think there may be a better way to do this... 116 | if lexer.peek()?.kind != terminator { 117 | guard let _ = lexer.nextUntil({ $0.kind == terminator }) else { 118 | throw BNFParserError.missingCloseQuote 119 | } 120 | } 121 | 122 | 123 | guard let end = lexer.next() else { 124 | throw BNFParserError.unexpectedToken 125 | } 126 | 127 | let contentRange = start.range.upperBound.. Rule.Kind { 135 | return try parseQuotedTerminal(lexer) 136 | } 137 | 138 | private func parseAlternation(_ lexer: BNFLexerReference, leftNode: Rule.Kind) throws -> Rule.Kind { 139 | precondition(lexer.skipIf({ $0.kind == .pipe })) 140 | 141 | var expressions = [leftNode] 142 | 143 | while true { 144 | let exp = try parsePrimaryExpression(lexer) 145 | 146 | expressions.append(exp) 147 | 148 | if lexer.skipIf({ $0.kind == .pipe }) { 149 | continue 150 | } else { 151 | break 152 | } 153 | } 154 | 155 | return .alternation(expressions) 156 | } 157 | 158 | private func parseConcatenation(_ lexer: BNFLexerReference, leftNode: Rule.Kind) throws -> Rule.Kind { 159 | _ = lexer.skipIf({ $0.kind == .comma }) 160 | 161 | var expressions = [leftNode] 162 | 163 | while true { 164 | let exp = try parsePrimaryExpression(lexer) 165 | 166 | expressions.append(exp) 167 | 168 | switch lexer.peek()?.kind { 169 | case .comma: 170 | _ = lexer.next() 171 | continue 172 | case .quote, .doubleQuote, .openBracket, .name, .openBrace, .lessThan, .openParen: 173 | continue 174 | default: 175 | break 176 | } 177 | 178 | break 179 | } 180 | 181 | return .concatenation(expressions) 182 | } 183 | 184 | private func parseException(_ lexer: BNFLexerReference, leftNode: Rule.Kind) throws -> Rule.Kind { 185 | guard lexer.skipIf({ $0.kind == .minus }) else { 186 | throw BNFParserError.unexpectedToken 187 | } 188 | 189 | let right = try parsePrimaryExpression(lexer) 190 | 191 | return .exception(leftNode, right) 192 | } 193 | 194 | private func parseOptional(_ lexer: BNFLexerReference) throws -> Rule.Kind { 195 | guard let _ = lexer.nextIf({ $0.kind == .openBracket }) else { 196 | throw BNFParserError.unexpectedToken 197 | } 198 | 199 | let rule = try parseExpression(lexer) 200 | 201 | guard let _ = lexer.nextIf({ $0.kind == .closeBracket }) else { 202 | throw BNFParserError.unexpectedToken 203 | } 204 | 205 | switch rule { 206 | case let .exception(.terminalCharacter(a), .terminalCharacter(b)): 207 | // this is a hack that transforms a previous parse into the desired form 208 | return .range(a, b) 209 | default: 210 | return .occurrence(rule, frequency: .zeroOrOne) 211 | } 212 | } 213 | 214 | private func parseRepetition(_ lexer: BNFLexerReference) throws -> Rule.Kind { 215 | guard let _ = lexer.nextIf({ $0.kind == .openBrace }) else { 216 | throw BNFParserError.unexpectedToken 217 | } 218 | 219 | let exp = try parseExpression(lexer) 220 | 221 | guard let _ = lexer.nextIf({ $0.kind == .closeBrace }) else { 222 | throw BNFParserError.unexpectedToken 223 | } 224 | 225 | return .occurrence(exp, frequency: .zeroOrMore) 226 | } 227 | 228 | private func parseGrouping(_ lexer: BNFLexerReference) throws -> Rule.Kind { 229 | guard let _ = lexer.nextIf({ $0.kind == .openParen }) else { 230 | throw BNFParserError.unexpectedToken 231 | } 232 | 233 | let kind = try parseExpression(lexer) 234 | 235 | guard let _ = lexer.nextIf({ $0.kind == .closeParen }) else { 236 | throw BNFParserError.unexpectedToken 237 | } 238 | 239 | return .grouping(kind) 240 | } 241 | 242 | private func parsePrimaryExpression(_ lexer: BNFLexerReference) throws -> Rule.Kind { 243 | _ = lexer.skipUntil({ $0.kind != .newline }) 244 | 245 | let kind = switch lexer.peek()?.kind { 246 | case .quote, .doubleQuote, .backtick: 247 | try parseTerminal(lexer) 248 | case .openBracket: 249 | try parseOptional(lexer) 250 | case .name: 251 | try parseReferenceOrUnicodeScalar(lexer) 252 | case .lessThan: 253 | try parseBNFReference(lexer) 254 | case .openBrace: 255 | try parseRepetition(lexer) 256 | case .openParen: 257 | try parseGrouping(lexer) 258 | default: 259 | throw BNFParserError.unexpectedToken 260 | } 261 | 262 | switch lexer.peek()?.kind { 263 | case .star: 264 | _ = lexer.next() 265 | 266 | return .occurrence(kind, frequency: .zeroOrMore) 267 | case .plus: 268 | _ = lexer.next() 269 | 270 | return .occurrence(kind, frequency: .oneOrMore) 271 | default: 272 | return kind 273 | } 274 | } 275 | 276 | private func parseRuleExpression(_ lexer: BNFLexerReference, leftNode: Rule.Kind) throws -> Rule.Kind? { 277 | switch lexer.peek()?.kind { 278 | case .question: 279 | _ = lexer.next() 280 | 281 | return .occurrence(leftNode, frequency: .zeroOrOne) 282 | case .pipe: 283 | return try parseAlternation(lexer, leftNode: leftNode) 284 | case .comma, .name, .quote, .doubleQuote, .backtick, .openBrace, .openParen, .openBracket: 285 | return try parseConcatenation(lexer, leftNode: leftNode) 286 | case .minus: 287 | return try parseException(lexer, leftNode: leftNode) 288 | case .closeBrace, .closeParen, .closeBracket: 289 | return nil 290 | default: 291 | throw BNFParserError.unexpectedToken 292 | } 293 | } 294 | 295 | private func parseExpression(_ lexer: BNFLexerReference) throws -> Rule.Kind { 296 | let start = try parsePrimaryExpression(lexer) 297 | 298 | var ending = start 299 | 300 | while let token = lexer.peek() { 301 | if token.kind == .semicolon || token.kind == .newline { 302 | break 303 | } 304 | 305 | guard let sub = try parseRuleExpression(lexer, leftNode: ending) else { 306 | break 307 | } 308 | 309 | ending = sub 310 | } 311 | 312 | return ending 313 | } 314 | 315 | func parseAssignmentOperator(_ lexer: BNFLexerReference) throws -> Bool { 316 | if lexer.skipIf({ $0.kind == .assignment }) { 317 | return true 318 | } 319 | 320 | guard lexer.skipIf({ $0.kind == .colon }) else { 321 | return false 322 | } 323 | 324 | guard lexer.skipIf({ $0.kind == .colon }) else { 325 | return false 326 | } 327 | 328 | return lexer.skipIf({ $0.kind == .assignment }) 329 | } 330 | 331 | private func parseRuleDefinition(_ lexer: BNFLexerReference) throws -> Rule { 332 | let name = try lexer.nextName() 333 | 334 | guard try parseAssignmentOperator(lexer) else { 335 | throw BNFParserError.unexpectedToken 336 | } 337 | 338 | let exp = try parseExpression(lexer) 339 | 340 | return Rule(name: name, kind: exp) 341 | } 342 | } 343 | -------------------------------------------------------------------------------- /Tests/GramophoneTests/ParserTests.swift: -------------------------------------------------------------------------------- 1 | import Testing 2 | import Gramophone 3 | 4 | struct ParserTests { 5 | @Test 6 | func assignment() throws { 7 | let string = "test = 'a';" 8 | let parser = BNFParser() 9 | 10 | let rules = try parser.parse(string).get() 11 | 12 | #expect(rules == [Rule("test", kind: "a")]) 13 | } 14 | 15 | @Test 16 | func BNFStyleAssignment() throws { 17 | let string = "test ::= abc;" 18 | let parser = BNFParser() 19 | 20 | let rules = try parser.parse(string).get() 21 | 22 | #expect(rules == [Rule("test", kind: .reference("abc"))]) 23 | } 24 | 25 | @Test 26 | func assignmentReference() throws { 27 | let string = "test = a" 28 | let parser = BNFParser() 29 | 30 | let rules = try parser.parse(string).get() 31 | 32 | #expect(rules == [Rule("test", kind: .reference("a"))]) 33 | } 34 | 35 | @Test 36 | func backslashTerminal() throws { 37 | let string = "test = '\\';" 38 | let parser = BNFParser() 39 | 40 | let rules = try parser.parse(string).get() 41 | 42 | #expect(rules == [Rule("test", kind: "\\")]) 43 | } 44 | 45 | @Test 46 | func reference() throws { 47 | let string = "test = something;" 48 | let parser = BNFParser() 49 | 50 | let rules = try parser.parse(string).get() 51 | 52 | #expect(rules == [Rule("test", kind: .reference("something"))]) 53 | } 54 | 55 | @Test 56 | func BNFStyleReference() throws { 57 | let string = "test = ;" 58 | let parser = BNFParser() 59 | 60 | let rules = try parser.parse(string).get() 61 | 62 | #expect(rules == [Rule("test", kind: .reference("something"))]) 63 | } 64 | 65 | @Test 66 | func alternation() throws { 67 | let string = "test = 'a' | 'b';" 68 | let parser = BNFParser() 69 | 70 | let rules = try parser.parse(string).get() 71 | 72 | let expectedRule = Rule( 73 | "test", 74 | kind: .alternation(["a", "b"]) 75 | ) 76 | #expect(rules == [expectedRule]) 77 | } 78 | 79 | @Test 80 | func alternationWithThreeElements() throws { 81 | let string = "test = 'a' | 'b' | 'c';" 82 | let parser = BNFParser() 83 | 84 | let rules = try parser.parse(string).get() 85 | 86 | let expectedRule = Rule( 87 | "test", 88 | kind: .alternation(["a", "b", "c"]) 89 | ) 90 | #expect(rules == [expectedRule]) 91 | } 92 | 93 | @Test 94 | func alternationOnNewline() throws { 95 | let string = "test = 'a' |\n 'b';" 96 | let parser = BNFParser() 97 | 98 | let rules = try parser.parse(string).get() 99 | 100 | let expectedRule = Rule( 101 | "test", 102 | kind: .alternation(["a", "b"]) 103 | ) 104 | #expect(rules == [expectedRule]) 105 | } 106 | 107 | @Test 108 | func concatenation() throws { 109 | let string = "test = 'a', 'b';" 110 | let parser = BNFParser() 111 | 112 | let rules = try parser.parse(string).get() 113 | 114 | let expectedRule = Rule( 115 | name: "test", 116 | kind: .concatenation(["a", "b"]) 117 | ) 118 | #expect(rules == [expectedRule]) 119 | } 120 | 121 | @Test 122 | func concatenationWithThreeElements() throws { 123 | let string = "test = 'a', 'b', 'c';" 124 | let parser = BNFParser() 125 | 126 | let rules = try parser.parse(string).get() 127 | 128 | let expectedRule = Rule( 129 | name: "test", 130 | kind: .concatenation(["a", "b", "c"]) 131 | ) 132 | #expect(rules == [expectedRule]) 133 | } 134 | 135 | @Test 136 | func implicitConcatenation() throws { 137 | let string = "test = 'a' 'b';" 138 | let parser = BNFParser() 139 | 140 | let rules = try parser.parse(string).get() 141 | 142 | let expectedRule = Rule( 143 | name: "test", 144 | kind: .concatenation(["a", "b"]) 145 | ) 146 | #expect(rules == [expectedRule]) 147 | } 148 | 149 | @Test 150 | func implicitConcatenationWithOptional() throws { 151 | let string = "test = 'a' ['b'];" 152 | let parser = BNFParser() 153 | 154 | let rules = try parser.parse(string).get() 155 | 156 | let expectedRule = Rule( 157 | name: "test", 158 | kind: .concatenation(["a", .optional("b")]) 159 | ) 160 | #expect(rules == [expectedRule]) 161 | } 162 | 163 | @Test func implicitConcatenationWithAngledQuotes() throws { 164 | let string = "test = 'a' `b´;" 165 | let parser = BNFParser() 166 | 167 | let rules = try parser.parse(string).get() 168 | 169 | let expectedRule = Rule( 170 | name: "test", 171 | kind: .concatenation(["a", "b"]) 172 | ) 173 | #expect(rules == [expectedRule]) 174 | } 175 | 176 | @Test 177 | func implicitConcatenationWithThreeElements() throws { 178 | let string = "test = 'a' 'b' 'c';" 179 | let parser = BNFParser() 180 | 181 | let rules = try parser.parse(string).get() 182 | 183 | let expectedRule = Rule( 184 | name: "test", 185 | kind: .concatenation(["a", "b", "c"]) 186 | ) 187 | #expect(rules == [expectedRule]) 188 | } 189 | 190 | @Test 191 | func optional() throws { 192 | let string = "test = ['-'], 'a';" 193 | let parser = BNFParser() 194 | 195 | let rules = try parser.parse(string).get() 196 | 197 | let expectedRule = Rule( 198 | name: "test", 199 | kind: .concatenation([.optional("-"), "a"]) 200 | ) 201 | #expect(rules == [expectedRule]) 202 | } 203 | 204 | @Test 205 | func tailingOptional() throws { 206 | let string = "a = b?;" 207 | let parser = BNFParser() 208 | 209 | let rules = try parser.parse(string).get() 210 | 211 | let expectedRules = [ 212 | Rule(name: "a", kind: .optional(.reference("b"))), 213 | ] 214 | 215 | #expect(rules == expectedRules) 216 | } 217 | 218 | @Test 219 | func repetition() throws { 220 | let string = "test = {'a'};" 221 | let parser = BNFParser() 222 | 223 | let rules = try parser.parse(string).get() 224 | 225 | let expectedRule = Rule( 226 | name: "test", 227 | kind: .occurrence("a", frequency: .zeroOrMore) 228 | ) 229 | #expect(rules == [expectedRule]) 230 | } 231 | 232 | @Test 233 | func starRepetition() throws { 234 | let string = "test = 'a'*;" 235 | let parser = BNFParser() 236 | 237 | let rules = try parser.parse(string).get() 238 | 239 | let expectedRule = Rule( 240 | name: "test", 241 | kind: .occurrence("a", frequency: .zeroOrMore) 242 | ) 243 | #expect(rules == [expectedRule]) 244 | } 245 | 246 | @Test 247 | func plusRepetition() throws { 248 | let string = "test = 'a'+;" 249 | let parser = BNFParser() 250 | 251 | let rules = try parser.parse(string).get() 252 | 253 | let expectedRule = Rule( 254 | name: "test", 255 | kind: .occurrence("a", frequency: .oneOrMore) 256 | ) 257 | #expect(rules == [expectedRule]) 258 | } 259 | 260 | @Test 261 | func grouping() throws { 262 | let string = "test = ('a');" 263 | let parser = BNFParser() 264 | 265 | let rules = try parser.parse(string).get() 266 | 267 | let expectedRule = Rule( 268 | name: "test", 269 | kind: .grouping("a") 270 | ) 271 | #expect(rules == [expectedRule]) 272 | } 273 | 274 | @Test 275 | func exception() throws { 276 | let string = "test = a - b;" 277 | let parser = BNFParser() 278 | 279 | let rules = try parser.parse(string).get() 280 | 281 | let expectedRule = Rule( 282 | name: "test", 283 | kind: .exception(.reference("a"), .reference("b")) 284 | ) 285 | #expect(rules == [expectedRule]) 286 | } 287 | } 288 | 289 | extension ParserTests { 290 | @Test 291 | func assignmentWithArrow() throws { 292 | let string = "test → 'a';" 293 | let parser = BNFParser() 294 | 295 | let rules = try parser.parse(string).get() 296 | 297 | #expect(rules == [Rule("test", kind: "a")]) 298 | } 299 | 300 | @Test 301 | func assignmentWithSymbols() throws { 302 | let string = "test = '%';" 303 | let parser = BNFParser() 304 | 305 | let rules = try parser.parse(string).get() 306 | 307 | #expect(rules == [Rule("test", kind: "%")]) 308 | } 309 | 310 | @Test 311 | func emptyCodePoint() throws { 312 | let string = "test = U+0000;" 313 | let parser = BNFParser() 314 | 315 | let rules = try parser.parse(string).get() 316 | 317 | #expect(rules == [Rule("test", kind: .terminalCharacter(0))]) 318 | } 319 | 320 | @Test 321 | func fiveDigitCodePoint() throws { 322 | let string = "test = U+1F34E;" 323 | let parser = BNFParser() 324 | 325 | let rules = try parser.parse(string).get() 326 | 327 | #expect(rules == [Rule("test", kind: .terminalCharacter(0x1F34E))]) 328 | } 329 | 330 | @Test 331 | func codePointRange() throws { 332 | let string = "test = [U+0000-U+0001];" 333 | let parser = BNFParser() 334 | 335 | let rules = try parser.parse(string).get() 336 | 337 | #expect(rules == [Rule("test", kind: .range(0, 1))]) 338 | } 339 | 340 | @Test 341 | func newlines() throws { 342 | let string = """ 343 | test = 'a'; 344 | """ 345 | let parser = BNFParser() 346 | 347 | let rules = try parser.parse(string).get() 348 | 349 | #expect(rules == [Rule("test", kind: "a")]) 350 | } 351 | 352 | @Test 353 | func doubleQuoteTerminal() throws { 354 | let string = "test = \"a\";" 355 | let parser = BNFParser() 356 | 357 | let rules = try parser.parse(string).get() 358 | 359 | #expect(rules == [Rule("test", kind: "a")]) 360 | } 361 | 362 | @Test 363 | func angledQuoteTerminal() throws { 364 | let string = "test = `a´" 365 | let parser = BNFParser() 366 | 367 | let rules = try parser.parse(string).get() 368 | 369 | #expect(rules == [Rule("test", kind: "a")]) 370 | } 371 | 372 | @Test 373 | func doubleQuoteSpace() throws { 374 | let string = "test = \" \"" 375 | let parser = BNFParser() 376 | 377 | let rules = try parser.parse(string).get() 378 | 379 | #expect(rules == [Rule("test", kind: " ")]) 380 | } 381 | 382 | @Test 383 | func multipleAssignments() throws { 384 | let string = """ 385 | a = 'a'; 386 | b = 'b'; 387 | """ 388 | let rules = try BNFParser().parse(string).get() 389 | 390 | let expectedRules = [ 391 | Rule("a", kind: "a"), 392 | Rule("b", kind: "b"), 393 | ] 394 | 395 | #expect(rules == expectedRules) 396 | } 397 | 398 | @Test 399 | func multipleNewLineSeparatedAssignments() throws { 400 | let string = """ 401 | a = 'a' 402 | 403 | b = 'b' 404 | """ 405 | let rules = try BNFParser().parse(string).get() 406 | 407 | let expectedRules = [ 408 | Rule("a", kind: "a"), 409 | Rule("b", kind: "b"), 410 | ] 411 | 412 | #expect(rules == expectedRules) 413 | } 414 | 415 | @Test 416 | func singleNewLineSeparatedAssignments() throws { 417 | let string = """ 418 | a = 'a' 419 | b = 'b' 420 | """ 421 | let rules = try BNFParser().parse(string).get() 422 | 423 | let expectedRules = [ 424 | Rule("a", kind: "a"), 425 | Rule("b", kind: "b"), 426 | ] 427 | 428 | #expect(rules == expectedRules) 429 | } 430 | } 431 | 432 | extension ParserTests { 433 | @Test 434 | func multipleRules() throws { 435 | let string = "a = 'a';b = 'b';" 436 | let parser = BNFParser() 437 | 438 | let rules = try parser.parse(string).get() 439 | 440 | let expectedRules = [ 441 | Rule(name: "a", kind: "a"), 442 | Rule(name: "b", kind: "b"), 443 | ] 444 | 445 | #expect(rules == expectedRules) 446 | } 447 | 448 | @Test 449 | func concatenationWithinRepetition() throws { 450 | let string = "a = {a, b};" 451 | let parser = BNFParser() 452 | 453 | let rules = try parser.parse(string).get() 454 | 455 | let expectedRules = [ 456 | Rule(name: "a", kind: .occurrence(.concatenation([.reference("a"), .reference("b")]), frequency: .zeroOrMore)) 457 | ] 458 | 459 | #expect(rules == expectedRules) 460 | } 461 | 462 | @Test 463 | func concatenationWithGrouping() throws { 464 | let string = "a = b (c);" 465 | let parser = BNFParser() 466 | 467 | let rules = try parser.parse(string).get() 468 | 469 | let expectedRules = [ 470 | Rule(name: "a", kind: .concatenation([.reference("b"), .grouping(.reference("c"))])), 471 | ] 472 | 473 | #expect(rules == expectedRules) 474 | } 475 | 476 | @Test 477 | func concatenationWithRepetition() throws { 478 | let string = "a = b {c};" 479 | let parser = BNFParser() 480 | 481 | let rules = try parser.parse(string).get() 482 | 483 | let expectedRules = [ 484 | Rule(name: "a", kind: .concatenation([.reference("b"), .occurrence(.reference("c"), frequency: .zeroOrMore)])), 485 | ] 486 | 487 | #expect(rules == expectedRules) 488 | } 489 | } 490 | 491 | extension ParserTests { 492 | @Test 493 | func concatenationAlternationPrecedence() throws { 494 | let string = "test = a b | c;" 495 | let parser = BNFParser() 496 | 497 | let rules = try parser.parse(string).get() 498 | 499 | let expectedRule = Rule( 500 | name: "test", 501 | kind: .alternation([.concatenation([.reference("a"), .reference("b")]), .reference("c")]) 502 | ) 503 | #expect(rules == [expectedRule]) 504 | } 505 | 506 | @Test(.disabled()) 507 | func alternationConcatenationPrecedence() throws { 508 | let string = "test = a | b c;" 509 | let parser = BNFParser() 510 | 511 | let rules = try parser.parse(string).get() 512 | 513 | let expectedRule = Rule( 514 | name: "test", 515 | kind: .alternation([.reference("a"), .concatenation([.reference("b"), .reference("c")])]) 516 | ) 517 | #expect(rules == [expectedRule]) 518 | } 519 | } 520 | -------------------------------------------------------------------------------- /Grammars/Swift.ebnf: -------------------------------------------------------------------------------- 1 | whitespace → whitespace-item whitespace? 2 | whitespace-item → line-break 3 | whitespace-item → inline-space 4 | whitespace-item → comment 5 | whitespace-item → multiline-comment 6 | whitespace-item → U+0000, U+000B, or U+000C 7 | line-break → U+000A 8 | line-break → U+000D 9 | line-break → U+000D followed by U+000A 10 | inline-spaces → inline-space inline-spaces? 11 | inline-space → U+0009 or U+0020 12 | comment → '//' comment-text line-break 13 | multiline-comment → '/*' multiline-comment-text '*/' 14 | comment-text → comment-text-item comment-text? 15 | comment-text-item → Any Unicode scalar value except U+000A or U+000D 16 | multiline-comment-text → multiline-comment-text-item multiline-comment-text? 17 | multiline-comment-text-item → multiline-comment 18 | multiline-comment-text-item → comment-text-item 19 | multiline-comment-text-item → Any Unicode scalar value except '/*' or '*/' 20 | identifier → identifier-head identifier-characters? 21 | identifier → '`' identifier-head identifier-characters? '`' 22 | identifier → implicit-parameter-name 23 | identifier → property-wrapper-projection 24 | identifier-list → identifier | identifier ',' identifier-list 25 | identifier-head → Upper- or lowercase letter A through Z 26 | identifier-head → '_' 27 | identifier-head → U+00A8, U+00AA, U+00AD, U+00AF, U+00B2–U+00B5, or U+00B7–U+00BA 28 | identifier-head → U+00BC–U+00BE, U+00C0–U+00D6, U+00D8–U+00F6, or U+00F8–U+00FF 29 | identifier-head → U+0100–U+02FF, U+0370–U+167F, U+1681–U+180D, or U+180F–U+1DBF 30 | identifier-head → U+1E00–U+1FFF 31 | identifier-head → U+200B–U+200D, U+202A–U+202E, U+203F–U+2040, U+2054, or U+2060–U+206F 32 | identifier-head → U+2070–U+20CF, U+2100–U+218F, U+2460–U+24FF, or U+2776–U+2793 33 | identifier-head → U+2C00–U+2DFF or U+2E80–U+2FFF 34 | identifier-head → U+3004–U+3007, U+3021–U+302F, U+3031–U+303F, or U+3040–U+D7FF 35 | identifier-head → U+F900–U+FD3D, U+FD40–U+FDCF, U+FDF0–U+FE1F, or U+FE30–U+FE44 36 | identifier-head → U+FE47–U+FFFD 37 | identifier-head → U+10000–U+1FFFD, U+20000–U+2FFFD, U+30000–U+3FFFD, or U+40000–U+4FFFD 38 | identifier-head → U+50000–U+5FFFD, U+60000–U+6FFFD, U+70000–U+7FFFD, or U+80000–U+8FFFD 39 | identifier-head → U+90000–U+9FFFD, U+A0000–U+AFFFD, U+B0000–U+BFFFD, or U+C0000–U+CFFFD 40 | identifier-head → U+D0000–U+DFFFD or U+E0000–U+EFFFD 41 | identifier-character → Digit 0 through 9 42 | identifier-character → U+0300–U+036F, U+1DC0–U+1DFF, U+20D0–U+20FF, or U+FE20–U+FE2F 43 | identifier-character → identifier-head 44 | identifier-characters → identifier-character identifier-characters? 45 | implicit-parameter-name → '$' decimal-digits 46 | property-wrapper-projection → '$' identifier-characters 47 | literal → numeric-literal | string-literal | regular-expression-literal | boolean-literal | nil-literal 48 | numeric-literal → '-'? integer-literal | '-'? floating-point-literal 49 | boolean-literal → 'true' | 'false' 50 | nil-literal → 'nil' 51 | integer-literal → binary-literal 52 | integer-literal → octal-literal 53 | integer-literal → decimal-literal 54 | integer-literal → hexadecimal-literal 55 | binary-literal → '0b' binary-digit binary-literal-characters? 56 | binary-digit → Digit 0 or 1 57 | binary-literal-character → binary-digit | '_' 58 | binary-literal-characters → binary-literal-character binary-literal-characters? 59 | octal-literal → '0o' octal-digit octal-literal-characters? 60 | octal-digit → Digit 0 through 7 61 | octal-literal-character → octal-digit | '_' 62 | octal-literal-characters → octal-literal-character octal-literal-characters? 63 | decimal-literal → decimal-digit decimal-literal-characters? 64 | decimal-digit → Digit 0 through 9 65 | decimal-digits → decimal-digit decimal-digits? 66 | decimal-literal-character → decimal-digit | '_' 67 | decimal-literal-characters → decimal-literal-character decimal-literal-characters? 68 | hexadecimal-literal → '0x' hexadecimal-digit hexadecimal-literal-characters? 69 | hexadecimal-digit → Digit 0 through 9, a through f, or A through F 70 | hexadecimal-literal-character → hexadecimal-digit | '_' 71 | hexadecimal-literal-characters → hexadecimal-literal-character hexadecimal-literal-characters? 72 | floating-point-literal → decimal-literal decimal-fraction? decimal-exponent? 73 | floating-point-literal → hexadecimal-literal hexadecimal-fraction? hexadecimal-exponent 74 | decimal-fraction → '.' decimal-literal 75 | decimal-exponent → floating-point-e sign? decimal-literal 76 | hexadecimal-fraction → '.' hexadecimal-digit hexadecimal-literal-characters? 77 | hexadecimal-exponent → floating-point-p sign? decimal-literal 78 | floating-point-e → 'e' | 'E' 79 | floating-point-p → 'p' | 'P' 80 | sign → '+' | '-' 81 | string-literal → static-string-literal | interpolated-string-literal 82 | string-literal-opening-delimiter → extended-string-literal-delimiter? '"' 83 | string-literal-closing-delimiter → '"' extended-string-literal-delimiter? 84 | static-string-literal → string-literal-opening-delimiter quoted-text? string-literal-closing-delimiter 85 | static-string-literal → multiline-string-literal-opening-delimiter multiline-quoted-text? multiline-string-literal-closing-delimiter 86 | multiline-string-literal-opening-delimiter → extended-string-literal-delimiter? '"""' 87 | multiline-string-literal-closing-delimiter → '"""' extended-string-literal-delimiter? 88 | extended-string-literal-delimiter → '#' extended-string-literal-delimiter? 89 | quoted-text → quoted-text-item quoted-text? 90 | quoted-text-item → escaped-character 91 | quoted-text-item → Any Unicode scalar value except '"', '\', U+000A, or U+000D 92 | multiline-quoted-text → multiline-quoted-text-item multiline-quoted-text? 93 | multiline-quoted-text-item → escaped-character 94 | multiline-quoted-text-item → Any Unicode scalar value except '\' 95 | multiline-quoted-text-item → escaped-newline 96 | interpolated-string-literal → string-literal-opening-delimiter interpolated-text? string-literal-closing-delimiter 97 | interpolated-string-literal → multiline-string-literal-opening-delimiter multiline-interpolated-text? multiline-string-literal-closing-delimiter 98 | interpolated-text → interpolated-text-item interpolated-text? 99 | interpolated-text-item → '\(' expression ')' | quoted-text-item 100 | multiline-interpolated-text → multiline-interpolated-text-item multiline-interpolated-text? 101 | multiline-interpolated-text-item → '\(' expression ')' | multiline-quoted-text-item 102 | escape-sequence → '\' extended-string-literal-delimiter 103 | escaped-character → escape-sequence '0' | escape-sequence '\' | escape-sequence 't' | escape-sequence 'n' | escape-sequence 'r' | escape-sequence '"' | escape-sequence "'" 104 | escaped-character → escape-sequence 'u' '{' unicode-scalar-digits '}' 105 | unicode-scalar-digits → Between one and eight hexadecimal digits 106 | escaped-newline → escape-sequence inline-spaces? line-break 107 | regular-expression-literal → regular-expression-literal-opening-delimiter regular-expression regular-expression-literal-closing-delimiter 108 | regular-expression → Any regular expression 109 | regular-expression-literal-opening-delimiter → extended-regular-expression-literal-delimiter? '/' 110 | regular-expression-literal-closing-delimiter → '/' extended-regular-expression-literal-delimiter? 111 | extended-regular-expression-literal-delimiter → '#' extended-regular-expression-literal-delimiter? 112 | operator → operator-head operator-characters? 113 | operator → dot-operator-head dot-operator-characters 114 | operator-head → '/' | '=' | '-' | '+' | '!' | '*' | '%' | '<' | '>' | '&' | '|' | '^' | '~' | '?' 115 | operator-head → U+00A1–U+00A7 116 | operator-head → U+00A9 or U+00AB 117 | operator-head → U+00AC or U+00AE 118 | operator-head → U+00B0–U+00B1 119 | operator-head → U+00B6, U+00BB, U+00BF, U+00D7, or U+00F7 120 | operator-head → U+2016–U+2017 121 | operator-head → U+2020–U+2027 122 | operator-head → U+2030–U+203E 123 | operator-head → U+2041–U+2053 124 | operator-head → U+2055–U+205E 125 | operator-head → U+2190–U+23FF 126 | operator-head → U+2500–U+2775 127 | operator-head → U+2794–U+2BFF 128 | operator-head → U+2E00–U+2E7F 129 | operator-head → U+3001–U+3003 130 | operator-head → U+3008–U+3020 131 | operator-head → U+3030 132 | operator-character → operator-head 133 | operator-character → U+0300–U+036F 134 | operator-character → U+1DC0–U+1DFF 135 | operator-character → U+20D0–U+20FF 136 | operator-character → U+FE00–U+FE0F 137 | operator-character → U+FE20–U+FE2F 138 | operator-character → U+E0100–U+E01EF 139 | operator-characters → operator-character operator-characters? 140 | dot-operator-head → '.' 141 | dot-operator-character → '.' | operator-character 142 | dot-operator-characters → dot-operator-character dot-operator-characters? 143 | infix-operator → operator 144 | prefix-operator → operator 145 | postfix-operator → operator 146 | type → function-type 147 | type → array-type 148 | type → dictionary-type 149 | type → type-identifier 150 | type → tuple-type 151 | type → optional-type 152 | type → implicitly-unwrapped-optional-type 153 | type → protocol-composition-type 154 | type → opaque-type 155 | type → metatype-type 156 | type → any-type 157 | type → self-type 158 | type → '(' type ')' 159 | type-annotation → ':' attributes? 'inout'? type 160 | type-identifier → type-name generic-argument-clause? | type-name generic-argument-clause? '.' type-identifier 161 | type-name → identifier 162 | tuple-type → '(' ')' | '(' tuple-type-element ',' tuple-type-element-list ')' 163 | tuple-type-element-list → tuple-type-element | tuple-type-element ',' tuple-type-element-list 164 | tuple-type-element → element-name type-annotation | type 165 | element-name → identifier 166 | function-type → attributes? function-type-argument-clause 'async'? throws-clause? '->' type 167 | function-type-argument-clause → '(' ')' 168 | function-type-argument-clause → '(' function-type-argument-list '...'? ')' 169 | function-type-argument-list → function-type-argument | function-type-argument ',' function-type-argument-list 170 | function-type-argument → attributes? 'inout'? type | argument-label type-annotation 171 | argument-label → identifier 172 | throws-clause → 'throws' | 'throws' '(' type ')' 173 | array-type → '[' type ']' 174 | dictionary-type → '[' type ':' type ']' 175 | optional-type → type '?' 176 | implicitly-unwrapped-optional-type → type '!' 177 | protocol-composition-type → type-identifier '&' protocol-composition-continuation 178 | protocol-composition-continuation → type-identifier | protocol-composition-type 179 | opaque-type → 'some' type 180 | boxed-protocol-type → 'any' type 181 | metatype-type → type '.' 'Type' | type '.' 'Protocol' 182 | any-type → 'Any' 183 | self-type → 'Self' 184 | type-inheritance-clause → ':' type-inheritance-list 185 | type-inheritance-list → attributes? type-identifier | attributes? type-identifier ',' type-inheritance-list 186 | expression → try-operator? await-operator? prefix-expression infix-expressions? 187 | prefix-expression → prefix-operator? postfix-expression 188 | prefix-expression → in-out-expression 189 | in-out-expression → '&' primary-expression 190 | try-operator → 'try' | 'try' '?' | 'try' '!' 191 | await-operator → 'await' 192 | infix-expression → infix-operator prefix-expression 193 | infix-expression → assignment-operator try-operator? await-operator? prefix-expression 194 | infix-expression → conditional-operator try-operator? await-operator? prefix-expression 195 | infix-expression → type-casting-operator 196 | infix-expressions → infix-expression infix-expressions? 197 | assignment-operator → '=' 198 | conditional-operator → '?' expression ':' 199 | type-casting-operator → 'is' type 200 | type-casting-operator → 'as' type 201 | type-casting-operator → 'as' '?' type 202 | type-casting-operator → 'as' '!' type 203 | primary-expression → identifier generic-argument-clause? 204 | primary-expression → literal-expression 205 | primary-expression → self-expression 206 | primary-expression → superclass-expression 207 | primary-expression → conditional-expression 208 | primary-expression → closure-expression 209 | primary-expression → parenthesized-expression 210 | primary-expression → tuple-expression 211 | primary-expression → implicit-member-expression 212 | primary-expression → wildcard-expression 213 | primary-expression → macro-expansion-expression 214 | primary-expression → key-path-expression 215 | primary-expression → selector-expression 216 | primary-expression → key-path-string-expression 217 | literal-expression → literal 218 | literal-expression → array-literal | dictionary-literal | playground-literal 219 | array-literal → '[' array-literal-items? ']' 220 | array-literal-items → array-literal-item ','? | array-literal-item ',' array-literal-items 221 | array-literal-item → expression 222 | dictionary-literal → '[' dictionary-literal-items ']' | '[' ':' ']' 223 | dictionary-literal-items → dictionary-literal-item ','? | dictionary-literal-item ',' dictionary-literal-items 224 | dictionary-literal-item → expression ':' expression 225 | playground-literal → '#colorLiteral' '(' 'red' ':' expression ',' 'green' ':' expression ',' 'blue' ':' expression ',' 'alpha' ':' expression ')' 226 | playground-literal → '#fileLiteral' '(' 'resourceName' ':' expression ')' 227 | playground-literal → '#imageLiteral' '(' 'resourceName' ':' expression ')' 228 | self-expression → 'self' | self-method-expression | self-subscript-expression | self-initializer-expression 229 | self-method-expression → 'self' '.' identifier 230 | self-subscript-expression → 'self' '[' function-call-argument-list ']' 231 | self-initializer-expression → 'self' '.' 'init' 232 | superclass-expression → superclass-method-expression | superclass-subscript-expression | superclass-initializer-expression 233 | superclass-method-expression → 'super' '.' identifier 234 | superclass-subscript-expression → 'super' '[' function-call-argument-list ']' 235 | superclass-initializer-expression → 'super' '.' 'init' 236 | conditional-expression → if-expression | switch-expression 237 | if-expression → 'if' condition-list '{' statement '}' if-expression-tail 238 | if-expression-tail → 'else' if-expression 239 | if-expression-tail → 'else' '{' statement '}' 240 | switch-expression → 'switch' expression '{' switch-expression-cases '}' 241 | switch-expression-cases → switch-expression-case switch-expression-cases? 242 | switch-expression-case → case-label statement 243 | switch-expression-case → default-label statement 244 | closure-expression → '{' attributes? closure-signature? statements? '}' 245 | closure-signature → capture-list? closure-parameter-clause 'async'? throws-clause? function-result? 'in' 246 | closure-signature → capture-list 'in' 247 | closure-parameter-clause → '(' ')' | '(' closure-parameter-list ')' | identifier-list 248 | closure-parameter-list → closure-parameter | closure-parameter ',' closure-parameter-list 249 | closure-parameter → closure-parameter-name type-annotation? 250 | closure-parameter → closure-parameter-name type-annotation '...' 251 | closure-parameter-name → identifier 252 | capture-list → '[' capture-list-items ']' 253 | capture-list-items → capture-list-item | capture-list-item ',' capture-list-items 254 | capture-list-item → capture-specifier? identifier 255 | capture-list-item → capture-specifier? identifier '=' expression 256 | capture-list-item → capture-specifier? self-expression 257 | capture-specifier → 'weak' | 'unowned' | 'unowned(safe)' | 'unowned(unsafe)' 258 | implicit-member-expression → '.' identifier 259 | implicit-member-expression → '.' identifier '.' postfix-expression 260 | parenthesized-expression → '(' expression ')' 261 | tuple-expression → '(' ')' | '(' tuple-element ',' tuple-element-list ')' 262 | tuple-element-list → tuple-element | tuple-element ',' tuple-element-list 263 | tuple-element → expression | identifier ':' expression 264 | wildcard-expression → '_' 265 | macro-expansion-expression → '#' identifier generic-argument-clause? function-call-argument-clause? trailing-closures? 266 | key-path-expression → '\' type? '.' key-path-components 267 | key-path-components → key-path-component | key-path-component '.' key-path-components 268 | key-path-component → identifier key-path-postfixes? | key-path-postfixes 269 | key-path-postfixes → key-path-postfix key-path-postfixes? 270 | key-path-postfix → '?' | '!' | 'self' | '[' function-call-argument-list ']' 271 | selector-expression → '#selector' '(' expression ')' 272 | selector-expression → '#selector' '(' 'getter:' expression ')' 273 | selector-expression → '#selector' '(' 'setter:' expression ')' 274 | key-path-string-expression → '#keyPath' '(' expression ')' 275 | postfix-expression → primary-expression 276 | postfix-expression → postfix-expression postfix-operator 277 | postfix-expression → function-call-expression 278 | postfix-expression → initializer-expression 279 | postfix-expression → explicit-member-expression 280 | postfix-expression → postfix-self-expression 281 | postfix-expression → subscript-expression 282 | postfix-expression → forced-value-expression 283 | postfix-expression → optional-chaining-expression 284 | function-call-expression → postfix-expression function-call-argument-clause 285 | function-call-expression → postfix-expression function-call-argument-clause? trailing-closures 286 | function-call-argument-clause → '(' ')' | '(' function-call-argument-list ')' 287 | function-call-argument-list → function-call-argument | function-call-argument ',' function-call-argument-list 288 | function-call-argument → expression | identifier ':' expression 289 | function-call-argument → operator | identifier ':' operator 290 | trailing-closures → closure-expression labeled-trailing-closures? 291 | labeled-trailing-closures → labeled-trailing-closure labeled-trailing-closures? 292 | labeled-trailing-closure → identifier ':' closure-expression 293 | initializer-expression → postfix-expression '.' 'init' 294 | initializer-expression → postfix-expression '.' 'init' '(' argument-names ')' 295 | explicit-member-expression → postfix-expression '.' decimal-digits 296 | explicit-member-expression → postfix-expression '.' identifier generic-argument-clause? 297 | explicit-member-expression → postfix-expression '.' identifier '(' argument-names ')' 298 | explicit-member-expression → postfix-expression conditional-compilation-block 299 | argument-names → argument-name argument-names? 300 | argument-name → identifier ':' 301 | postfix-self-expression → postfix-expression '.' 'self' 302 | subscript-expression → postfix-expression '[' function-call-argument-list ']' 303 | forced-value-expression → postfix-expression '!' 304 | optional-chaining-expression → postfix-expression '?' 305 | statement → expression ';'? 306 | statement → declaration ';'? 307 | statement → loop-statement ';'? 308 | statement → branch-statement ';'? 309 | statement → labeled-statement ';'? 310 | statement → control-transfer-statement ';'? 311 | statement → defer-statement ';'? 312 | statement → do-statement ';'? 313 | statement → compiler-control-statement 314 | statements → statement statements? 315 | loop-statement → for-in-statement 316 | loop-statement → while-statement 317 | loop-statement → repeat-while-statement 318 | for-in-statement → 'for' 'case'? pattern 'in' expression where-clause? code-block 319 | while-statement → 'while' condition-list code-block 320 | condition-list → condition | condition ',' condition-list 321 | condition → expression | availability-condition | case-condition | optional-binding-condition 322 | case-condition → 'case' pattern initializer 323 | optional-binding-condition → 'let' pattern initializer? | 'var' pattern initializer? 324 | repeat-while-statement → 'repeat' code-block 'while' expression 325 | branch-statement → if-statement 326 | branch-statement → guard-statement 327 | branch-statement → switch-statement 328 | if-statement → 'if' condition-list code-block else-clause? 329 | else-clause → 'else' code-block | 'else' if-statement 330 | guard-statement → 'guard' condition-list 'else' code-block 331 | switch-statement → 'switch' expression '{' switch-cases? '}' 332 | switch-cases → switch-case switch-cases? 333 | switch-case → case-label statements 334 | switch-case → default-label statements 335 | switch-case → conditional-switch-case 336 | case-label → attributes? 'case' case-item-list ':' 337 | case-item-list → pattern where-clause? | pattern where-clause? ',' case-item-list 338 | default-label → attributes? 'default' ':' 339 | where-clause → 'where' where-expression 340 | where-expression → expression 341 | conditional-switch-case → switch-if-directive-clause switch-elseif-directive-clauses? switch-else-directive-clause? endif-directive 342 | switch-if-directive-clause → if-directive compilation-condition switch-cases? 343 | switch-elseif-directive-clauses → elseif-directive-clause switch-elseif-directive-clauses? 344 | switch-elseif-directive-clause → elseif-directive compilation-condition switch-cases? 345 | switch-else-directive-clause → else-directive switch-cases? 346 | labeled-statement → statement-label loop-statement 347 | labeled-statement → statement-label if-statement 348 | labeled-statement → statement-label switch-statement 349 | labeled-statement → statement-label do-statement 350 | statement-label → label-name ':' 351 | label-name → identifier 352 | control-transfer-statement → break-statement 353 | control-transfer-statement → continue-statement 354 | control-transfer-statement → fallthrough-statement 355 | control-transfer-statement → return-statement 356 | control-transfer-statement → throw-statement 357 | break-statement → 'break' label-name? 358 | continue-statement → 'continue' label-name? 359 | fallthrough-statement → 'fallthrough' 360 | return-statement → 'return' expression? 361 | throw-statement → 'throw' expression 362 | defer-statement → 'defer' code-block 363 | do-statement → 'do' throws-clause? code-block catch-clauses? 364 | catch-clauses → catch-clause catch-clauses? 365 | catch-clause → 'catch' catch-pattern-list? code-block 366 | catch-pattern-list → catch-pattern | catch-pattern ',' catch-pattern-list 367 | catch-pattern → pattern where-clause? 368 | compiler-control-statement → conditional-compilation-block 369 | compiler-control-statement → line-control-statement 370 | compiler-control-statement → diagnostic-statement 371 | conditional-compilation-block → if-directive-clause elseif-directive-clauses? else-directive-clause? endif-directive 372 | if-directive-clause → if-directive compilation-condition statements? 373 | elseif-directive-clauses → elseif-directive-clause elseif-directive-clauses? 374 | elseif-directive-clause → elseif-directive compilation-condition statements? 375 | else-directive-clause → else-directive statements? 376 | if-directive → '#if' 377 | elseif-directive → '#elseif' 378 | else-directive → '#else' 379 | endif-directive → '#endif' 380 | compilation-condition → platform-condition 381 | compilation-condition → identifier 382 | compilation-condition → boolean-literal 383 | compilation-condition → '(' compilation-condition ')' 384 | compilation-condition → '!' compilation-condition 385 | compilation-condition → compilation-condition '&&' compilation-condition 386 | compilation-condition → compilation-condition '||' compilation-condition 387 | platform-condition → 'os' '(' operating-system ')' 388 | platform-condition → 'arch' '(' architecture ')' 389 | platform-condition → 'swift' '(' '>=' swift-version ')' | 'swift' '(' '<' swift-version ')' 390 | platform-condition → 'compiler' '(' '>=' swift-version ')' | 'compiler' '(' '<' swift-version ')' 391 | platform-condition → 'canImport' '(' import-path ')' 392 | platform-condition → 'targetEnvironment' '(' environment ')' 393 | operating-system → 'macOS' | 'iOS' | 'watchOS' | 'tvOS' | 'visionOS' | 'Linux' | 'Windows' 394 | architecture → 'i386' | 'x86_64' | 'arm' | 'arm64' 395 | swift-version → decimal-digits swift-version-continuation? 396 | swift-version-continuation → '.' decimal-digits swift-version-continuation? 397 | environment → 'simulator' | 'macCatalyst' 398 | line-control-statement → '#sourceLocation' '(' 'file:' file-path ',' 'line:' line-number ')' 399 | line-control-statement → '#sourceLocation' '(' ')' 400 | line-number → A decimal integer greater than zero 401 | file-path → static-string-literal 402 | availability-condition → '#available' '(' availability-arguments ')' 403 | availability-condition → '#unavailable' '(' availability-arguments ')' 404 | availability-arguments → availability-argument | availability-argument ',' availability-arguments 405 | availability-argument → platform-name platform-version 406 | availability-argument → '*' 407 | platform-name → 'iOS' | 'iOSApplicationExtension' 408 | platform-name → 'macOS' | 'macOSApplicationExtension' 409 | platform-name → 'macCatalyst' | 'macCatalystApplicationExtension' 410 | platform-name → 'watchOS' | 'watchOSApplicationExtension' 411 | platform-name → 'tvOS' | 'tvOSApplicationExtension' 412 | platform-name → 'visionOS' | 'visionOSApplicationExtension' 413 | platform-version → decimal-digits 414 | platform-version → decimal-digits '.' decimal-digits 415 | platform-version → decimal-digits '.' decimal-digits '.' decimal-digits 416 | declaration → import-declaration 417 | declaration → constant-declaration 418 | declaration → variable-declaration 419 | declaration → typealias-declaration 420 | declaration → function-declaration 421 | declaration → enum-declaration 422 | declaration → struct-declaration 423 | declaration → class-declaration 424 | declaration → actor-declaration 425 | declaration → protocol-declaration 426 | declaration → initializer-declaration 427 | declaration → deinitializer-declaration 428 | declaration → extension-declaration 429 | declaration → subscript-declaration 430 | declaration → operator-declaration 431 | declaration → precedence-group-declaration 432 | top-level-declaration → statements? 433 | code-block → '{' statements? '}' 434 | import-declaration → attributes? 'import' import-kind? import-path 435 | import-kind → 'typealias' | 'struct' | 'class' | 'enum' | 'protocol' | 'let' | 'var' | 'func' 436 | import-path → identifier | identifier '.' import-path 437 | constant-declaration → attributes? declaration-modifiers? 'let' pattern-initializer-list 438 | pattern-initializer-list → pattern-initializer | pattern-initializer ',' pattern-initializer-list 439 | pattern-initializer → pattern initializer? 440 | initializer → '=' expression 441 | variable-declaration → variable-declaration-head pattern-initializer-list 442 | variable-declaration → variable-declaration-head variable-name type-annotation code-block 443 | variable-declaration → variable-declaration-head variable-name type-annotation getter-setter-block 444 | variable-declaration → variable-declaration-head variable-name type-annotation getter-setter-keyword-block 445 | variable-declaration → variable-declaration-head variable-name initializer willSet-didSet-block 446 | variable-declaration → variable-declaration-head variable-name type-annotation initializer? willSet-didSet-block 447 | variable-declaration-head → attributes? declaration-modifiers? 'var' 448 | variable-name → identifier 449 | getter-setter-block → code-block 450 | getter-setter-block → '{' getter-clause setter-clause? '}' 451 | getter-setter-block → '{' setter-clause getter-clause '}' 452 | getter-clause → attributes? mutation-modifier? 'get' code-block 453 | setter-clause → attributes? mutation-modifier? 'set' setter-name? code-block 454 | setter-name → '(' identifier ')' 455 | getter-setter-keyword-block → '{' getter-keyword-clause setter-keyword-clause? '}' 456 | getter-setter-keyword-block → '{' setter-keyword-clause getter-keyword-clause '}' 457 | getter-keyword-clause → attributes? mutation-modifier? 'get' 458 | setter-keyword-clause → attributes? mutation-modifier? 'set' 459 | willSet-didSet-block → '{' willSet-clause didSet-clause? '}' 460 | willSet-didSet-block → '{' didSet-clause willSet-clause? '}' 461 | willSet-clause → attributes? 'willSet' setter-name? code-block 462 | didSet-clause → attributes? 'didSet' setter-name? code-block 463 | typealias-declaration → attributes? access-level-modifier? 'typealias' typealias-name generic-parameter-clause? typealias-assignment 464 | typealias-name → identifier 465 | typealias-assignment → '=' type 466 | function-declaration → function-head function-name generic-parameter-clause? function-signature generic-where-clause? function-body? 467 | function-head → attributes? declaration-modifiers? 'func' 468 | function-name → identifier | operator 469 | function-signature → parameter-clause 'async'? throws-clause? function-result? 470 | function-signature → parameter-clause 'async'? 'rethrows' function-result? 471 | function-result → '->' attributes? type 472 | function-body → code-block 473 | parameter-clause → '(' ')' | '(' parameter-list ')' 474 | parameter-list → parameter | parameter ',' parameter-list 475 | parameter → external-parameter-name? local-parameter-name parameter-type-annotation default-argument-clause? 476 | parameter → external-parameter-name? local-parameter-name parameter-type-annotation 477 | parameter → external-parameter-name? local-parameter-name parameter-type-annotation '...' 478 | external-parameter-name → identifier 479 | local-parameter-name → identifier 480 | parameter-type-annotation → ':' attributes? parameter-modifier? type 481 | parameter-modifier → 'inout' | 'borrowing' | 'consuming' 482 | default-argument-clause → '=' expression 483 | enum-declaration → attributes? access-level-modifier? union-style-enum 484 | enum-declaration → attributes? access-level-modifier? raw-value-style-enum 485 | union-style-enum → 'indirect'? 'enum' enum-name generic-parameter-clause? type-inheritance-clause? generic-where-clause? '{' union-style-enum-members? '}' 486 | union-style-enum-members → union-style-enum-member union-style-enum-members? 487 | union-style-enum-member → declaration | union-style-enum-case-clause | compiler-control-statement 488 | union-style-enum-case-clause → attributes? 'indirect'? 'case' union-style-enum-case-list 489 | union-style-enum-case-list → union-style-enum-case | union-style-enum-case ',' union-style-enum-case-list 490 | union-style-enum-case → enum-case-name tuple-type? 491 | enum-name → identifier 492 | enum-case-name → identifier 493 | raw-value-style-enum → 'enum' enum-name generic-parameter-clause? type-inheritance-clause generic-where-clause? '{' raw-value-style-enum-members '}' 494 | raw-value-style-enum-members → raw-value-style-enum-member raw-value-style-enum-members? 495 | raw-value-style-enum-member → declaration | raw-value-style-enum-case-clause | compiler-control-statement 496 | raw-value-style-enum-case-clause → attributes? 'case' raw-value-style-enum-case-list 497 | raw-value-style-enum-case-list → raw-value-style-enum-case | raw-value-style-enum-case ',' raw-value-style-enum-case-list 498 | raw-value-style-enum-case → enum-case-name raw-value-assignment? 499 | raw-value-assignment → '=' raw-value-literal 500 | raw-value-literal → numeric-literal | static-string-literal | boolean-literal 501 | struct-declaration → attributes? access-level-modifier? 'struct' struct-name generic-parameter-clause? type-inheritance-clause? generic-where-clause? struct-body 502 | struct-name → identifier 503 | struct-body → '{' struct-members? '}' 504 | struct-members → struct-member struct-members? 505 | struct-member → declaration | compiler-control-statement 506 | class-declaration → attributes? access-level-modifier? 'final'? 'class' class-name generic-parameter-clause? type-inheritance-clause? generic-where-clause? class-body 507 | class-declaration → attributes? 'final' access-level-modifier? 'class' class-name generic-parameter-clause? type-inheritance-clause? generic-where-clause? class-body 508 | class-name → identifier 509 | class-body → '{' class-members? '}' 510 | class-members → class-member class-members? 511 | class-member → declaration | compiler-control-statement 512 | actor-declaration → attributes? access-level-modifier? 'actor' actor-name generic-parameter-clause? type-inheritance-clause? generic-where-clause? actor-body 513 | actor-name → identifier 514 | actor-body → '{' actor-members? '}' 515 | actor-members → actor-member actor-members? 516 | actor-member → declaration | compiler-control-statement 517 | protocol-declaration → attributes? access-level-modifier? 'protocol' protocol-name type-inheritance-clause? generic-where-clause? protocol-body 518 | protocol-name → identifier 519 | protocol-body → '{' protocol-members? '}' 520 | protocol-members → protocol-member protocol-members? 521 | protocol-member → protocol-member-declaration | compiler-control-statement 522 | protocol-member-declaration → protocol-property-declaration 523 | protocol-member-declaration → protocol-method-declaration 524 | protocol-member-declaration → protocol-initializer-declaration 525 | protocol-member-declaration → protocol-subscript-declaration 526 | protocol-member-declaration → protocol-associated-type-declaration 527 | protocol-member-declaration → typealias-declaration 528 | protocol-property-declaration → variable-declaration-head variable-name type-annotation getter-setter-keyword-block 529 | protocol-method-declaration → function-head function-name generic-parameter-clause? function-signature generic-where-clause? 530 | protocol-initializer-declaration → initializer-head generic-parameter-clause? parameter-clause throws-clause? generic-where-clause? 531 | protocol-initializer-declaration → initializer-head generic-parameter-clause? parameter-clause 'rethrows' generic-where-clause? 532 | protocol-subscript-declaration → subscript-head subscript-result generic-where-clause? getter-setter-keyword-block 533 | protocol-associated-type-declaration → attributes? access-level-modifier? 'associatedtype' typealias-name type-inheritance-clause? typealias-assignment? generic-where-clause? 534 | initializer-declaration → initializer-head generic-parameter-clause? parameter-clause 'async'? throws-clause? generic-where-clause? initializer-body 535 | initializer-declaration → initializer-head generic-parameter-clause? parameter-clause 'async'? 'rethrows' generic-where-clause? initializer-body 536 | initializer-head → attributes? declaration-modifiers? 'init' 537 | initializer-head → attributes? declaration-modifiers? 'init' '?' 538 | initializer-head → attributes? declaration-modifiers? 'init' '!' 539 | initializer-body → code-block 540 | deinitializer-declaration → attributes? 'deinit' code-block 541 | extension-declaration → attributes? access-level-modifier? 'extension' type-identifier type-inheritance-clause? generic-where-clause? extension-body 542 | extension-body → '{' extension-members? '}' 543 | extension-members → extension-member extension-members? 544 | extension-member → declaration | compiler-control-statement 545 | subscript-declaration → subscript-head subscript-result generic-where-clause? code-block 546 | subscript-declaration → subscript-head subscript-result generic-where-clause? getter-setter-block 547 | subscript-declaration → subscript-head subscript-result generic-where-clause? getter-setter-keyword-block 548 | subscript-head → attributes? declaration-modifiers? 'subscript' generic-parameter-clause? parameter-clause 549 | subscript-result → '->' attributes? type 550 | macro-declaration → macro-head identifier generic-parameter-clause? macro-signature macro-definition? generic-where-clause 551 | macro-head → attributes? declaration-modifiers? 'macro' 552 | macro-signature → parameter-clause macro-function-signature-result? 553 | macro-function-signature-result → '->' type 554 | macro-definition → '=' expression 555 | operator-declaration → prefix-operator-declaration | postfix-operator-declaration | infix-operator-declaration 556 | prefix-operator-declaration → 'prefix' 'operator' operator 557 | postfix-operator-declaration → 'postfix' 'operator' operator 558 | infix-operator-declaration → 'infix' 'operator' operator infix-operator-group? 559 | infix-operator-group → ':' precedence-group-name 560 | precedence-group-declaration → 'precedencegroup' precedence-group-name '{' precedence-group-attributes? '}' 561 | precedence-group-attributes → precedence-group-attribute precedence-group-attributes? 562 | precedence-group-attribute → precedence-group-relation 563 | precedence-group-attribute → precedence-group-assignment 564 | precedence-group-attribute → precedence-group-associativity 565 | precedence-group-relation → 'higherThan' ':' precedence-group-names 566 | precedence-group-relation → 'lowerThan' ':' precedence-group-names 567 | precedence-group-assignment → 'assignment' ':' boolean-literal 568 | precedence-group-associativity → 'associativity' ':' 'left' 569 | precedence-group-associativity → 'associativity' ':' 'right' 570 | precedence-group-associativity → 'associativity' ':' 'none' 571 | precedence-group-names → precedence-group-name | precedence-group-name ',' precedence-group-names 572 | precedence-group-name → identifier 573 | declaration-modifier → 'class' | 'convenience' | 'dynamic' | 'final' | 'infix' | 'lazy' | 'optional' | 'override' | 'postfix' | 'prefix' | 'required' | 'static' | 'unowned' | 'unowned' '(' 'safe' ')' | 'unowned' '(' 'unsafe' ')' | 'weak' 574 | declaration-modifier → access-level-modifier 575 | declaration-modifier → mutation-modifier 576 | declaration-modifier → actor-isolation-modifier 577 | declaration-modifiers → declaration-modifier declaration-modifiers? 578 | access-level-modifier → 'private' | 'private' '(' 'set' ')' 579 | access-level-modifier → 'fileprivate' | 'fileprivate' '(' 'set' ')' 580 | access-level-modifier → 'internal' | 'internal' '(' 'set' ')' 581 | access-level-modifier → 'package' | 'package' '(' 'set' ')' 582 | access-level-modifier → 'public' | 'public' '(' 'set' ')' 583 | access-level-modifier → 'open' | 'open' '(' 'set' ')' 584 | mutation-modifier → 'mutating' | 'nonmutating' 585 | actor-isolation-modifier → 'nonisolated' 586 | attribute → '@' attribute-name attribute-argument-clause? 587 | attribute-name → identifier 588 | attribute-argument-clause → '(' balanced-tokens? ')' 589 | attributes → attribute attributes? 590 | balanced-tokens → balanced-token balanced-tokens? 591 | balanced-token → '(' balanced-tokens? ')' 592 | balanced-token → '[' balanced-tokens? ']' 593 | balanced-token → '{' balanced-tokens? '}' 594 | balanced-token → Any identifier, keyword, literal, or operator 595 | balanced-token → Any punctuation except '(', ')', '[', ']', '{', or '}' 596 | pattern → wildcard-pattern type-annotation? 597 | pattern → identifier-pattern type-annotation? 598 | pattern → value-binding-pattern 599 | pattern → tuple-pattern type-annotation? 600 | pattern → enum-case-pattern 601 | pattern → optional-pattern 602 | pattern → type-casting-pattern 603 | pattern → expression-pattern 604 | wildcard-pattern → '_' 605 | identifier-pattern → identifier 606 | value-binding-pattern → 'var' pattern | 'let' pattern 607 | tuple-pattern → '(' tuple-pattern-element-list? ')' 608 | tuple-pattern-element-list → tuple-pattern-element | tuple-pattern-element ',' tuple-pattern-element-list 609 | tuple-pattern-element → pattern | identifier ':' pattern 610 | enum-case-pattern → type-identifier? '.' enum-case-name tuple-pattern? 611 | optional-pattern → identifier-pattern '?' 612 | type-casting-pattern → is-pattern | as-pattern 613 | is-pattern → 'is' type 614 | as-pattern → pattern 'as' type 615 | expression-pattern → expression 616 | generic-parameter-clause → '<' generic-parameter-list '>' 617 | generic-parameter-list → generic-parameter | generic-parameter ',' generic-parameter-list 618 | generic-parameter → type-name 619 | generic-parameter → type-name ':' type-identifier 620 | generic-parameter → type-name ':' protocol-composition-type 621 | generic-where-clause → 'where' requirement-list 622 | requirement-list → requirement | requirement ',' requirement-list 623 | requirement → conformance-requirement | same-type-requirement 624 | conformance-requirement → type-identifier ':' type-identifier 625 | conformance-requirement → type-identifier ':' protocol-composition-type 626 | same-type-requirement → type-identifier '==' type 627 | generic-argument-clause → '<' generic-argument-list '>' 628 | generic-argument-list → generic-argument | generic-argument ',' generic-argument-list 629 | generic-argument → type 630 | --------------------------------------------------------------------------------