
Values.pudu
Pudu140 lines5.6 KB
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 Log8910export fn boolean(flag: Bool) -> Option[Log.Value] { Some(Log.Scalar(Log.Boolean(flag))) }111213export 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}181920export fn text(held: Str) -> Option[Log.Value] { Some(Log.Scalar(Log.Text(held))) }212223export fn isTrue(held: &Option[Log.Value]) -> Bool { *held == Some(Log.Scalar(Log.Boolean(true))) }242526export 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}343536export 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}424344export 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}50515253export 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}666768export 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}727374export 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}84858687export 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)959697fn 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}114115116fn 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}131132133134export 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