@@ -12,42 +12,39 @@ class Environment {
12
12
13
13
var variables : [ String : any RuntimeValue ] = [
14
14
" namespace " : FunctionValue ( value: { args, _ in
15
- if args. count == 0 {
15
+ if args. isEmpty {
16
16
return ObjectValue ( value: [ : ] )
17
17
}
18
18
19
- if args. count != 1 || ! ( args [ 0 ] is ObjectValue ) {
19
+ guard args. count == 1 , let objectArg = args [ 0 ] as? ObjectValue else {
20
20
throw JinjaError . runtime ( " `namespace` expects either zero arguments or a single object argument " )
21
21
}
22
22
23
- return args [ 0 ]
23
+ return objectArg
24
24
} )
25
25
]
26
26
27
27
var tests : [ String : ( any RuntimeValue . . . ) throws -> Bool ] = [
28
- " boolean " : {
29
- args in
30
- args [ 0 ] is BooleanValue
28
+ " boolean " : { args in
29
+ return args [ 0 ] is BooleanValue
31
30
} ,
32
31
33
- " callable " : {
34
- args in
35
- args [ 0 ] is FunctionValue
32
+ " callable " : { args in
33
+ return args [ 0 ] is FunctionValue
36
34
} ,
37
35
38
- " odd " : {
39
- args in
40
- if let arg = args. first as? NumericValue {
41
- return arg. value as! Int % 2 != 0
36
+ " odd " : { args in
37
+ if let arg = args. first as? NumericValue , let intValue = arg. value as? Int {
38
+ return intValue % 2 != 0
42
39
} else {
43
- throw JinjaError . runtime ( " Cannot apply test 'odd' to type: \( type ( of: args. first) ) " )
40
+ throw JinjaError . runtime ( " Cannot apply test 'odd' to type: \( type ( of: args. first) ) " )
44
41
}
45
42
} ,
46
43
" even " : { args in
47
- if let arg = args. first as? NumericValue {
48
- return arg . value as! Int % 2 == 0
44
+ if let arg = args. first as? NumericValue , let intValue = arg . value as? Int {
45
+ return intValue % 2 == 0
49
46
} else {
50
- throw JinjaError . runtime ( " Cannot apply test 'even' to type: \( type ( of: args. first) ) " )
47
+ throw JinjaError . runtime ( " Cannot apply test 'even' to type: \( type ( of: args. first) ) " )
51
48
}
52
49
} ,
53
50
" false " : { args in
@@ -62,24 +59,28 @@ class Environment {
62
59
}
63
60
return false
64
61
} ,
62
+ " string " : { args in
63
+ return args [ 0 ] is StringValue
64
+ } ,
65
65
" number " : { args in
66
- args [ 0 ] is NumericValue
66
+ return args [ 0 ] is NumericValue
67
67
} ,
68
68
" integer " : { args in
69
69
if let arg = args [ 0 ] as? NumericValue {
70
70
return arg. value is Int
71
71
}
72
-
73
72
return false
74
73
} ,
74
+ " mapping " : { args in
75
+ return args [ 0 ] is ObjectValue
76
+ } ,
75
77
" iterable " : { args in
76
- args [ 0 ] is ArrayValue || args [ 0 ] is StringValue
78
+ return args [ 0 ] is ArrayValue || args [ 0 ] is StringValue || args [ 0 ] is ObjectValue
77
79
} ,
78
80
" lower " : { args in
79
81
if let arg = args [ 0 ] as? StringValue {
80
82
return arg. value == arg. value. lowercased ( )
81
83
}
82
-
83
84
return false
84
85
} ,
85
86
" upper " : { args in
@@ -89,16 +90,47 @@ class Environment {
89
90
return false
90
91
} ,
91
92
" none " : { args in
92
- args [ 0 ] is NullValue
93
+ return args [ 0 ] is NullValue
93
94
} ,
94
95
" defined " : { args in
95
- !( args [ 0 ] is UndefinedValue )
96
+ return !( args [ 0 ] is UndefinedValue )
96
97
} ,
97
98
" undefined " : { args in
98
- args [ 0 ] is UndefinedValue
99
+ return args [ 0 ] is UndefinedValue
99
100
} ,
100
- " equalto " : { _ in
101
- throw JinjaError . syntaxNotSupported ( " equalto " )
101
+ " equalto " : { args in
102
+ if args. count == 2 {
103
+ if let left = args [ 0 ] as? StringValue , let right = args [ 1 ] as? StringValue {
104
+ return left. value == right. value
105
+ } else if let left = args [ 0 ] as? NumericValue , let right = args [ 1 ] as? NumericValue ,
106
+ let leftInt = left. value as? Int , let rightInt = right. value as? Int
107
+ {
108
+ return leftInt == rightInt
109
+ } else if let left = args [ 0 ] as? BooleanValue , let right = args [ 1 ] as? BooleanValue {
110
+ return left. value == right. value
111
+ } else {
112
+ return false
113
+ }
114
+ } else {
115
+ return false
116
+ }
117
+ } ,
118
+ " eq " : { args in
119
+ if args. count == 2 {
120
+ if let left = args [ 0 ] as? StringValue , let right = args [ 1 ] as? StringValue {
121
+ return left. value == right. value
122
+ } else if let left = args [ 0 ] as? NumericValue , let right = args [ 1 ] as? NumericValue ,
123
+ let leftInt = left. value as? Int , let rightInt = right. value as? Int
124
+ {
125
+ return leftInt == rightInt
126
+ } else if let left = args [ 0 ] as? BooleanValue , let right = args [ 1 ] as? BooleanValue {
127
+ return left. value == right. value
128
+ } else {
129
+ return false
130
+ }
131
+ } else {
132
+ return false
133
+ }
102
134
} ,
103
135
]
104
136
@@ -107,61 +139,91 @@ class Environment {
107
139
}
108
140
109
141
func isFunction< T> ( _ value: Any , functionType: T . Type ) -> Bool {
110
- value is T
142
+ return value is T
111
143
}
112
144
113
145
func convertToRuntimeValues( input: Any ) throws -> any RuntimeValue {
114
146
switch input {
115
147
case let value as Bool :
116
148
return BooleanValue ( value: value)
117
- case let values as [ any Numeric ] :
118
- var items : [ any RuntimeValue ] = [ ]
119
- for value in values {
120
- try items. append ( self . convertToRuntimeValues ( input: value) )
121
- }
122
- return ArrayValue ( value: items)
123
149
case let value as any Numeric :
124
150
return NumericValue ( value: value)
125
151
case let value as String :
126
152
return StringValue ( value: value)
153
+ case let data as Data :
154
+ guard let string = String ( data: data, encoding: . utf8) else {
155
+ throw JinjaError . runtime ( " Failed to convert data to string " )
156
+ }
157
+ return StringValue ( value: string)
127
158
case let fn as ( String ) throws -> Void :
128
159
return FunctionValue { args, _ in
129
- var arg = " "
130
- switch args [ 0 ] . value {
131
- case let value as String :
132
- arg = value
133
- case let value as Bool :
134
- arg = String ( value)
135
- default :
136
- throw JinjaError . runtime ( " Unknown arg type: \( type ( of: args [ 0 ] . value) ) " )
160
+ guard let stringArg = args [ 0 ] as? StringValue else {
161
+ throw JinjaError . runtime ( " Argument must be a StringValue " )
137
162
}
138
-
139
- try fn ( arg)
163
+ try fn ( stringArg. value)
140
164
return NullValue ( )
141
165
}
142
166
case let fn as ( Bool ) throws -> Void :
143
167
return FunctionValue { args, _ in
144
- try fn ( args [ 0 ] . value as! Bool )
168
+ guard let boolArg = args [ 0 ] as? BooleanValue else {
169
+ throw JinjaError . runtime ( " Argument must be a BooleanValue " )
170
+ }
171
+ try fn ( boolArg. value)
145
172
return NullValue ( )
146
173
}
147
174
case let fn as ( Int , Int ? , Int ) -> [ Int ] :
148
175
return FunctionValue { args, _ in
149
- let result = fn ( args [ 0 ] . value as! Int , args [ 1 ] . value as? Int , args [ 2 ] . value as! Int )
176
+ guard args. count > 0 , let arg0 = args [ 0 ] as? NumericValue , let int0 = arg0. value as? Int else {
177
+ throw JinjaError . runtime ( " First argument must be an Int " )
178
+ }
179
+ var int1 : Int ? = nil
180
+ if args. count > 1 {
181
+ if let numericValue = args [ 1 ] as? NumericValue , let tempInt1 = numericValue. value as? Int {
182
+ int1 = tempInt1
183
+ } else {
184
+ throw JinjaError . runtime ( " Second argument must be an Int or nil " )
185
+ }
186
+ }
187
+ var int2 : Int = 1
188
+ if args. count > 2 {
189
+ if let numericValue = args [ 2 ] as? NumericValue , let tempInt2 = numericValue. value as? Int {
190
+ int2 = tempInt2
191
+ } else {
192
+ throw JinjaError . runtime ( " Third argument must be an Int " )
193
+ }
194
+ }
195
+ let result = fn ( int0, int1, int2)
150
196
return try self . convertToRuntimeValues ( input: result)
151
197
}
152
- case let values as [ Any ] :
153
- var items : [ any RuntimeValue ] = [ ]
154
- for value in values {
155
- try items. append ( self . convertToRuntimeValues ( input: value) )
198
+ case let fn as ( [ Int ] ) -> [ Int ] :
199
+ return FunctionValue { args, _ in
200
+ let intArgs = args. compactMap { ( $0 as? NumericValue ) ? . value as? Int }
201
+ guard intArgs. count == args. count else {
202
+ throw JinjaError . runtime ( " Arguments to range must be Ints " )
203
+ }
204
+ let result = fn ( intArgs)
205
+ return try self . convertToRuntimeValues ( input: result)
156
206
}
207
+ case let fn as ( Int , Int ? , Int ) -> [ Int ] :
208
+ return FunctionValue { args, _ in
209
+ guard let arg0 = args [ 0 ] as? NumericValue , let int0 = arg0. value as? Int else {
210
+ throw JinjaError . runtime ( " First argument must be an Int " )
211
+ }
212
+ let int1 = ( args. count > 1 ) ? ( args [ 1 ] as? NumericValue ) ? . value as? Int : nil
213
+ guard let arg2 = args. last as? NumericValue , let int2 = arg2. value as? Int else {
214
+ throw JinjaError . runtime ( " Last argument must be an Int " )
215
+ }
216
+ let result = fn ( int0, int1, int2)
217
+ return try self . convertToRuntimeValues ( input: result)
218
+ }
219
+ case let values as [ Any ] :
220
+ let items = try values. map { try self . convertToRuntimeValues ( input: $0) }
157
221
return ArrayValue ( value: items)
158
- case let dictionary as [ String : String ] :
222
+ case let dictionary as [ String : Any ] :
159
223
var object : [ String : any RuntimeValue ] = [ : ]
160
-
161
224
for (key, value) in dictionary {
162
- object [ key] = StringValue ( value : value)
225
+ object [ key] = try self . convertToRuntimeValues ( input : value)
163
226
}
164
-
165
227
return ObjectValue ( value: object)
166
228
case is NullValue :
167
229
return NullValue ( )
@@ -176,12 +238,11 @@ class Environment {
176
238
}
177
239
178
240
func declareVariable( name: String , value: any RuntimeValue ) throws -> any RuntimeValue {
179
- if self . variables. contains ( where : { $0 . 0 == name } ) {
241
+ if self . variables. keys . contains ( name) {
180
242
throw JinjaError . syntax ( " Variable already declared: \( name) " )
181
243
}
182
244
183
245
self . variables [ name] = value
184
-
185
246
return value
186
247
}
187
248
@@ -191,25 +252,21 @@ class Environment {
191
252
return value
192
253
}
193
254
194
- func resolve( name: String ) throws -> Self {
195
- if self . variables. contains ( where : { $0 . 0 == name } ) {
255
+ func resolve( name: String ) throws -> Environment {
256
+ if self . variables. keys . contains ( name) {
196
257
return self
197
258
}
198
259
199
- if let parent {
200
- return try parent. resolve ( name: name) as! Self
260
+ if let parent = self . parent {
261
+ return try parent. resolve ( name: name)
201
262
}
202
263
203
264
throw JinjaError . runtime ( " Unknown variable: \( name) " )
204
265
}
205
266
206
267
func lookupVariable( name: String ) -> any RuntimeValue {
207
268
do {
208
- if let value = try self . resolve ( name: name) . variables [ name] {
209
- return value
210
- } else {
211
- return UndefinedValue ( )
212
- }
269
+ return try self . resolve ( name: name) . variables [ name] ?? UndefinedValue ( )
213
270
} catch {
214
271
return UndefinedValue ( )
215
272
}
0 commit comments