
Evaluator.pudu
Pudu233 lines11.4 KB
1/** @Log.Expressions.Evaluator.Engine — expressions evaluated against an event */2module PuduLangLog.Expressions.Evaluator34import Std.Decimal as Decimal5import Std.List as List6import Std.Math.Float as Float7import Std.Option as Option8import PuduLangLog.Domain.Display as Display9import PuduLangLog.Domain.EventId as EventId10import PuduLangLog.Domain.Levels as Levels11import PuduLangLog.Domain.Properties as Properties12import PuduLangLog.Expressions.Functions as Functions13import PuduLangLog.Expressions.Syntax as Syntax14import PuduLangLog.Expressions.Values as Values15import PuduLangLog as Log1617/** @Log.Expressions.Evaluator.Scope — the event, bound names, and the present moment */18export type Scope = { event: Log.Event, locals: Array[(Str, Log.Value)], now: Log.Timestamp, referenced: Array[Str] }192021export fn scopeOf(event: &Log.Event, now: Log.Timestamp) -> Scope { Scope{event: *event, locals: [], now: now, referenced: []} }222324export fn evaluate(expression: &Syntax.Expression, within: &Scope) -> Option[Log.Value] {25 match expression {26 case Syntax.Constant(held) => Some(held)27 case Syntax.Name(name) => nameOf(name, within)28 case Syntax.Member(target, name) => Functions.elementAt(&evaluate(&target, within) ?, &Log.Scalar(Log.Text(name)), false)29 case Syntax.Index(target, key) => Functions.elementAt(&evaluate(&target, within) ?, &evaluate(&key, within) ?, false)30 case Syntax.Wildcard(_, _) => None31 case Syntax.Call(name, arguments, ignoring) => {32 if let Some(expanded) = comprehension(expression, within) { return expanded }33 if name == "inspect" && arguments == [Syntax.Name("@x")] { return failureOf(&within.event.failure) }34 let values = arguments.map(|argument: Syntax.Expression| evaluate(&argument, within))35 if name == "rest" { return restOf(within, Values.isTrue(&Option.flatten(List.first(&values)))) }36 Functions.call(name, &values, ignoring, &within.now)37 }38 case Syntax.Unary(operator, operand) => {39 let held = evaluate(&operand, within)40 if operator == "not" {41 return match held {42 case Some(Log.Scalar(Log.Boolean(flag))) => Values.boolean(!flag)43 case _ => None44 }45 }46 Values.number(Decimal.zero() - Values.decimalOf(&held) ?)47 }48 case Syntax.Binary(operator, left, right, ignoring) => {49 if let Some(expanded) = comprehension(expression, within) { return expanded }50 binary(operator, &left, &right, ignoring, within)51 }52 case Syntax.ArrayOf(elements) => {53 var items: Array[Log.Value] = []54 for element in elements {55 match element {56 case Syntax.Item(item) => { if let Some(held) = evaluate(&item, within) { items = items.push(held) } }57 case Syntax.SpreadItems(spread) => { items = items.concat(Option.unwrapOr(Values.itemsOf(&evaluate(&spread, within)), [])) }58 }59 }60 Some(Log.Sequence(items))61 }62 case Syntax.ObjectOf(fields) => {63 var members: Array[Log.Property] = []64 for field in fields {65 match field {66 case Syntax.Pair(name, held) => {67 members = match evaluate(&held, within) {68 case Some(found) => Properties.put(&members, name, found)69 case None => Properties.remove(&members, name)70 }71 }72 case Syntax.SpreadMembers(spread) => {73 if let Some(Log.Structure(shape)) = evaluate(&spread, within) {74 for member in shape.properties { members = Properties.put(&members, member.name, member.value) }75 }76 }77 }78 }79 Some(Log.Structure(Log.Structure{tag: None, properties: members}))80 }81 case Syntax.Conditional(condition, then, otherwise) => if Values.isTrue(&evaluate(&condition, within)) { evaluate(&then, within) } else { evaluate(&otherwise, within) }82 }83}848586fn nameOf(name: Str, within: &Scope) -> Option[Log.Value] {87 if let Some(bound) = List.find(&within.locals, |local: (Str, Log.Value)| local[0] == name) { return Some(bound[1]) }88 let event = &within.event89 match name {90 case "@t" => Some(Log.Scalar(Log.Moment(event.timestamp)))91 case "@m" => Values.text(Display.plain(&Display.message(&event.template, &event.properties, &Some("l"))))92 case "@mt" => Values.text(event.template.text)93 case "@l" => Values.text(Levels.name(event.level))94 case "@x" => Values.text(Display.failure(&event.failure ?))95 case "@p" => Some(Log.Structure(Log.Structure{tag: None, properties: event.properties}))96 case "@i" => Some(Log.Scalar(Log.Integer(Option.unwrapOr(convertInteger[Int](EventId.compute(event.template.text)), 0))))97 case "@r" => Some(Log.Sequence(event.template.holes.filter(|hole: Log.Hole| hole.format != None).map(|hole: Log.Hole| Log.Scalar(Log.Text(Display.plain(&Display.hole(&hole, &event.properties, false, false)))))))98 case "@tr" => Values.text(event.traceId ?)99 case "@sp" => Values.text(event.spanId ?)100 case _ => Properties.find(&event.properties, name)101 }102}103104105106fn restOf(within: &Scope, deep: Bool) -> Option[Log.Value] {107 let named = within.event.template.holes.map(|hole: Log.Hole| hole.name)108 let kept = within.event.properties.filter(|property: Log.Property| !within.referenced.contains(property.name) && !(deep && named.contains(property.name)))109 Some(Log.Structure(Log.Structure{tag: None, properties: kept}))110}111112113fn failureOf(problem: &Option[Log.Failure]) -> Option[Log.Value] {114 let found = (*problem) ?115 Some(Log.Structure(Log.Structure {116 tag: Some(found.kind),117 properties: [118 Log.Property{name: "Kind", value: Log.Scalar(Log.Text(found.kind))},119 Log.Property{name: "Message", value: Log.Scalar(Log.Text(found.message))},120 Log.Property{name: "Trace", value: Log.Sequence(found.trace.map(|line: Str| Log.Scalar(Log.Text(line))))},121 Log.Property{name: "Causes", value: Log.Sequence(found.causes.map(|cause: Log.Failure| Option.unwrapOr(failureOf(&Some(cause)), Log.Scalar(Log.Null))))}122 ]123 }))124}125126127fn binary(operator: Str, left: &Syntax.Expression, right: &Syntax.Expression, ignoring: Bool, within: &Scope) -> Option[Log.Value] {128 if operator == "and" { return Values.boolean(Values.isTrue(&evaluate(left, within)) && Values.isTrue(&evaluate(right, within))) }129 if operator == "or" { return Values.boolean(Values.isTrue(&evaluate(left, within)) || Values.isTrue(&evaluate(right, within))) }130 let first = evaluate(left, within)131 if operator == "is null" { return Values.boolean(first == None || first == Some(Log.Scalar(Log.Null))) }132 let second = evaluate(right, within)133 match operator {134 case "=" => Values.equals(&first, &second, ignoring)135 case "<>" => Option.map(Values.equals(&first, &second, ignoring), |equal: Log.Value| Log.Scalar(Log.Boolean(equal != Log.Scalar(Log.Boolean(true)))))136 case "like" => Values.boolean(Values.like(Values.textOf(&first) ?, Values.textOf(&second) ?, ignoring))137 case "in" => {138 let wanted = first ?139 Values.boolean(List.any(&Values.itemsOf(&second) ?, |item: Log.Value| Values.equal(&wanted, &item, ignoring)))140 }141 case "+" => Values.number(Values.decimalOf(&first) ? + Values.decimalOf(&second) ?)142 case "-" => Values.number(Values.decimalOf(&first) ? - Values.decimalOf(&second) ?)143 case "*" => Values.number(Values.decimalOf(&first) ? * Values.decimalOf(&second) ?)144 case "/" => Values.number(Decimal.divideHalfEven(Values.decimalOf(&first) ?, Values.decimalOf(&second) ?, 28) ?)145 case "%" => remainder(&first, &second)146 case "^" => {147 let base = Decimal.toFloat64(Values.decimalOf(&first) ?)148 let exponent = Decimal.toFloat64(Values.decimalOf(&second) ?)149 Some(Log.Scalar(Log.Real(Float.powf(base, exponent) ?)))150 }151 case _ => Values.compare(operator, &first, &second)152 }153}154155156fn remainder(left: &Option[Log.Value], right: &Option[Log.Value]) -> Option[Log.Value] {157 let dividend = Values.decimalOf(left) ?158 let divisor = Values.decimalOf(right) ?159 let quotient = Decimal.truncate(Decimal.divideHalfEven(dividend, divisor, 28) ?)160 Values.number(dividend - quotient * divisor)161}162163164165166fn comprehension(expression: &Syntax.Expression, within: &Scope) -> Option[Option[Log.Value]] {167 let (target, quantifier) = wildcardIn(expression) ?168 let elements = match evaluate(&target, within) {169 case Some(Log.Sequence(items)) => items170 case Some(Log.Structure(shape)) => shape.properties.map(|member: Log.Property| member.value)171 case _ => { return Some(Values.boolean(false)) }172 }173 let results = elements.map(|element: Log.Value| Values.isTrue(&evaluate(&replaced(expression, &element), within)))174 Some(Values.boolean(if quantifier == Syntax.AnyOf { List.any(&results, |held: Bool| held) } else { List.all(&results, |held: Bool| held) }))175}176177178fn wildcardIn(expression: &Syntax.Expression) -> Option[(Syntax.Expression, Syntax.Quantifier)] {179 let operands = match expression {180 case Syntax.Binary(_, left, right, _) => [left, right]181 case Syntax.Call(_, arguments, _) => arguments182 case _ => []183 }184 for operand in operands {185 if let Some(found) = chainWildcard(&operand) { return Some(found) }186 }187 None188}189190191fn chainWildcard(expression: &Syntax.Expression) -> Option[(Syntax.Expression, Syntax.Quantifier)] {192 match expression {193 case Syntax.Wildcard(target, quantifier) => Some((target, quantifier))194 case Syntax.Member(target, _) => chainWildcard(&target)195 case Syntax.Index(target, _) => chainWildcard(&target)196 case _ => None197 }198}199200201fn replaced(expression: &Syntax.Expression, element: &Log.Value) -> Syntax.Expression {202 match expression {203 case Syntax.Binary(operator, left, right, ignoring) => {204 if chainWildcard(&left) != None { return Syntax.Binary(operator, substitute(&left, element), right, ignoring) }205 Syntax.Binary(operator, left, substitute(&right, element), ignoring)206 }207 case Syntax.Call(name, arguments, ignoring) => {208 var done = false209 var updated: Array[Syntax.Expression] = []210 for argument in arguments {211 if !done && chainWildcard(&argument) != None {212 updated = updated.push(substitute(&argument, element))213 done = true214 } else {215 updated = updated.push(argument)216 }217 }218 Syntax.Call(name, updated, ignoring)219 }220 case _ => *expression221 }222}223224225fn substitute(expression: &Syntax.Expression, element: &Log.Value) -> Syntax.Expression {226 match expression {227 case Syntax.Wildcard(_, _) => Syntax.Constant(*element)228 case Syntax.Member(target, name) => Syntax.Member(substitute(&target, element), name)229 case Syntax.Index(target, key) => Syntax.Index(substitute(&target, element), key)230 case _ => *expression231 }232}233