Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
85acd3f
Create grammar pipeline types
GeorgeElsham Mar 26, 2026
19d8598
Create GrammarPipelineLiteral
GeorgeElsham Mar 28, 2026
77e3ae8
Use dictionary for heads
GeorgeElsham Mar 28, 2026
85900d2
Refactor pipeline stuff
GeorgeElsham Mar 28, 2026
9af4dc6
Rename GrammarPipelineHeads to GrammarPipelineSource
GeorgeElsham Mar 28, 2026
7b8a3fe
Make fns private
GeorgeElsham Mar 28, 2026
847b5d0
Create empty
GeorgeElsham Mar 28, 2026
3ebaeea
Fix always setting empty body
GeorgeElsham Mar 29, 2026
6923540
Make parts init accessible
GeorgeElsham Mar 29, 2026
a3bc22b
Handle first part being empty
GeorgeElsham Mar 29, 2026
7f14127
Combine all empty bodies
GeorgeElsham Mar 29, 2026
482719e
Create Consume type
GeorgeElsham Mar 29, 2026
faec838
Separate wildcard
GeorgeElsham Mar 29, 2026
0f764db
Consume wildcard
GeorgeElsham Mar 29, 2026
613537b
Fix incorrect grammar pipeline expansion
GeorgeElsham Mar 29, 2026
434cf3f
Cache parts init
GeorgeElsham Mar 29, 2026
0951669
Fix logic bug when checking if can accept nothing
GeorgeElsham Mar 29, 2026
b69f130
Do early check for can accept nothing
GeorgeElsham Mar 29, 2026
f37ef82
Fix adding rest to pipeline
GeorgeElsham Mar 31, 2026
5c12241
Use greediest
GeorgeElsham Mar 31, 2026
78b10d8
Change StreamResult enum
GeorgeElsham Mar 31, 2026
ba5391a
Use nextIf fn directly
GeorgeElsham Mar 31, 2026
b1724f7
Use new pipeline system
GeorgeElsham Mar 31, 2026
09c6ee3
Remove passing context to consumeLiteral fn
GeorgeElsham Apr 1, 2026
ca86024
Try consume the wildcard or empty if a head fails
GeorgeElsham Apr 1, 2026
7a8fad7
Use subsequence for parts init
GeorgeElsham Apr 1, 2026
cc1e3cb
Fix consuming wildcard
GeorgeElsham Apr 1, 2026
7e78fe6
Do not attempt something we can't do better at
GeorgeElsham Apr 1, 2026
530f5cf
Comment out consuming ir
GeorgeElsham Apr 1, 2026
d7f3220
Temporary file used for debugging
GeorgeElsham Apr 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions Sources/Bootstrap/Swift/Literal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Output> {
stream.next()
}
}

enum LineFeed: GrammarLiteral {
Expand Down
136 changes: 136 additions & 0 deletions Sources/Grammar/Consume.swift
Original file line number Diff line number Diff line change
@@ -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)
}
8 changes: 8 additions & 0 deletions Sources/Grammar/Context.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//
// Context.swift
// atom
//
// Created by George Elsham on 31/03/2026.
//

struct GrammarContext {}
171 changes: 171 additions & 0 deletions Sources/Grammar/Pipeline.swift
Original file line number Diff line number Diff line change
@@ -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")
}
}
Loading