Skip to content

Commit

Permalink
Add functionality from TypeScript implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
DePasqualeOrg committed Dec 29, 2024
1 parent 9d79438 commit 0fdf32f
Show file tree
Hide file tree
Showing 7 changed files with 418 additions and 16 deletions.
21 changes: 21 additions & 0 deletions Sources/Ast.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ struct For: Statement {
var loopvar: Loopvar
var iterable: Expression
var body: [Statement]
var defaultBlock: [Statement]
}

struct MemberExpression: Expression {
Expand Down Expand Up @@ -124,3 +125,23 @@ struct KeywordArgumentExpression: Expression {
struct NullLiteral: Literal {
var value: Any? = nil
}

struct SelectExpression: Expression {
var iterable: Expression
var test: Expression
}

struct Macro: Statement {
var name: Identifier
var args: [Expression]
var body: [Statement]
}

struct KeywordArgumentsValue: RuntimeValue {
var value: [String: any RuntimeValue]
var builtins: [String: any RuntimeValue] = [:]

func bool() -> Bool {
!value.isEmpty
}
}
15 changes: 5 additions & 10 deletions Sources/Environment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ class Environment {
}
return false
},
"string": { args in
args[0] is StringValue
},
"number": { args in
args[0] is NumericValue
},
Expand Down Expand Up @@ -114,12 +117,6 @@ class Environment {
switch input {
case let value as Bool:
return BooleanValue(value: value)
case let values as [any Numeric]:
var items: [any RuntimeValue] = []
for value in values {
try items.append(self.convertToRuntimeValues(input: value))
}
return ArrayValue(value: items)
case let value as any Numeric:
return NumericValue(value: value)
case let value as String:
Expand Down Expand Up @@ -155,13 +152,11 @@ class Environment {
try items.append(self.convertToRuntimeValues(input: value))
}
return ArrayValue(value: items)
case let dictionary as [String: String]:
case let dictionary as [String: Any]: // Handle dictionaries recursively
var object: [String: any RuntimeValue] = [:]

for (key, value) in dictionary {
object[key] = StringValue(value: value)
object[key] = try self.convertToRuntimeValues(input: value)
}

return ObjectValue(value: object)
case is NullValue:
return NullValue()
Expand Down
30 changes: 28 additions & 2 deletions Sources/Parser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -447,15 +447,41 @@ func parse(tokens: [Token]) throws -> Program {

let iterable = try parseExpression()

let iterableExpr: Expression
if typeof(.if) {
current += 1 // consume if token
let predicate = try parseExpression()
iterableExpr = SelectExpression(iterable: iterable as! Expression, test: predicate as! Expression)
} else {
iterableExpr = iterable as! Expression
}

try expect(type: .closeStatement, error: "Expected closing statement token")

var body: [Statement] = []
while not(.openStatement, .endFor) {
var defaultBlock: [Statement] = []

while not(.openStatement, .endFor) && not(.openStatement, .else) {
try body.append(parseAny())
}

if typeof(.openStatement, .else) {
current += 1 // consume {%
try expect(type: .else, error: "Expected else token")
try expect(type: .closeStatement, error: "Expected closing statement token")

while not(.openStatement, .endFor) {
try defaultBlock.append(parseAny())
}
}

if let loopVariable = loopVariable as? Loopvar {
return For(loopvar: loopVariable, iterable: iterable as! Expression, body: body)
return For(
loopvar: loopVariable,
iterable: iterableExpr,
body: body,
defaultBlock: defaultBlock
)
}

throw JinjaError.syntax(
Expand Down
19 changes: 17 additions & 2 deletions Sources/Runtime.swift
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,18 @@ struct Interpreter {
throw JinjaError.runtime("Cannot perform operation on null values")
} else if let left = left as? NumericValue, let right = right as? NumericValue {
switch node.operation.value {
case "+": throw JinjaError.syntaxNotSupported("+")
case "+":
if let leftInt = left.value as? Int, let rightInt = right.value as? Int {
return NumericValue(value: leftInt + rightInt)
} else if let leftDouble = left.value as? Double, let rightDouble = right.value as? Double {
return NumericValue(value: leftDouble + rightDouble)
} else if let leftInt = left.value as? Int, let rightDouble = right.value as? Double {
return NumericValue(value: Double(leftInt) + rightDouble)
} else if let leftDouble = left.value as? Double, let rightInt = right.value as? Int {
return NumericValue(value: leftDouble + Double(rightInt))
} else {
throw JinjaError.runtime("Unsupported numeric types for addition")
}
case "-": throw JinjaError.syntaxNotSupported("-")
case "*": throw JinjaError.syntaxNotSupported("*")
case "/": throw JinjaError.syntaxNotSupported("/")
Expand Down Expand Up @@ -376,6 +387,8 @@ struct Interpreter {
rightValue = String(value)
case let value as Bool:
rightValue = String(value)
case let value as Double:
rightValue = String(value)
default:
throw JinjaError.runtime("Unknown right value type:\(type(of: right.value))")
}
Expand All @@ -386,7 +399,9 @@ struct Interpreter {
case let value as Int:
leftValue = String(value)
case let value as Bool:
rightValue = String(value)
leftValue = String(value)
case let value as Double:
leftValue = String(value)
default:
throw JinjaError.runtime("Unknown left value type:\(type(of: left.value))")
}
Expand Down
1 change: 0 additions & 1 deletion Sources/Template.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ public struct Template {
throw JinjaError.runtime("\(args)")
}
)
try env.set(name: "range", value: range)

for (key, value) in items {
try env.set(name: key, value: value)
Expand Down
83 changes: 83 additions & 0 deletions Sources/Utilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,86 @@ func slice<T>(_ array: [T], start: Int? = nil, stop: Int? = nil, step: Int? = 1)

return slicedArray
}

func toJSON(_ input: any RuntimeValue, indent: Int? = nil, depth: Int = 0) throws -> String {
let currentDepth = depth

switch input {
case is NullValue, is UndefinedValue:
return "null"

case let value as NumericValue:
return String(describing: value.value)

case let value as StringValue:
return "\"\(value.value)\"" // Directly wrap string in quotes

case let value as BooleanValue:
return value.value ? "true" : "false"

case let arr as ArrayValue:
let indentValue = indent != nil ? String(repeating: " ", count: indent!) : ""
let basePadding = "\n" + String(repeating: indentValue, count: currentDepth)
let childrenPadding = basePadding + indentValue // Depth + 1

let core = try arr.value.map { try toJSON($0, indent: indent, depth: currentDepth + 1) }

if indent != nil {
return "[\(childrenPadding)\(core.joined(separator: ",\(childrenPadding)"))\(basePadding)]"
} else {
return "[\(core.joined(separator: ", "))]"
}

case let obj as ObjectValue:
let indentValue = indent != nil ? String(repeating: " ", count: indent!) : ""
let basePadding = "\n" + String(repeating: indentValue, count: currentDepth)
let childrenPadding = basePadding + indentValue // Depth + 1

let core = try obj.value.map { key, value in
let v = "\"\(key)\": \(try toJSON(value, indent: indent, depth: currentDepth + 1))"
return indent != nil ? "\(childrenPadding)\(v)" : v
}

if indent != nil {
return "{\(core.joined(separator: ","))\(basePadding)}"
} else {
return "{\(core.joined(separator: ", "))}"
}

default:
throw JinjaError.runtime("Cannot convert to JSON: \(type(of: input))")
}
}

// Helper function to convert values to JSON strings
private func jsonString(_ value: Any) throws -> String {
let data = try JSONSerialization.data(withJSONObject: value)
guard let string = String(data: data, encoding: .utf8) else {
throw JinjaError.runtime("Failed to convert value to JSON string")
}
return string
}

extension String {
func titleCase() -> String {
self.components(separatedBy: .whitespacesAndNewlines)
.map { $0.prefix(1).uppercased() + $0.dropFirst().lowercased() }
.joined(separator: " ")
}

func indent(_ width: Int, first: Bool = false, blank: Bool = false) -> String {
let indent = String(repeating: " ", count: width)
return self.components(separatedBy: .newlines)
.enumerated()
.map { index, line in
if line.isEmpty && !blank {
return line
}
if index == 0 && !first {
return line
}
return indent + line
}
.joined(separator: "\n")
}
}
Loading

0 comments on commit 0fdf32f

Please sign in to comment.