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 10cf426 commit 9a43ea0
Show file tree
Hide file tree
Showing 5 changed files with 393 additions and 4 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
}
}
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
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
84 changes: 84 additions & 0 deletions Sources/Utilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,87 @@ 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 9a43ea0

Please sign in to comment.