@@ -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,74 @@ 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)
127
153
case let fn as ( String ) throws -> Void :
128
154
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) ) " )
155
+ guard let stringArg = args [ 0 ] as? StringValue else {
156
+ throw JinjaError . runtime ( " Argument must be a StringValue " )
137
157
}
138
-
139
- try fn ( arg)
158
+ try fn ( stringArg. value)
140
159
return NullValue ( )
141
160
}
142
161
case let fn as ( Bool ) throws -> Void :
143
162
return FunctionValue { args, _ in
144
- try fn ( args [ 0 ] . value as! Bool )
163
+ guard let boolArg = args [ 0 ] as? BooleanValue else {
164
+ throw JinjaError . runtime ( " Argument must be a BooleanValue " )
165
+ }
166
+ try fn ( boolArg. value)
145
167
return NullValue ( )
146
168
}
147
169
case let fn as ( Int , Int ? , Int ) -> [ Int ] :
148
170
return FunctionValue { args, _ in
149
- let result = fn ( args [ 0 ] . value as! Int , args [ 1 ] . value as? Int , args [ 2 ] . value as! Int )
171
+ guard let arg0 = args [ 0 ] as? NumericValue , let int0 = arg0. value as? Int else {
172
+ throw JinjaError . runtime ( " First argument must be an Int " )
173
+ }
174
+ let int1 = ( args [ 1 ] as? NumericValue ) ? . value as? Int
175
+ guard let arg2 = args [ 2 ] as? NumericValue , let int2 = arg2. value as? Int else {
176
+ throw JinjaError . runtime ( " Third argument must be an Int " )
177
+ }
178
+ let result = fn ( int0, int1, int2)
150
179
return try self . convertToRuntimeValues ( input: result)
151
180
}
152
- case let values as [ Any ] :
153
- var items : [ any RuntimeValue ] = [ ]
154
- for value in values {
155
- try items. append ( self . convertToRuntimeValues ( input: value) )
181
+ case let fn as ( [ Int ] ) -> [ Int ] :
182
+ return FunctionValue { args, _ in
183
+ let intArgs = args. compactMap { ( $0 as? NumericValue ) ? . value as? Int }
184
+ guard intArgs. count == args. count else {
185
+ throw JinjaError . runtime ( " Arguments to range must be Ints " )
186
+ }
187
+ let result = fn ( intArgs)
188
+ return try self . convertToRuntimeValues ( input: result)
189
+ }
190
+ case let fn as ( Int , Int ? , Int ) -> [ Int ] :
191
+ return FunctionValue { args, _ in
192
+ guard let arg0 = args [ 0 ] as? NumericValue , let int0 = arg0. value as? Int else {
193
+ throw JinjaError . runtime ( " First argument must be an Int " )
194
+ }
195
+ let int1 = ( args. count > 1 ) ? ( args [ 1 ] as? NumericValue ) ? . value as? Int : nil
196
+ guard let arg2 = args. last as? NumericValue , let int2 = arg2. value as? Int else {
197
+ throw JinjaError . runtime ( " Last argument must be an Int " )
198
+ }
199
+ let result = fn ( int0, int1, int2)
200
+ return try self . convertToRuntimeValues ( input: result)
156
201
}
202
+ case let values as [ Any ] :
203
+ let items = try values. map { try self . convertToRuntimeValues ( input: $0) }
157
204
return ArrayValue ( value: items)
158
- case let dictionary as [ String : String ] :
205
+ case let dictionary as [ String : Any ] :
159
206
var object : [ String : any RuntimeValue ] = [ : ]
160
-
161
207
for (key, value) in dictionary {
162
- object [ key] = StringValue ( value : value)
208
+ object [ key] = try self . convertToRuntimeValues ( input : value)
163
209
}
164
-
165
210
return ObjectValue ( value: object)
166
211
case is NullValue :
167
212
return NullValue ( )
@@ -176,12 +221,11 @@ class Environment {
176
221
}
177
222
178
223
func declareVariable( name: String , value: any RuntimeValue ) throws -> any RuntimeValue {
179
- if self . variables. contains ( where : { $0 . 0 == name } ) {
224
+ if self . variables. keys . contains ( name) {
180
225
throw JinjaError . syntax ( " Variable already declared: \( name) " )
181
226
}
182
227
183
228
self . variables [ name] = value
184
-
185
229
return value
186
230
}
187
231
@@ -191,25 +235,21 @@ class Environment {
191
235
return value
192
236
}
193
237
194
- func resolve( name: String ) throws -> Self {
195
- if self . variables. contains ( where : { $0 . 0 == name } ) {
238
+ func resolve( name: String ) throws -> Environment {
239
+ if self . variables. keys . contains ( name) {
196
240
return self
197
241
}
198
242
199
- if let parent {
200
- return try parent. resolve ( name: name) as! Self
243
+ if let parent = self . parent {
244
+ return try parent. resolve ( name: name)
201
245
}
202
246
203
247
throw JinjaError . runtime ( " Unknown variable: \( name) " )
204
248
}
205
249
206
250
func lookupVariable( name: String ) -> any RuntimeValue {
207
251
do {
208
- if let value = try self . resolve ( name: name) . variables [ name] {
209
- return value
210
- } else {
211
- return UndefinedValue ( )
212
- }
252
+ return try self . resolve ( name: name) . variables [ name] ?? UndefinedValue ( )
213
253
} catch {
214
254
return UndefinedValue ( )
215
255
}
0 commit comments