Skip to content

Commit 021e694

Browse files
matttjohnmai-dev
andcommitted
Add Jinja module to package
Co-authored-by: John Mai <[email protected]>
1 parent f6ca318 commit 021e694

24 files changed

+11457
-27
lines changed

Package.swift

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ let package = Package(
1313
platforms: [.iOS(.v16), .macOS(.v13)],
1414
products: [
1515
.library(name: "Transformers", targets: ["Tokenizers", "Generation", "Models"]),
16+
.library(name: "Jinja", targets: ["Jinja"]),
1617
.executable(name: "transformers", targets: ["TransformersCLI"]),
1718
.executable(name: "hub-cli", targets: ["HubCLI"]),
1819
],
1920
dependencies: [
2021
.package(url: "https://github.com/apple/swift-argument-parser.git", .upToNextMinor(from: "1.4.0")),
21-
.package(url: "https://github.com/johnmai-dev/Jinja", .upToNextMinor(from: "1.3.0")),
22+
.package(url: "https://github.com/apple/swift-collections.git", from: "1.0.0"),
2223
],
2324
targets: [
2425
.executableTarget(
@@ -29,13 +30,17 @@ let package = Package(
2930
]
3031
),
3132
.executableTarget(name: "HubCLI", dependencies: ["Hub", .product(name: "ArgumentParser", package: "swift-argument-parser")]),
32-
.target(name: "Hub", resources: [.process("FallbackConfigs")], swiftSettings: swiftSettings),
33-
.target(name: "Tokenizers", dependencies: ["Hub", .product(name: "Jinja", package: "Jinja")]),
33+
.target(name: "Hub", dependencies: ["Jinja"], resources: [.process("FallbackConfigs")], swiftSettings: swiftSettings),
34+
.target(name: "Jinja", dependencies: [
35+
.product(name: "OrderedCollections", package: "swift-collections"),
36+
]),
37+
.target(name: "Tokenizers", dependencies: ["Hub", "Jinja"]),
3438
.target(name: "TensorUtils"),
3539
.target(name: "Generation", dependencies: ["Tokenizers", "TensorUtils"]),
3640
.target(name: "Models", dependencies: ["Tokenizers", "Generation", "TensorUtils"]),
3741
.testTarget(name: "TokenizersTests", dependencies: ["Tokenizers", "Models", "Hub"], resources: [.process("Resources"), .process("Vocabs")]),
38-
.testTarget(name: "HubTests", dependencies: ["Hub", .product(name: "Jinja", package: "Jinja")], swiftSettings: swiftSettings),
42+
.testTarget(name: "HubTests", dependencies: ["Hub", "Jinja"], swiftSettings: swiftSettings),
3943
.testTarget(name: "TensorUtilsTests", dependencies: ["TensorUtils", "Models", "Hub"], resources: [.process("Resources")]),
44+
.testTarget(name: "JinjaTests", dependencies: ["Jinja"]),
4045
]
4146
)

Sources/Hub/Config.swift

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// Created by Piotr Kowalczuk on 06.03.25.
66

77
import Foundation
8+
import Jinja
89

910
// MARK: - Configuration files with dynamic lookup
1011

@@ -413,28 +414,32 @@ public struct Config: Hashable, Sendable,
413414
self.dictionary(or: or)
414415
}
415416

