
Parser.pudu
Pudu106 lines4.0 KB
1/** @Domain.Parser.Module — lines of an environment file to entries */2module PuduLangEnvironment.Domain.Parser34import Std.Char as Char5import Std.Text as Text6import PuduLangEnvironment.Constants.Names as Names7import PuduLangEnvironment.Domain.Expander as Expander8import PuduLangEnvironment.Domain.Layers as Layers910/** @Domain.Parser.Rules — the reading rules the options set */11export type Rules = { trimValues: Bool, exportSyntax: Bool, inlineComments: Bool, expansion: Bool, overwrite: Bool }1213/** @Domain.Parser.Unclosed — a quote opened and never closed */14export type Unclosed = { line: Int, key: Str }1516/** @Domain.Parser.Parsed — entries and what later expansions know */17export type Parsed = { entries: Array[Layers.Entry], known: Map[Str, Str] }1819202122export fn parse(rows: &Array[Str], rules: &Rules, known: Map[Str, Str], process: &Map[Str, Str]) -> Result[Parsed, Unclosed] {23 var entries: Array[Layers.Entry] = []24 var seen = known25 var index = 026 let size = rows.length()27 while index < size {28 let line = rows[index]29 let number = index + 130 index = index + 131 let visible = Text.trimStart(line)32 let equals = line.indexOf("=")33 if visible.isEmpty() || visible.startsWith("#") || equals < 1 { continue }34 let key = keyOf(line.take(equals), rules.exportSyntax)35 if key.isEmpty() { continue }36 let raw = line.drop(equals + 1)37 let opened = Text.trimStart(raw)38 var value = ""39 if opened.startsWith("'") || opened.startsWith("\"") {40 let quote = opened.take(1)41 var body = ""42 var current = opened.drop(1)43 var close = closingQuote(current, quote)44 while close < 0 {45 if index >= size { return Err(Unclosed{line: number, key: key}) }46 body = body + current + "\n"47 current = rows[index]48 index = index + 149 close = closingQuote(current, quote)50 }51 let unquoted = (body + current.take(close)).replace("\\" + quote, quote)52 let expanded = if rules.expansion && quote == "\"" { Expander.expand(unquoted, &seen, process) } else { unquoted }53 value = expanded.replace("\\\\", "\\")54 } else {55 let bare = if rules.inlineComments { withoutComment(raw) } else { raw }56 value = if rules.expansion { Expander.expand(bare, &seen, process) } else { bare }57 }58 if rules.trimValues { value = value.trim() }59 seen = Layers.keep(&seen, key, value, rules.overwrite)60 entries = entries.push(Layers.Entry{key: key, value: value})61 }62 Ok(Parsed{entries: entries, known: seen})63}64656667export fn lines(text: Str) -> Array[Str] {68 Text.stripPrefix(text, Names.BYTE_ORDER_MARK).split("\n").map(|line: Str| if line.endsWith("\r") { line.take(line.length() - 1) } else { line })69}707172fn keyOf(text: Str, exportSyntax: Bool) -> Str {73 let key = text.trim()74 if exportSyntax && key.startsWith(Names.EXPORT_KEYWORD) {75 let rest = key.drop(Names.EXPORT_KEYWORD.length())76 if Text.trimStart(rest) != rest { return rest.trim() }77 }78 key79}808182fn closingQuote(text: Str, quote: Str) -> Int {83 let chars = text.chars()84 var escaped = false85 var at = 086 while at < chars.length() {87 let symbol = chars[at].toText()88 if symbol == quote && !escaped { return at }89 escaped = symbol == "\\" && !escaped90 at = at + 191 }92 -193}94959697fn withoutComment(value: Str) -> Str {98 let chars = value.chars()99 var at = 1100 while at < chars.length() {101 if chars[at] == '#' && Char.isWhitespace(chars[at - 1]) { return Text.trimEnd(value.take(at)) }102 at = at + 1103 }104 value105}106