Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-log

Structured event logging for Pudu: message templates, enrichment, filtering, formatting, and sinks

0.1.0Apache-2.01

InstallClose

Values.pudu

Pudu140 lines5.6 KB

GitHub ↗
1/** @Log.Expressions.Values.Semantics — equality, order, and matching of values */2module PuduLangLog.Expressions.Values34import Std.Decimal as Decimal5import Std.List as List6import PuduLangLog.Domain.Display as Display7import PuduLangLog as Log89/// A boolean value.10export fn boolean(flag: Bool) -> Option[Log.Value] { Some(Log.Scalar(Log.Boolean(flag))) }1112/// An exact number value at the smallest scale that keeps it, so `1 / 4` is `0.25`.13export fn number(held: Decimal) -> Option[Log.Value] {14  var places = Decimal.scale(held)15  while places > 0 && Decimal.rescale(held, places - 1) == held { places = places - 1 }16  Some(Log.Scalar(Log.Exact(Decimal.rescale(held, places))))17}1819/// A text value.20export fn text(held: Str) -> Option[Log.Value] { Some(Log.Scalar(Log.Text(held))) }2122/// Whether a value is the boolean `true`; nothing else counts as true.23export fn isTrue(held: &Option[Log.Value]) -> Bool { *held == Some(Log.Scalar(Log.Boolean(true))) }2425/// A value's number, reading integers, floats, and decimals alike.26export fn decimalOf(held: &Option[Log.Value]) -> Option[Decimal] {27  match held {28    case Some(Log.Scalar(Log.Integer(value))) => Some(Decimal.fromInt(value))29    case Some(Log.Scalar(Log.Exact(value))) => Some(value)30    case Some(Log.Scalar(Log.Real(value))) => show(value).toDecimal()31    case _ => None32  }33}3435/// A value's text.36export fn textOf(held: &Option[Log.Value]) -> Option[Str] {37  match held {38    case Some(Log.Scalar(Log.Text(value))) => Some(value)39    case _ => None40  }41}4243/// A value's elements when it is a sequence.44export fn itemsOf(held: &Option[Log.Value]) -> Option[Array[Log.Value]] {45  match held {46    case Some(Log.Sequence(items)) => Some(items)47    case _ => None48  }49}5051/// Whether two values are equal: numbers by value whatever their kind, text optionally ignoring52/// case, and sequences, structures, and dictionaries member by member.53export fn equal(left: &Log.Value, right: &Log.Value, ignoring: Bool) -> Bool {54  let numbers = (decimalOf(&Some(*left)), decimalOf(&Some(*right)))55  if let (Some(first), Some(second)) = numbers { return first == second }56  match (*left, *right) {57    case (Log.Scalar(Log.Text(first)), Log.Scalar(Log.Text(second))) => if ignoring { first.toLower() == second.toLower() } else { first == second }58    case (Log.Sequence(first), Log.Sequence(second)) => first.length() == second.length() && List.all(&List.zip(&first, &second), |pair: (Log.Value, Log.Value)| equal(&pair[0], &pair[1], ignoring))59    case (Log.Structure(first), Log.Structure(second)) => first.properties.length() == second.properties.length() && List.all(&first.properties, |member: Log.Property| match List.find(&second.properties, |other: Log.Property| other.name == member.name) {60        case Some(other) => equal(&member.value, &other.value, ignoring)61        case None => false62      })63    case _ => *left == *right64  }65}6667/// The equality of two possibly undefined values; undefined when either is.68export fn equals(left: &Option[Log.Value], right: &Option[Log.Value], ignoring: Bool) -> Option[Log.Value] {69  let (first, second) = ((*left) ?, (*right) ?)70  boolean(equal(&first, &second, ignoring))71}7273/// The order of two numbers under a comparison operator; undefined unless both are numbers.74export fn compare(operator: Str, left: &Option[Log.Value], right: &Option[Log.Value]) -> Option[Log.Value] {75  let first = decimalOf(left) ?76  let second = decimalOf(right) ?77  match operator {78    case "<" => boolean(first < second)79    case "<=" => boolean(first <= second)80    case ">" => boolean(first > second)81    case _ => boolean(first >= second)82  }83}8485/// Whether text matches a pattern where `%` stands for any run of characters and `_` for one;86/// `%%` and `__` stand for the characters themselves.87export fn like(subject: Str, pattern: Str, ignoring: Bool) -> Bool {88  let cased = if ignoring { subject.toLower() } else { subject }89  let wanted = if ignoring { pattern.toLower() } else { pattern }90  matches(&cased.chars(), 0, &piecesOf(wanted), 0)91}9293/** @Log.Expressions.Values.Piece — one element of a like pattern */94type Piece = Any | One | Exactly(Char)9596/// The pattern as wildcards and literal characters.97fn piecesOf(pattern: Str) -> Array[Piece] {98  let characters = pattern.chars()99  var pieces: Array[Piece] = []100  var index = 0101  while index < characters.length() {102    let character = characters[index]103    let doubled = List.get(&characters, index + 1) == Some(character)104    if (character == '%' || character == '_') && doubled {105      pieces = pieces.push(Exactly(character))106      index = index + 2107    } else {108      pieces = pieces.push(if character == '%' { Any } else if character == '_' { One } else { Exactly(character) })109      index = index + 1110    }111  }112  pieces113}114115/// Whether the characters from `at` match the pieces from `piece`.116fn matches(subject: &Array[Char], at: Int, pieces: &Array[Piece], piece: Int) -> Bool {117  match List.get(pieces, piece) {118    case None => at == subject.length()119    case Some(Any) => {120      var start = at121      while start <= subject.length() {122        if matches(subject, start, pieces, piece + 1) { return true }123        start = start + 1124      }125      false126    }127    case Some(One) => at < subject.length() && matches(subject, at + 1, pieces, piece + 1)128    case Some(Exactly(character)) => List.get(subject, at) == Some(character) && matches(subject, at + 1, pieces, piece + 1)129  }130}131132/// A value as text for `ToString` and templates: text as it is, and anything else as displayed133/// under the format.134export fn render(held: &Log.Value, format: Option[Str]) -> Str {135  match held {136    case Log.Scalar(Log.Text(value)) => value137    case _ => Display.plain(&Display.value(held, &format))138  }139}140