diff --git a/Sources/Bootstrap/Swift/Literal.swift b/Sources/Bootstrap/Swift/Literal.swift index a53b021..6e0141a 100644 --- a/Sources/Bootstrap/Swift/Literal.swift +++ b/Sources/Bootstrap/Swift/Literal.swift @@ -8,10 +8,6 @@ enum Literal { enum Wildcard: GrammarLiteral { static let literal: Character = "\u{0}" // character never read - - static func consume(stream: inout Stream, context: GrammarContext) -> StreamStateMatch { - stream.next() - } } enum LineFeed: GrammarLiteral { diff --git a/Sources/Grammar/Consume.swift b/Sources/Grammar/Consume.swift new file mode 100644 index 0000000..9fa1b61 --- /dev/null +++ b/Sources/Grammar/Consume.swift @@ -0,0 +1,136 @@ +// +// Consume.swift +// atom +// +// Created by George Elsham on 29/03/2026. +// + +enum Consume { + static func consumeMatch(match: any GrammarMatch.Type, stream: inout Stream, context: GrammarContext) -> StreamResult { + let source = GrammarPipelineSource(match: match) + return consumeSource(source: source, stream: &stream, context: context) + } + + static func consumeSource(source: GrammarPipelineSource, stream: inout Stream, context: GrammarContext) -> StreamResult { + guard !stream.isEnd() else { + return source.canAcceptNothing() ? .doConsume(RawStringIr(string: "")) : .dontConsume + } + + headsLoop: for (literal, head) in source.heads { + var s = stream + switch consumeLiteral(literal: literal.value, stream: &s) { + case .dontConsume: + continue + case .doConsume: + let res = consumeHead(head: head, stream: &s, context: context) + switch res { + case .dontConsume: + break headsLoop + case .doConsume, .error: + stream = s + return res + } + case let .error(diagnostic): + return .error(diagnostic) + } + } + if !source.wildcard.bodies.isEmpty { + var s = stream + switch consumeWildcard(stream: &s) { + case .dontConsume: + break + case .doConsume: + let res = consumeHead(head: source.wildcard, stream: &s, context: context) + switch res { + case .dontConsume: + break + case .doConsume, .error: + stream = s + return res + } + case let .error(diagnostic): + return .error(diagnostic) + } + } + return consumeHead(head: source.empty, stream: &stream, context: context) + } + + static func consumeHead(head: GrammarPipelineHead, stream: inout Stream, context: GrammarContext) -> StreamResult { + var hasSeenEmpty = false + var greediest: (stream: Stream, result: StreamResult)? + var successfuls = [GrammarPipelineBody]() + bodyLoop: for body in head.bodies { + for successful in successfuls { + if successful.rest.starts(with: body.rest, by: { $0 == $1 }) { + // Isn't doing any better, continue + continue bodyLoop + } + } + + guard let source = GrammarPipelineSource(parts: body.rest) else { + if stream.isEnd() { + // Shortcut because nothing else can be consumed anyways + return .doConsume(RawStringIr(string: "")) + } + hasSeenEmpty = true + continue + } + + var s = stream + let res = consumeSource(source: source, stream: &s, context: context) + switch res { + case .dontConsume: + continue + case .doConsume, .error: + successfuls.append(body) + if let g = greediest { + if s.isAheadOf(stream: g.stream) { + greediest = (stream: s, result: res) + } + } else { + greediest = (stream: s, result: res) + } + } + } + if let greediest { + stream = greediest.stream + return greediest.result + } + + return hasSeenEmpty ? .doConsume(RawStringIr(string: "")) : .dontConsume + } + + static func consumeLiteral(literal: any GrammarLiteral.Type, stream: inout Stream) -> StreamResult { + switch stream.nextIf(char: literal.literal) { + case .dontConsume: + return .dontConsume + case let .doConsume(ir): +// print("consume ir = \(ir)") + return .doConsume(ir) + case .end: + fatalError("Unreachable") + case let .error(diagnostic): + return .error(diagnostic) + } + } + + static func consumeWildcard(stream: inout Stream) -> StreamResult { + switch stream.next() { + case .dontConsume: + return .dontConsume + case let .doConsume(ir): +// print("consume wildcard ir = \(ir)") + return .doConsume(ir) + case .end: + fatalError("Unreachable") + case let .error(diagnostic): + return .error(diagnostic) + } + } +} + +enum StreamResult { + case dontConsume + case doConsume(RawStringIr) + case error(Diagnostic) +} diff --git a/Sources/Grammar/Context.swift b/Sources/Grammar/Context.swift new file mode 100644 index 0000000..dd29ecf --- /dev/null +++ b/Sources/Grammar/Context.swift @@ -0,0 +1,8 @@ +// +// Context.swift +// atom +// +// Created by George Elsham on 31/03/2026. +// + +struct GrammarContext {} diff --git a/Sources/Grammar/Pipeline.swift b/Sources/Grammar/Pipeline.swift new file mode 100644 index 0000000..36ddb35 --- /dev/null +++ b/Sources/Grammar/Pipeline.swift @@ -0,0 +1,171 @@ +// +// Pipeline.swift +// atom +// +// Created by George Elsham on 26/03/2026. +// + +struct GrammarPipelineSource { + var heads: [GrammarPipelineLiteral: GrammarPipelineHead] + var wildcard: GrammarPipelineHead + var empty: GrammarPipelineHead + + init(match: any GrammarMatch.Type) { + self.init(match: match, rest: []) + } + + init?(parts: [any Grammar.Type].SubSequence) { + let id = parts.map { ObjectIdentifier($0) } + if let source = partsCache[id] { + guard let source else { + return nil + } + self = source + } else { + let source = GrammarPipelineSource(uncachedParts: parts) + partsCache.updateValue(source, forKey: id) + guard let source else { + return nil + } + self = source + } + } + + private init() { + heads = [:] + wildcard = GrammarPipelineHead(bodies: []) + empty = GrammarPipelineHead(bodies: []) + } + + private init(literal: any GrammarLiteral.Type, rest: [any Grammar.Type].SubSequence) { + let literal = GrammarPipelineLiteral(value: literal) + let head = GrammarPipelineHead(bodies: [GrammarPipelineBody(rest: rest)]) + heads = [literal: head] + wildcard = GrammarPipelineHead(bodies: []) + empty = GrammarPipelineHead(bodies: []) + } + + private init(wildcardWithRest rest: [any Grammar.Type].SubSequence) { + heads = [:] + wildcard = GrammarPipelineHead(bodies: [GrammarPipelineBody(rest: rest)]) + empty = GrammarPipelineHead(bodies: []) + } + + private init(match: any GrammarMatch.Type, rest: [any Grammar.Type].SubSequence) { + let patterns = match.patterns.map { pattern in + pattern.anyParts() + } + self = GrammarPipelineSource(patterns: patterns, rest: rest) + } + + private init(patterns: [[any Grammar.Type]], rest: [any Grammar.Type].SubSequence) { + var source = GrammarPipelineSource() + for parts in patterns { + guard let pipelines = GrammarPipelineSource(parts: .SubSequence(parts)) else { + let body = GrammarPipelineBody(rest: rest) + source.empty.bodies.append(body) + continue + } + source.merge(with: pipelines, rest: rest) + } + self = source + } + + private init?(uncachedParts parts: [any Grammar.Type].SubSequence) { + guard let first = parts.first else { + return nil + } + let rest = parts.dropFirst() + + if let literal = first as? any GrammarLiteral.Type { + if literal == Literal.Wildcard.self { + self = GrammarPipelineSource(wildcardWithRest: rest) + } else { + self = GrammarPipelineSource(literal: literal, rest: rest) + } + } else if let match = first as? any GrammarMatch.Type { + var source = GrammarPipelineSource(match: match, rest: rest) + if source.canAcceptNothing(), let new = Self(parts: rest) { + source.merge(with: new, rest: []) + } + self = source + } else { + fatalError("Unreachable") + } + } + + private mutating func combine(with other: GrammarPipelineHead, literal: GrammarPipelineLiteral) { + if heads[literal] == nil { + heads[literal] = other + } else { + heads[literal]!.bodies.append(contentsOf: other.bodies) + } + } + + private mutating func merge(with other: GrammarPipelineSource, rest: [any Grammar.Type].SubSequence) { + for (literal, var head) in other.heads { + for index in head.bodies.indices { + head.bodies[index].rest.append(contentsOf: rest) + } + combine(with: head, literal: literal) + } + + var other = other + + for index in other.wildcard.bodies.indices { + other.wildcard.bodies[index].rest.append(contentsOf: rest) + } + wildcard.bodies.append(contentsOf: other.wildcard.bodies) + + for index in other.empty.bodies.indices { + other.empty.bodies[index].rest.append(contentsOf: rest) + } + empty.bodies.append(contentsOf: other.empty.bodies) + } + + func canAcceptNothing() -> Bool { + empty.bodies.contains { body in + body.rest.allSatisfy(isEmpty(grammar:)) + } + } +} + +struct GrammarPipelineHead { + var bodies: [GrammarPipelineBody] +} + +struct GrammarPipelineBody { + var rest: [any Grammar.Type].SubSequence +} + +struct GrammarPipelineLiteral: Hashable { + let value: any GrammarLiteral.Type + + func hash(into hasher: inout Hasher) { + hasher.combine(ObjectIdentifier(value)) + } + + static func == (lhs: GrammarPipelineLiteral, rhs: GrammarPipelineLiteral) -> Bool { + lhs.value == rhs.value + } +} + +nonisolated(unsafe) private var partsCache: [[ObjectIdentifier]: GrammarPipelineSource?] = [:] + +private func isEmpty(grammar: any Grammar.Type) -> Bool { + if grammar is any GrammarLiteral.Type { + return false + } else if let match = grammar as? any GrammarMatch.Type { + patternLoop: for pattern in match.patterns { + for part in pattern.anyParts() { + if !isEmpty(grammar: part) { + continue patternLoop + } + } + return true + } + return false + } else { + fatalError("Unreachable") + } +} diff --git a/Sources/Grammar/Structure.swift b/Sources/Grammar/Structure.swift index cfaa903..4f56d3c 100644 --- a/Sources/Grammar/Structure.swift +++ b/Sources/Grammar/Structure.swift @@ -7,73 +7,20 @@ protocol Grammar: Sendable { associatedtype Output: IR - - static func consume(stream: inout Stream, context: GrammarContext) -> StreamStateMatch } protocol GrammarLiteral: Grammar where Output == RawStringIr { static var literal: Character { get } } -extension GrammarLiteral { - static func consume(stream: inout Stream, context: GrammarContext) -> StreamStateMatch { - stream.nextIf(char: literal) - } -} - protocol GrammarMatch: Grammar { static var patterns: [any GrammarPatternProtocol] { get } } -extension GrammarMatch { - static func consume(stream: inout Stream, context: GrammarContext) -> StreamStateMatch { - var greediest: (stream: Stream, result: Result)? = nil - var context = context - context.setGrammarType(Self.self) - - for (index, pattern) in patterns.enumerated() { - var s = stream - context.setPatternIndex(index) - - let state = pattern.consume(stream: &s, context: context) - stream.updateFarthest(relativeTo: s) - switch state { - case .dontConsume: - continue - case let .doConsume(result): - if let g = greediest { - guard s.isGreedierThan(stream: g.stream, since: stream) else { - continue - } - } - greediest = (stream: s, result: result) - case .end: - continue - case let .error(diagnostic): - return .error(diagnostic) - } - } - - guard let greediest else { - // Nothing was able to be consumed - return .dontConsume - } - - switch greediest.result { - case let .success(ir): - stream = greediest.stream - return .doConsume(ir) - case let .failure(error): - let diagnostic = Diagnostic(start: stream.currentLocation(), end: greediest.stream.currentLocation(), error: error) - return .error(diagnostic) - } - } -} - protocol GrammarPatternProtocol: Sendable { associatedtype Output: IR - func consume(stream: inout Stream, context: GrammarContext) -> StreamStatePattern + func anyParts() -> [any Grammar.Type] } struct GrammarPattern: GrammarPatternProtocol { @@ -93,142 +40,11 @@ struct GrammarPattern: GrammarPatternProtocol { gen = { (_: repeat (each Part).Output) in NeverIr() } } - func consume(stream: inout Stream, context: GrammarContext) -> StreamStatePattern { - var context = context - var s = stream - var irPack: any IrPackProtocol = IrPack< >(irs: ()) - var index = 0 - + func anyParts() -> [any Grammar.Type] { + var anyParts = [any Grammar.Type]() for part in repeat each parts { - context.setPartIndex(index) - - switch context.addingToHistory() { - case .cycle: - return .dontConsume - case let .changed(newContext): - let saved = s - let state = part.consume(stream: &s, context: newContext) - stream.updateFarthest(relativeTo: s) - switch state { - case .dontConsume: - return .dontConsume - case let .doConsume(ir): - if s.isAheadOf(stream: saved) { - context.resetHistory() - } - irPack = irPack.appending(ir: ir) - case .end: - return .end - case let .error(diagnostic): - return .error(diagnostic) - } - } - - index += 1 - } - - stream = s - let irPackConcrete = irPack as! IrPack - let result = Result(catching: { () throws(GrammarError) in - try gen(repeat each irPackConcrete.irs) - }) - return .doConsume(result) - } -} - -fileprivate protocol IrPackProtocol { - func appending(ir: T) -> any IrPackProtocol -} - -fileprivate struct IrPack: IrPackProtocol { - let irs: (repeat each I) - - init(irs: (repeat each I)) { - self.irs = irs - } - - func appending(ir: T) -> any IrPackProtocol { - IrPack(irs: (repeat each irs, ir)) - } -} - -struct GrammarContext { - private var history: [HistorySnapshot] - private var grammarType: (any Grammar.Type)? - private var patternIndex: Int? - private var partIndex: Int? - - init() { - history = [] - grammarType = nil - patternIndex = nil - partIndex = nil - } - - fileprivate func addingToHistory() -> HistoryResult { - let snapshot = snapshot() - - for historySnapshot in history.reversed() { - if historySnapshot == snapshot { - return .cycle - } + anyParts.append(part) } - - var new = self - new.history.append(snapshot) - return .changed(new) - } - - fileprivate mutating func resetHistory() { - history.removeAll() + return anyParts } - - fileprivate mutating func setGrammarType(_ value: any Grammar.Type) { - grammarType = value - } - - fileprivate func isGrammarType(_ type: any Grammar.Type) -> Bool { - grammarType == type - } - - fileprivate mutating func setPatternIndex(_ value: Int) { - patternIndex = value - } - - fileprivate mutating func setPartIndex(_ value: Int) { - partIndex = value - } - - private func snapshot() -> HistorySnapshot { - HistorySnapshot(grammarType: grammarType!, patternIndex: patternIndex!, partIndex: partIndex!) - } -} - -fileprivate struct HistorySnapshot: Equatable { - private let grammarType: any Grammar.Type - private let patternIndex: Int - private let partIndex: Int - - init(grammarType: any Grammar.Type, patternIndex: Int, partIndex: Int) { - self.grammarType = grammarType - self.patternIndex = patternIndex - self.partIndex = partIndex - } - - static func == (lhs: HistorySnapshot, rhs: HistorySnapshot) -> Bool { - lhs.grammarType == rhs.grammarType && - lhs.patternIndex == rhs.patternIndex && - lhs.partIndex == rhs.partIndex - } -} - -extension HistorySnapshot: CustomDebugStringConvertible { - var debugDescription: String { - "\(String(describing: grammarType))[\(patternIndex), \(partIndex)]" - } -} - -fileprivate enum HistoryResult { - case cycle // Detected infinite cycle - case changed(GrammarContext) // Continue, with new context } diff --git a/Sources/Grammar/TEMP.swift b/Sources/Grammar/TEMP.swift new file mode 100644 index 0000000..5b6a7ef --- /dev/null +++ b/Sources/Grammar/TEMP.swift @@ -0,0 +1,77 @@ +// +// TEMP.swift +// atom +// +// Created by George Elsham on 01/04/2026. +// + +import Foundation + +extension GrammarPipelineSource: Encodable { + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + var nested1 = container.nestedContainer(keyedBy: CodingKeys.self, forKey: .heads) + for (literal, pipeline) in heads { + try nested1.encode(pipeline, forKey: .custom(String(describing: literal.value))) + } + try container.encode(wildcard, forKey: .wildcard) + try container.encode(empty, forKey: .empty) + } + + private enum CodingKeys: CodingKey { + case heads + case wildcard + case empty + case custom(String) + + init?(stringValue: String) { + nil + } + + init?(intValue: Int) { + nil + } + + var stringValue: String { + switch self { + case .heads: "heads" + case .wildcard: "wildcard" + case .empty: "empty" + case let .custom(name): name + } + } + + var intValue: Int? { + nil + } + } +} + +extension GrammarPipelineHead: Encodable { + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(bodies, forKey: .bodies) + } + + private enum CodingKeys: CodingKey { + case bodies + } +} + +extension GrammarPipelineBody: Encodable { + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(rest.map { String(describing: $0) }, forKey: .rest) + } + + private enum CodingKeys: CodingKey { + case rest + } +} + +func sourceStr(source: some Encodable) -> String { + let encoder = JSONEncoder() + encoder.outputFormatting = .sortedKeys + let data = try! encoder.encode(source) + return String(data: data, encoding: .utf8)! +} diff --git a/Sources/Source/Program.swift b/Sources/Source/Program.swift index 3360f6b..ce80a69 100644 --- a/Sources/Source/Program.swift +++ b/Sources/Source/Program.swift @@ -20,7 +20,7 @@ extension Program { private func intoLanguage(root: Root.Type) -> ConversionResult { var stream = Stream(raw: source.raw) - let result = root.consume(stream: &stream, context: GrammarContext()) + let result = Consume.consumeMatch(match: root, stream: &stream, context: GrammarContext()) func earlyEndResult() -> ConversionResult { let location = stream.farthestLocation() @@ -41,8 +41,6 @@ extension Program { return earlyEndResult() } return .program(C.fromIr(ir)) - case .end: - fatalError("Unreachable") case let .error(diagnostic): return .error(diagnostic) }