spb/metrika Public
Stata-class statistics, GPU-accelerated by Apple Silicon. Native Swift — no Electron, no Python runtime, no compromises.
Swift 92.4%
HTML 3.3%
R 3%
Shell 1.3%
1//2// ExpressionParser.swift3// Metrika4//5// Author: Simon-Pierre Boucher6// Contact: contact@spboucher.ai7// Copyright © 2026 Simon-Pierre Boucher. All rights reserved.8//910/// Pratt (precedence-climbing) parser for ZQL expressions, shared by `if`11/// qualifiers, `gen`/`replace` right-hand sides, and weight expressions.12///13/// Precedence (low → high):14/// | · & · comparison (== != < <= > >=) · + - · * / · ^ · unary (- !)15struct ExpressionParser {16 var tokens: [ZQToken]17 var position: Int1819 init(tokens: [ZQToken], position: Int = 0) {20 self.tokens = tokens21 self.position = position22 }2324 var current: ZQToken { tokens[position] }2526 mutating func advance() -> ZQToken {27 let token = tokens[position]28 if position < tokens.count - 1 { position += 1 }29 return token30 }3132 private static let precedence: [String: Int] = [33 "|": 10,34 "&": 20,35 "==": 30, "!=": 30, "<": 30, "<=": 30, ">": 30, ">=": 30,36 "+": 40, "-": 40,37 "*": 50, "/": 50,38 "^": 60,39 ]4041 mutating func parseExpression(minPrecedence: Int = 0) throws(ZQParseError) -> ZQExpression {42 var left = try parseUnary()4344 while case .op(let symbol) = current.kind,45 let precedence = Self.precedence[symbol],46 precedence >= minPrecedence {47 _ = advance()48 // ^ is right-associative; everything else is left-associative.49 let nextMin = symbol == "^" ? precedence : precedence + 150 let right = try parseExpression(minPrecedence: nextMin)51 left = .binary(op: symbol, left, right)52 }53 return left54 }5556 private mutating func parseUnary() throws(ZQParseError) -> ZQExpression {57 if case .op(let symbol) = current.kind, symbol == "-" || symbol == "!" {58 _ = advance()59 let operand = try parseUnary()60 return .unary(op: symbol, operand)61 }62 return try parsePrimary()63 }6465 private mutating func parsePrimary() throws(ZQParseError) -> ZQExpression {66 let token = current67 switch token.kind {68 case .number(let value):69 _ = advance()70 return .number(value)7172 case .string(let value):73 _ = advance()74 return .string(value)7576 case .op("."):77 // Bare `.` is the missing-value literal (`if income == .`).78 _ = advance()79 return .missing8081 case .identifier(let name):82 _ = advance()83 if case .lparen = current.kind {84 _ = advance()85 var arguments: [ZQExpression] = []86 if !isKind(.rparen) {87 repeat {88 arguments.append(try parseExpression())89 } while consumeIfKind(.comma)90 }91 try expect(.rparen)92 return .call(name: name, arguments: arguments)93 }94 return .variable(name)9596 case .lparen:97 _ = advance()98 let inner = try parseExpression()99 try expect(.rparen)100 return inner101102 default:103 throw ZQParseError(104 message: "expected an expression, found \(token.kind.describe)",105 column: token.column106 )107 }108 }109110 func isKind(_ kind: ZQToken.Kind) -> Bool { current.kind == kind }111112 mutating func consumeIfKind(_ kind: ZQToken.Kind) -> Bool {113 if current.kind == kind {114 _ = advance()115 return true116 }117 return false118 }119120 mutating func expect(_ kind: ZQToken.Kind) throws(ZQParseError) {121 guard consumeIfKind(kind) else {122 throw ZQParseError(123 message: "expected \(kind.describe), found \(current.kind.describe)",124 column: current.column125 )126 }127 }128}129