Skip to content

Commit

Permalink
Improved #export/#import Behavior (#58)
Browse files Browse the repository at this point in the history
* * Adds BodiedSyntax protocol to Syntaxes
  * Publish external or import dependencies for adherants
  * Provide `inlineRefs` methods to adherents to self-rebuild
* Provides corresponding `import` capabilities to ParameterDescription and Parameter
* Refactors Syntax.Conditional into a chained block struct instead of chained classes

* * New tests
* Import resolution inside Parameters
* Adjustments to test cases for internal behaviors

* * Minor internal changes
  • Loading branch information
tdotclare authored Jun 19, 2020
1 parent 192c92e commit 0cd6c01
Show file tree
Hide file tree
Showing 8 changed files with 638 additions and 231 deletions.
39 changes: 24 additions & 15 deletions Sources/LeafKit/LeafAST.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,30 @@ public struct LeafAST: Hashable {

// inline provided externals
while pos < ast.endIndex {
switch ast[pos] {
case .extend(let e):
let key = e.key
if let insert = externals[key] {
let inlined = e.extend(base: insert.ast)
ast.replaceSubrange(pos...pos, with: inlined)
} else {
unresolvedRefs.insert(key)
pos = ast.index(after: pos)
}
default:
var new: Syntax? = nil
unresolvedRefs.formUnion(ast[pos].inlineRefs(externals, &new))
if let new = new { ast[pos] = new }
pos = ast.index(after: pos)
// get desired externals for this Syntax - if none, continue
let wantedExts = ast[pos].externals()
if wantedExts.isEmpty {
pos = ast.index(after: pos)
continue
}
// see if we can provide any of them - if not, continue
let providedExts = externals.filter { wantedExts.contains($0.key) }
if providedExts.isEmpty {
unresolvedRefs.formUnion(wantedExts)
pos = ast.index(after: pos)
continue
}

// replace the original Syntax with the results of inlining, potentially 1...n
let replacementSyntax = ast[pos].inlineRefs(providedExts, [:])
ast.replaceSubrange(pos...pos, with: replacementSyntax)
// any returned new inlined syntaxes can't be further resolved at this point
// but we need to add their unresolvable references to the global set
var offset = replacementSyntax.startIndex
while offset < replacementSyntax.endIndex {
unresolvedRefs.formUnion(ast[pos].externals())
offset = replacementSyntax.index(after: offset)
pos = ast.index(after: pos)
}
}

Expand Down
72 changes: 69 additions & 3 deletions Sources/LeafKit/LeafLexer/LeafToken.swift
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,9 @@ public indirect enum ParameterDeclaration: CustomStringConvertible {

public var description: String {
switch self {
case .parameter(let p): return p.description
case .expression(let p): return name + "(\(p.describe()))"
case .tag(let t): return "tag(\(t.name): \(t.params.describe(",")))"
case .parameter(let p): return p.description
case .expression(_): return self.short
case .tag(let t): return "tag(\(t.name): \(t.params.describe(",")))"
}
}

Expand All @@ -202,6 +202,41 @@ public indirect enum ParameterDeclaration: CustomStringConvertible {
case .tag: return "tag"
}
}

internal func imports() -> Set<String> {
switch self {
case .parameter(_): return .init()
case .expression(let e): return e.imports()
case .tag(let t):
guard t.name == "import" else { return t.imports() }
guard let parameter = t.params.first,
case .parameter(let p) = parameter,
case .stringLiteral(let key) = p,
!key.isEmpty else { return .init() }
return .init(arrayLiteral: key)
}
}

internal func inlineImports(_ imports: [String : Syntax.Export]) -> ParameterDeclaration {
switch self {
case .parameter(_): return self
case .tag(let t):
guard t.name == "import" else {
return .tag(.init(name: t.name, params: t.params.inlineImports(imports)))
}
guard let parameter = t.params.first,
case .parameter(let p) = parameter,
case .stringLiteral(let key) = p,
let export = imports[key]?.body.first,
case .expression(let exp) = export,
exp.count == 1,
let e = exp.first else { return self }
return e
case .expression(let e):
guard !e.isEmpty else { return self }
return .expression(e.inlineImports(imports))
}
}
}

internal extension Array where Element == ParameterDeclaration {
Expand Down Expand Up @@ -300,6 +335,37 @@ internal extension Array where Element == ParameterDeclaration {
func describe(_ joinBy: String = " ") -> String {
return self.map {$0.short }.joined(separator: joinBy)
}

func imports() -> Set<String> {
var result = Set<String>()
self.forEach { result.formUnion($0.imports()) }
return result
}

func inlineImports(_ imports: [String : Syntax.Export]) -> [ParameterDeclaration] {
guard !self.isEmpty else { return self }
guard !imports.isEmpty else { return self }
return self.map { $0.inlineImports(imports) }
}

func atomicRaw() -> Syntax? {
// only atomic expressions can be converted
guard self.count < 2 else { return nil }
var buffer = ByteBufferAllocator().buffer(capacity: 0)
// empty expressions = empty raw
guard self.count == 1 else { return .raw(buffer) }
// only single value parameters can be converted
guard case .parameter(let p) = self[0] else { return nil }
switch p {
case .constant(let c): buffer.writeString(c.description)
case .keyword(let k): buffer.writeString(k.rawValue)
case .operator(let o): buffer.writeString(o.rawValue)
case .stringLiteral(let s): buffer.writeString(s)
// .tag, .variable not atomic
default: return nil
}
return .raw(buffer)
}
}
// MARK: --- END OF SECTION TO BE MOVED ---

Expand Down
36 changes: 24 additions & 12 deletions Sources/LeafKit/LeafParser/LeafParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -186,18 +186,30 @@ struct LeafParser {
}

if case .conditional(let new) = newSyntax {
switch new.condition {
// a new if, never attaches to a previous
case .if:
append(newSyntax)
case .elseif, .else:
// elseif and else ALWAYS attach
// ensure there is a leading conditional to
// attach to, prioritize waiting bodies, then check finished
guard let last = awaitingBody.last?.body.last ?? finished.last, case .conditional(let tail) = last else {
throw "unable to attach \(new.condition) to \(finished.last?.description ?? "<>")"
}
try tail.attach(new)
guard let conditional = new.chain.first else { throw "Malformed syntax block" }
switch conditional.0.naturalType {
// a new if, never attaches to a previous
case .if:
append(newSyntax)
case .elseif, .else:
let aW = awaitingBody.last?.body
let previousBlock: Syntax?
switch aW {
case .none: previousBlock = finished.last
case .some(let b): previousBlock = b.last
}
guard let existingConditional = previousBlock,
case .conditional(var tail) = existingConditional else {
throw "Can't attach \(conditional.0) to \(previousBlock?.description ?? "empty AST")"
}
try tail.attach(new)
switch aW {
case .none:
finished[finished.index(before: finished.endIndex)] = .conditional(tail)
case .some(_):
awaitingBody[awaitingBody.index(before: awaitingBody.endIndex)].body.removeLast()
awaitingBody[awaitingBody.index(before: awaitingBody.endIndex)].body.append(.conditional(tail))
}
}
} else {
append(newSyntax)
Expand Down
37 changes: 19 additions & 18 deletions Sources/LeafKit/LeafSerialize/LeafSerializer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,24 +61,13 @@ struct LeafSerializer {
}

mutating func serialize(_ conditional: Syntax.Conditional) throws {
let list: [ParameterDeclaration]
switch conditional.condition {
case .if(let l):
list = l
case .elseif(let l):
list = l
case .else:
try serialize(body: conditional.body)
return
}

let satisfied = try self.resolve(parameters: list).map {
$0.bool ?? !$0.isNull
}.reduce(true) { $0 && $1 }
if satisfied {
try serialize(body: conditional.body)
} else if let next = conditional.next {
try serialize(next)
evaluate:
for block in conditional.chain {
let evaluated = try self.resolveAtomic(parameters: block.condition.expression())
if evaluated.bool ?? !evaluated.isNull {
try serialize(body: block.body)
break evaluate
}
}
}

Expand Down Expand Up @@ -172,6 +161,18 @@ struct LeafSerializer {
)
return try resolver.resolve().map { $0.result }
}

// Directive resolver for a [ParameterDeclaration] where only one parameter is allowed that must resolve to a single value
private func resolveAtomic(parameters: [ParameterDeclaration]) throws -> LeafData {
guard parameters.count == 1 else {
if parameters.isEmpty {
throw LeafError(.unknownError("Parameter statement can't be empty"))
} else {
throw LeafError(.unknownError("Parameter statement must hold a single value"))
}
}
return try resolve(parameters: parameters).first ?? .null
}

func peek() -> Syntax? {
guard self.offset < self.ast.count else {
Expand Down
4 changes: 3 additions & 1 deletion Sources/LeafKit/LeafSerialize/ParameterResolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,9 @@ struct ParameterResolver {

// #if(lowercase(first(name == "admin")) == "welcome")
private func resolve(expression: [ParameterDeclaration]) throws -> LeafData {
if expression.count == 2 {
if expression.count == 1 {
return try resolve(expression[0]).result
} else if expression.count == 2 {
if let lho = expression[0].operator() {
let rhs = try resolve(expression[1]).result
return try resolve(op: lho, rhs: rhs)
Expand Down
Loading

0 comments on commit 0cd6c01

Please sign in to comment.