416-
public func toJinjaCompatible() -> Any? {
417+
public func toJinjaCompatible() -> Jinja.Value {
417418
switch self.value {
418419
case let .array(val):
419-
return val.map { $0.toJinjaCompatible() }
420+
let converted: [Jinja.Value] = val.map { $0.toJinjaCompatible() }
421+
return Jinja.Value.array(converted)
420422
case let .dictionary(val):
421-
var result: [String: Any?] = [:]
423+
var result: OrderedDictionary<String, Jinja.Value> = [:]
422424
for (key, config) in val {
423425
result[key.string] = config.toJinjaCompatible()
424426
}
425-
return result
427+
return Jinja.Value.object(result)
426428
case let .boolean(val):
427-
return val
429+
return Jinja.Value.boolean(val)
428430
case let .floating(val):
429-
return val
431+
return Jinja.Value.double(Double(val))
430432
case let .integer(val):
431-
return val
433+
return Jinja.Value.int(val)
432434
case let .string(val):
433-
return val.string
435+
return Jinja.Value.string(val.string)
434436
case let .token(val):
435-
return [String(val.0): val.1.string] as [String: String]
437+
// Represent token as an object mapping id -> token string
438+
var obj: OrderedDictionary<String, Jinja.Value> = [:]
439+
obj[String(val.0)] = Jinja.Value.string(val.1.string)
440+
return Jinja.Value.object(obj)
436441
case .null:
437-
return nil
442+
return Jinja.Value.null
438443
}
439444
}
440445

Sources/Jinja/AST.swift

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
import Foundation
2+
import OrderedCollections
3+
4+
/// A node in the abstract syntax tree representing template content.
5+
public indirect enum Node: Hashable, Codable, Sendable {
6+
/// Plain text content to be output directly.
7+
case text(String)
8+
9+
/// Expression to be evaluated and output.
10+
case expression(Expression)
11+
12+
/// Control flow statement to be executed.
13+
case statement(Statement)
14+
15+
/// Comment content to be ignored during execution.
16+
case comment(String)
17+
}
18+
19+
/// An expression that can be evaluated to produce a value.
20+
public indirect enum Expression: Hashable, Codable, Sendable {
21+
/// String literal value.
22+
case string(String)
23+
24+
/// Floating-point number literal.
25+
case number(Double)
26+
27+
/// Integer literal value.
28+
case integer(Int)
29+
30+
/// Boolean literal value.
31+
case boolean(Bool)
32+
33+
/// Null literal value.
34+
case null
35+
36+
/// Array literal with ordered elements.
37+
case array([Expression])
38+
39+
/// Tuple literal with ordered elements.
40+
case tuple([Expression])
41+
42+
/// Object literal with key-value pairs.
43+
case object(OrderedDictionary<String, Expression>)
44+
45+
/// Variable or function identifier reference.
46+
case identifier(String)
47+
48+
/// Unary operators for expressions.
49+
public enum UnaryOp: String, Hashable, CaseIterable, Codable, Sendable {
50+
/// Logical negation operator.
51+
case not = "not"
52+
/// Numeric negation operator.
53+
case minus = "-"
54+
/// Numeric identity operator.
55+
case plus = "+"
56+
/// Unpacking operator.
57+
case splat = "*"
58+
}
59+
/// Unary operation with operator and operand.
60+
case unary(UnaryOp, Expression)
61+
62+
/// Binary operators for expressions.
63+
public enum BinaryOp: String, Hashable, CaseIterable, Codable, Sendable {
64+
// MARK: Arithmetic Operators
65+
66+
/// Addition operator (`+`)
67+
case add = "+"
68+
69+
/// Subtraction operator (`-`)
70+
case subtract = "-"
71+
72+
/// Multiplication operator (`*`)
73+
case multiply = "*"
74+
75+
/// Division operator (`/`)
76+
case divide = "/"
77+
78+
/// Floor division operator (`//`)
79+
case floorDivide = "//"
80+
81+
/// Exponentiation operator (`**`)
82+
case power = "**"
83+
84+
/// Modulo operator (`%`)
85+
case modulo = "%"
86+
87+
// MARK: String Operators
88+
89+
/// String concatenation operator (`~`)
90+
case concat = "~"
91+
92+
// MARK: Equality Comparison Operators
93+
94+
/// Equality operator (`==`)
95+
case equal = "=="
96+
97+
/// Inequality operator (`!=`)
98+
case notEqual = "!="
99+
100+
// MARK: Relational Comparison Operators
101+
102+
/// Less than operator (`<`)
103+
case less = "<"
104+
105+
/// Less than or equal to operator (`<=`)
106+
case lessEqual = "<="
107+
108+
/// Greater than operator (`>`)
109+
case greater = ">"
110+
111+
/// Greater than or equal to operator (`>=`)
112+
case greaterEqual = ">="
113+
114+
// MARK: Logical Operators
115+
116+
/// Logical AND operator (`and`)
117+
case and = "and"
118+
119+
/// Logical OR operator (`or`)
120+
case or = "or"
121+
122+
// MARK: Membership Test Operators
123+
124+
/// Membership test operator (`in`)
125+
case `in` = "in"
126+
127+
/// Negated membership test operator (`not in`)
128+
case notIn = "not in"
129+
}
130+
/// Binary operation with operator and operands.
131+
case binary(BinaryOp, Expression, Expression)
132+
133+
/// Ternary conditional expression (`value if test else alternate`)
134+
case ternary(Expression, test: Expression, alternate: Expression?)
135+
136+
/// Function call with arguments and keyword arguments.
137+
case call(Expression, [Expression], [String: Expression])
138+
139+
/// Member access (object.property or object[key]).
140+
case member(Expression, Expression, computed: Bool)
141+
142+
/// Array or string slicing operation (`foo[1:2:3]`)
143+
case slice(Expression, start: Expression?, stop: Expression?, step: Expression?)
144+
145+
/// Filter application to transform a value (`foo | filter(2)`)
146+
case filter(Expression, String, [Expression], [String: Expression])
147+
148+
/// `is` test (`foo is divisibleby(2)`)
149+
case test(Expression, String, [Expression], negated: Bool)
150+
}
151+
152+
/// A control flow statement that affects template execution.
153+
public enum Statement: Hashable, Codable, Sendable {
154+
/// Block of nodes to execute sequentially.
155+
case program([Node])
156+
157+
/// Variable assignment statement.
158+
case set(target: Expression, value: Expression?, body: [Node])
159+
160+
/// Conditional statement with test, body, and optional alternate.
161+
case `if`(Expression, [Node], [Node])
162+
163+
/// Loop variable specification for for-loops.
164+
public enum LoopVar: Hashable, Codable, Sendable {
165+
/// Single loop variable.
166+
case single(String)
167+
/// Multiple loop variables for unpacking.
168+
case tuple([String])
169+
}
170+
/// Loop statement with variable, iterable, body, else block, and optional condition.
171+
case `for`(LoopVar, Expression, [Node], [Node], test: Expression?)
172+
173+
/// Macro definition with name, parameters, default values, and body.
174+
case macro(String, [String], OrderedDictionary<String, Expression>, [Node])
175+
176+
/// Exits a loop immediately.
177+
case `break`
178+
179+
/// Skips the current iteration of a loop.
180+
case `continue`
181+
182+
/// Calls a macro with a body.
183+
case call(callable: Expression, callerArgs: [Expression]?, body: [Node])
184+
185+
/// Applies a filter to a block of content.
186+
case filter(filterExpr: Expression, body: [Node])
187+
}

Sources/Jinja/Error.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// Errors that can occur during Jinja template processing.
2+
public enum JinjaError: Error, Sendable {
3+
/// Error during tokenization of template source.
4+
case lexer(String)
5+
/// Error during parsing of tokens into AST.
6+
case parser(String)
7+
/// Error during template execution or evaluation.
8+
case runtime(String)
9+
/// Error due to invalid template syntax.
10+
case syntax(String)
11+
}

0 commit comments

Comments
 (0)