Skip to content

Commit 35b60a6

Browse files
matttjohnmai-dev
andcommitted
Add Jinja module to package
Co-authored-by: John Mai <maiqingqiang@gmail.com>
1 parent f6ca318 commit 35b60a6

24 files changed

+11508
-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: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
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
52+
/// Numeric negation operator.
53+
case minus = "-"
54+
/// Numeric identity operator.
55+
case plus = "+"
56+
/// Unpacking operator.
57+
case splat = "*"
58+
}
59+
60+
/// Unary operation with operator and operand.
61+
case unary(UnaryOp, Expression)
62+
63+
/// Binary operators for expressions.
64+
public enum BinaryOp: String, Hashable, CaseIterable, Codable, Sendable {
65+
// MARK: Arithmetic Operators
66+
67+
/// Addition operator (`+`)
68+
case add = "+"
69+
70+
/// Subtraction operator (`-`)
71+
case subtract = "-"
72+
73+
/// Multiplication operator (`*`)
74+
case multiply = "*"
75+
76+
/// Division operator (`/`)
77+
case divide = "/"
78+
79+
/// Floor division operator (`//`)
80+
case floorDivide = "//"
81+
82+
/// Exponentiation operator (`**`)
83+
case power = "**"
84+
85+
/// Modulo operator (`%`)
86+
case modulo = "%"
87+
88+
// MARK: String Operators
89+
90+
/// String concatenation operator (`~`)
91+
case concat = "~"
92+
93+
// MARK: Equality Comparison Operators
94+
95+
/// Equality operator (`==`)
96+
case equal = "=="
97+
98+
/// Inequality operator (`!=`)
99+
case notEqual = "!="
100+
101+
// MARK: Relational Comparison Operators
102+
103+
/// Less than operator (`<`)
104+
case less = "<"
105+
106+
/// Less than or equal to operator (`<=`)
107+
case lessEqual = "<="
108+
109+
/// Greater than operator (`>`)
110+
case greater = ">"
111+
112+
/// Greater than or equal to operator (`>=`)
113+
case greaterEqual = ">="
114+
115+
// MARK: Logical Operators
116+
117+
/// Logical AND operator (`and`)
118+
case and
119+
120+
/// Logical OR operator (`or`)
121+
case or
122+
123+
// MARK: Membership Test Operators
124+
125+
/// Membership test operator (`in`)
126+
case `in`
127+
128+
/// Negated membership test operator (`not in`)
129+
case notIn = "not in"
130+
}
131+
132+
/// Binary operation with operator and operands.
133+
case binary(BinaryOp, Expression, Expression)
134+
135+
/// Ternary conditional expression (`value if test else alternate`)
136+
case ternary(Expression, test: Expression, alternate: Expression?)
137+
138+
/// Function call with arguments and keyword arguments.
139+
case call(Expression, [Expression], [String: Expression])
140+
141+
/// Member access (object.property or object[key]).
142+
case member(Expression, Expression, computed: Bool)
143+
144+
/// Array or string slicing operation (`foo[1:2:3]`)
145+
case slice(Expression, start: Expression?, stop: Expression?, step: Expression?)
146+
147+
/// Filter application to transform a value (`foo | filter(2)`)
148+
case filter(Expression, String, [Expression], [String: Expression])
149+
150+
/// `is` test (`foo is divisibleby(2)`)
151+
case test(Expression, String, [Expression], negated: Bool)
152+
}
153+
154+
/// A control flow statement that affects template execution.
155+
public enum Statement: Hashable, Codable, Sendable {
156+
/// Block of nodes to execute sequentially.
157+
case program([Node])
158+
159+
/// Variable assignment statement.
160+
case set(target: Expression, value: Expression?, body: [Node])
161+
162+
/// Conditional statement with test, body, and optional alternate.
163+
case `if`(Expression, [Node], [Node])
164+
165+
/// Loop variable specification for for-loops.
166+
public enum LoopVar: Hashable, Codable, Sendable {
167+
/// Single loop variable.
168+
case single(String)
169+
/// Multiple loop variables for unpacking.
170+
case tuple([String])
171+
}
172+
173+
/// Loop statement with variable, iterable, body, else block, and optional condition.
174+
case `for`(LoopVar, Expression, [Node], [Node], test: Expression?)
175+
176+
/// Macro definition with name, parameters, default values, and body.
177+
case macro(String, [String], OrderedDictionary<String, Expression>, [Node])
178+
179+
/// Exits a loop immediately.
180+
case `break`
181+
182+
/// Skips the current iteration of a loop.
183+
case `continue`
184+
185+
/// Calls a macro with a body.
186+
case call(callable: Expression, callerArgs: [Expression]?, body: [Node])
187+
188+
/// Applies a filter to a block of content.
189+
case filter(filterExpr: Expression, body: [Node])
190+
}

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)