
Expander.pudu
Pudu133 lines4.5 KB
1/** @Domain.Expander.Module — replaces references to other variables */2module PuduLangEnvironment.Domain.Expander34import Std.Char as Char5import Std.Option as Option6import Std.Text as Text78/** @Domain.Expander.Fallback — what a braced reference answers instead */9type Fallback10 = NoFallback11 | WhenUnsetOrEmpty(Str)12 | WhenUnset(Str)1314151617export fn expand(value: Str, known: &Map[Str, Str], process: &Map[Str, Str]) -> Str {18 let chars = value.chars()19 let size = chars.length()20 var pieces: Array[Str] = []21 var index = 022 while index < size {23 let symbol = chars[index]24 if symbol == '\\' {25 let start = index26 while index < size && chars[index] == '\\' { index = index + 1 }27 let run = index - start28 if index < size && chars[index] == '$' {29 pieces = pieces.push("\\".repeat(run / 2))30 if run % 2 == 1 {31 pieces = pieces.push("$")32 index = index + 133 }34 } else {35 pieces = pieces.push("\\".repeat(run))36 }37 } else if symbol == '$' {38 let replaced = reference(&chars, index, known, process)39 pieces = pieces.push(replaced[0])40 index = replaced[1]41 } else {42 pieces = pieces.push(symbol.toText())43 index = index + 144 }45 }46 pieces.join("")47}484950fn reference(chars: &Array[Char], at: Int, known: &Map[Str, Str], process: &Map[Str, Str]) -> (Str, Int) {51 let size = chars.length()52 let next = at + 153 if next < size && chars[next] == '{' {54 match closingBrace(chars, next + 1) {55 case Some(close) => (braced(Text.fromChars(&chars.slice(next + 1, close)), known, process), close + 1)56 case None => ("$", next)57 }58 } else if next < size && startsName(chars[next]) {59 var end = next + 160 while end < size && continuesName(chars[end]) { end = end + 1 }61 (resolve(Text.fromChars(&chars.slice(next, end)), &NoFallback, known, process), end)62 } else {63 ("$", next)64 }65}666768fn closingBrace(chars: &Array[Char], from: Int) -> Option[Int] {69 var depth = 170 var at = from71 while at < chars.length() {72 let symbol = chars[at]73 if symbol == '{' {74 depth = depth + 175 } else if symbol == '}' {76 depth = depth - 177 if depth == 0 { return Some(at) }78 }79 at = at + 180 }81 None82}83848586fn braced(inner: Str, known: &Map[Str, Str], process: &Map[Str, Str]) -> Str {87 let chars = inner.chars()88 var depth = 089 var at = 090 while at < chars.length() {91 let symbol = chars[at]92 if symbol == '{' {93 depth = depth + 194 } else if symbol == '}' {95 depth = depth - 196 } else if depth == 0 && symbol == ':' && at + 1 < chars.length() && chars[at + 1] == '-' {97 return resolve(inner.take(at).trim(), &WhenUnsetOrEmpty(inner.drop(at + 2)), known, process)98 } else if depth == 0 && symbol == '-' {99 return resolve(inner.take(at).trim(), &WhenUnset(inner.drop(at + 1)), known, process)100 }101 at = at + 1102 }103 resolve(inner.trim(), &NoFallback, known, process)104}105106107fn resolve(name: Str, fallback: &Fallback, known: &Map[Str, Str], process: &Map[Str, Str]) -> Str {108 let found = lookup(name, known, process)109 match fallback {110 case WhenUnsetOrEmpty(text) => if Option.unwrapOr(found, "").isEmpty() { expand(text, known, process) } else { Option.unwrapOr(found, "") }111 case WhenUnset(text) => match found {112 case Some(held) => held113 case None => expand(text, known, process)114 }115 case NoFallback => Option.unwrapOr(found, "")116 }117}118119120fn lookup(name: Str, known: &Map[Str, Str], process: &Map[Str, Str]) -> Option[Str] {121 if name.isEmpty() { return None }122 match known.get(name) {123 case Some(held) => Some(held)124 case None => process.get(name)125 }126}127128129fn startsName(symbol: Char) -> Bool { Char.isLower(symbol) || Char.isUpper(symbol) || symbol == '_' }130131132fn continuesName(symbol: Char) -> Bool { startsName(symbol) || Char.isDigit(symbol) }133