///|
const EMU_TAG = re"]*)>([^<]*)"

///|
const PRE_BLOCK = re"
(.*?)
" ///| /// Fetch the spec HTML and extract grammar terminals. async fn fetch_and_extract() -> (Array[String], Array[String]) { let (_response, body) = @http.get("https://webidl.spec.whatwg.org/") let html = body.text() extract_terminals(html) } ///| fn escape_str(s : String) -> String { let mut out = "" for c in s.iter() { if c == '"' { out = out + "\\\"" } else if c == '\\' { out = out + "\\\\" } else { out = out + c.to_string() } } out } ///| fn sort_by_length_desc(arr : Array[String]) -> Unit { arr.sort_by((a, b) => { let length_cmp = b.length().compare(a.length()) if length_cmp == 0 { a.compare(b) } else { length_cmp } }) } ///| fn generate_output(kw : Array[String], punc : Array[String]) -> String { fn build_lines(arr : Array[String]) -> String { match arr { [] => "" _ => arr.map(item => " \"" + escape_str(item) + "\"").join(",\n") + "\n" } } let kw_body = build_lines(kw) let punc_body = build_lines(punc) let head = #|/// Auto-generated by `cmd/build`. Do not edit manually. #|/// Literal terminals from the WebIDL grammar. #|/// Source: https://webidl.spec.whatwg.org/#idl-grammar #| #|///| #|/// Grammar keyword literals, sorted by length descending. #|let keywords : Array[String] = [ let mid = #|] #| #|///| #|/// Grammar punctuation literals, sorted by length descending. #|let punctuation : Array[String] = [ let tail = #|] #| #|///| #|/// Returns whether `text` is a grammar keyword literal. #|fn is_keyword(text : StringView) -> Bool { #| for kw in keywords { #| if text == kw[:] { #| return true #| } #| } #| false #|} #| #|///| #|/// Try to match a grammar punctuation literal at the start of `text`. #|fn match_punctuation(text : StringView) -> Int { #| for p in punctuation { #| if text.has_prefix(p[:]) { #| return p.length() #| } #| } #| 0 #|} head + kw_body + mid + punc_body + tail } ///| async fn main { println("Fetching the WebIDL specification; this may take a moment...") let (raw_kw, raw_punc) = fetch_and_extract() sort_by_length_desc(raw_kw) sort_by_length_desc(raw_punc) println("Writing lexer/literals.mbt...") @fs.write_file("lexer/literals.mbt", generate_output(raw_kw, raw_punc)) println("Generated lexer/literals.mbt.") } ///| /// Grammar terminals in the spec are inside
 blocks
/// as  tags. We use regex for both the pre blocks and emu-t tags.
fn extract_terminals(html : String) -> (Array[String], Array[String]) {
  let kw_set = @set.Set([])
  let punc_set = @set.Set([])
  let mut rem : StringView = html
  while true {
    match PRE_BLOCK.execute(rem) {
      None => break
      Some(m) => {
        if m.group(1) is Some(pre_body) {
          let mut inner : StringView = pre_body
          while true {
            match EMU_TAG.execute(inner) {
              None => break
              Some(m2) => {
                let is_regex = m2.group(1) is Some(attr) &&
                  attr.contains("regex")
                if !is_regex {
                  if m2.group(2) is Some(text) {
                    let decoded = if text == "<" {
                      "<"
                    } else if text == ">" {
                      ">"
                    } else {
                      text.to_owned()
                    }
                    if decoded.length() == 1 {
                      let c = decoded[0].to_int()
                      let is_punc = !((c >= 48 && c <= 57) ||
                        (c >= 65 && c <= 90) ||
                        (c >= 97 && c <= 122) ||
                        c == 95)
                      if is_punc {
                        punc_set.add(decoded)
                      } else {
                        kw_set.add(decoded)
                      }
                    } else if decoded.length() > 1 {
                      if decoded == "..." {
                        punc_set.add(decoded)
                      } else {
                        kw_set.add(decoded)
                      }
                    }
                  }
                }
                inner = m2.after()
              }
            }
          }
        }
        rem = m.after()
      }
    }
  }
  (kw_set.to_array(), punc_set.to_array())
}