///|
priv struct Parser {
source : String
mut position : Int
}
///|
fn Parser::new(source : String) -> Parser {
{ source, position: 0 }
}
///|
fn Parser::peek(self : Parser) -> Char? {
self.source.view().get_char(self.position)
}
///|
fn Parser::advance(self : Parser) -> Unit {
self.position = self.position + 1
}
///|
fn Parser::skip_space(self : Parser) -> Unit {
while self.peek() is Some(ch) && ch.is_whitespace() {
self.advance()
}
}
///|
fn Parser::token(self : Parser) -> String {
let out = StringBuilder::new()
while self.peek() is Some(ch) &&
!ch.is_whitespace() &&
ch != ',' &&
ch != '{' &&
ch != '}' {
out.write_char(ch)
self.advance()
}
out.to_string()
}
///|
fn is_ascii_digit(ch : Char) -> Bool {
ch >= '0' && ch <= '9'
}
///|
fn is_exact_selector(selector : String) -> Bool {
if !selector.has_prefix("=") {
return false
}
if selector.length() < 2 {
return false
}
let mut has_digit = false
for index, ch in selector {
if index == 0 {
continue
}
if index == 1 && ch == '-' {
continue
}
if !is_ascii_digit(ch) {
return false
}
has_digit = true
}
has_digit
}
///|
fn is_plural_category(selector : String) -> Bool {
selector == "zero" ||
selector == "one" ||
selector == "two" ||
selector == "few" ||
selector == "many" ||
selector == "other"
}
///|
fn validate_selector(
kind : String,
selector : String,
position : Int,
) -> Unit raise MessageError {
if kind != "select" &&
!is_plural_category(selector) &&
!is_exact_selector(selector) {
raise ParseError(
position~,
message="Invalid plural selector '\{selector}'.",
)
}
}
///|
fn Parser::expect(self : Parser, expected : Char) -> Unit raise MessageError {
match self.peek() {
Some(ch) if ch == expected => self.advance()
_ =>
raise ParseError(
position=self.position,
message="Expected '\{expected}'.",
)
}
}
///|
fn Parser::flush_text(nodes : Array[MessageNode], text : StringBuilder) -> Unit {
if !text.is_empty() {
nodes.push(TextNode(text.to_string()))
text.reset()
}
}
///|
fn Parser::nodes_until(
self : Parser,
close? : Char,
) -> Array[MessageNode] raise MessageError {
let nodes : Array[MessageNode] = []
let text = StringBuilder::new()
while self.peek() is Some(ch) {
match close {
Some(end) if ch == end => {
Parser::flush_text(nodes, text)
self.advance()
return nodes
}
_ => ()
}
match ch {
'{' => {
Parser::flush_text(nodes, text)
self.advance()
nodes.push(self.braced())
}
'#' => {
Parser::flush_text(nodes, text)
nodes.push(PoundNode)
self.advance()
}
'\\' => {
self.advance()
match self.peek() {
Some(escaped) => {
text.write_char(escaped)
self.advance()
}
None =>
raise ParseError(position=self.position, message="Dangling escape.")
}
}
_ => {
text.write_char(ch)
self.advance()
}
}
}
Parser::flush_text(nodes, text)
match close {
Some(_) =>
raise ParseError(
position=self.position,
message="Unclosed message branch.",
)
None => nodes
}
}
///|
fn Parser::braced(self : Parser) -> MessageNode raise MessageError {
self.skip_space()
let name = self.token()
if name == "" {
raise ParseError(position=self.position, message="Expected argument name.")
}
self.skip_space()
match self.peek() {
Some('}') => {
self.advance()
ArgumentNode(name)
}
Some(',') => {
self.advance()
self.skip_space()
let kind = self.token()
if kind == "selectorordinal" {
raise ParseError(
position=self.position,
message="Did you mean 'selectordinal' instead of 'selectorordinal'?",
)
}
if kind != "plural" && kind != "selectordinal" && kind != "select" {
raise ParseError(
position=self.position,
message="Unsupported formatter '\{kind}'.",
)
}
self.skip_space()
self.expect(',')
let cases : Map[String, Array[MessageNode]] = Map([])
while self.peek() is Some(ch) && ch != '}' {
self.skip_space()
let selector_position = self.position
let selector = self.token()
if selector == "" {
raise ParseError(
position=self.position,
message="Expected choice selector.",
)
}
validate_selector(kind, selector, selector_position)
if cases.contains(selector) {
raise ParseError(
position=selector_position,
message="Duplicate selector '\{selector}'.",
)
}
self.skip_space()
self.expect('{')
cases[selector] = self.nodes_until(close='}')
self.skip_space()
}
self.expect('}')
if !cases.contains("other") {
raise ParseError(
position=self.position,
message="Choice needs an other branch.",
)
}
ChoiceNode(name, kind, cases)
}
_ =>
raise ParseError(position=self.position, message="Expected '}' or ','.")
}
}
///|
/// Parse a message template using the supported ICU MessageFormat subset.
pub fn parse_message(template : String) -> Message raise MessageError {
let parser = Parser::new(template)
{ nodes: parser.nodes_until() }
}