This commit is contained in:
		
							
								
								
									
										273
									
								
								src/glxml.gleam
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										273
									
								
								src/glxml.gleam
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,273 @@
 | 
			
		||||
import gleam/result
 | 
			
		||||
 | 
			
		||||
pub type Declaration {
 | 
			
		||||
  Declaration(versioninfo: String, encoding: String)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub type DocType {
 | 
			
		||||
  None
 | 
			
		||||
  DocType(name: String)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub type Document {
 | 
			
		||||
  Document(decl: Declaration, doctype: DocType)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn main() {
 | 
			
		||||
  parse_document("<?xml version=\"1.1\" encoding='UTF-8'?>") |> echo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn parse_document(doc: String) -> Result(Document, Nil) {
 | 
			
		||||
  use #(decl, doctype, _doc) <- result.try(parse_prolog(doc))
 | 
			
		||||
 | 
			
		||||
  Ok(Document(decl, doctype))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn parse_prolog(doc: String) -> Result(#(Declaration, DocType, String), Nil) {
 | 
			
		||||
  use #(decl, doc) <- result.try(parse_decl(doc))
 | 
			
		||||
 | 
			
		||||
  Ok(#(decl, None, doc))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn parse_decl(doc: String) -> Result(#(Declaration, String), Nil) {
 | 
			
		||||
  case doc {
 | 
			
		||||
    "<?xml" <> tail -> {
 | 
			
		||||
      use #(versioninfo, doc) <- result.try(parse_versioninfo(tail))
 | 
			
		||||
      let #(encoding, doc) = case parse_encodingdecl(doc) {
 | 
			
		||||
        Ok(e) -> e
 | 
			
		||||
        Error(_) -> #("", doc)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      case trim_space(doc) {
 | 
			
		||||
        "?>" <> tail -> Ok(#(Declaration(versioninfo:, encoding:), tail))
 | 
			
		||||
        _ -> Error(Nil)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    _ -> Error(Nil)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn parse_versioninfo(doc: String) -> Result(#(String, String), Nil) {
 | 
			
		||||
  use #(_, doc) <- result.try(parse_space(doc))
 | 
			
		||||
  case doc {
 | 
			
		||||
    "version=" <> tail -> {
 | 
			
		||||
      use #(version, doc) <- result.try(parse_version(tail))
 | 
			
		||||
      Ok(#(version, doc))
 | 
			
		||||
    }
 | 
			
		||||
    _ -> Error(Nil)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn parse_version(doc: String) -> Result(#(String, String), Nil) {
 | 
			
		||||
  case doc {
 | 
			
		||||
    "\"1." <> tail -> {
 | 
			
		||||
      use #(version, doc) <- result.try(do_parse_version(tail, "1."))
 | 
			
		||||
      case doc {
 | 
			
		||||
        "\"" <> tail -> Ok(#(version, tail))
 | 
			
		||||
        _ -> Error(Nil)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    "'1." <> tail -> {
 | 
			
		||||
      use #(version, doc) <- result.try(do_parse_version(tail, "1."))
 | 
			
		||||
      case doc {
 | 
			
		||||
        "'" <> tail -> Ok(#(version, tail))
 | 
			
		||||
        _ -> Error(Nil)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    _ -> Error(Nil)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn do_parse_version(
 | 
			
		||||
  doc: String,
 | 
			
		||||
  version: String,
 | 
			
		||||
) -> Result(#(String, String), Nil) {
 | 
			
		||||
  case do_parse_digit(doc) {
 | 
			
		||||
    Ok(#(digit, doc)) -> do_parse_version(doc, version <> digit)
 | 
			
		||||
    Error(_) if version == "" -> Error(Nil)
 | 
			
		||||
    Error(_) -> Ok(#(version, doc))
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn parse_encodingdecl(doc: String) -> Result(#(String, String), Nil) {
 | 
			
		||||
  use #(_, doc) <- result.try(parse_space(doc))
 | 
			
		||||
 | 
			
		||||
  case doc {
 | 
			
		||||
    "encoding=" <> tail -> {
 | 
			
		||||
      case tail {
 | 
			
		||||
        "\"" <> tail -> {
 | 
			
		||||
          use #(encoding, doc) <- result.try(parse_encoding(tail))
 | 
			
		||||
          case doc {
 | 
			
		||||
            "\"" <> tail -> Ok(#(encoding, tail))
 | 
			
		||||
            _ -> Error(Nil)
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        "'" <> tail -> {
 | 
			
		||||
          use #(encoding, doc) <- result.try(parse_encoding(tail))
 | 
			
		||||
          case doc {
 | 
			
		||||
            "'" <> tail -> Ok(#(encoding, tail))
 | 
			
		||||
            _ -> Error(Nil)
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        _ -> Error(Nil)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    _ -> Error(Nil)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn parse_encoding(doc: String) -> Result(#(String, String), Nil) {
 | 
			
		||||
  case do_parse_alpha(doc) {
 | 
			
		||||
    Ok(#(char, doc)) -> {
 | 
			
		||||
      Ok(parse_multiple_optional(
 | 
			
		||||
        doc,
 | 
			
		||||
        try_parsers(
 | 
			
		||||
          [
 | 
			
		||||
            do_parse_alpha,
 | 
			
		||||
            do_parse_digit,
 | 
			
		||||
            fn(doc) {
 | 
			
		||||
              case doc {
 | 
			
		||||
                "." as char <> tail | "_" as char <> tail | "-" as char <> tail ->
 | 
			
		||||
                  Ok(#(char, tail))
 | 
			
		||||
                _ -> Error(Nil)
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
          _,
 | 
			
		||||
        ),
 | 
			
		||||
        char,
 | 
			
		||||
      ))
 | 
			
		||||
    }
 | 
			
		||||
    Error(_) -> Error(Nil)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn do_parse_digit(doc: String) -> Result(#(String, String), Nil) {
 | 
			
		||||
  case doc {
 | 
			
		||||
    "0" as digit <> tail
 | 
			
		||||
    | "1" as digit <> tail
 | 
			
		||||
    | "2" as digit <> tail
 | 
			
		||||
    | "3" as digit <> tail
 | 
			
		||||
    | "4" as digit <> tail
 | 
			
		||||
    | "5" as digit <> tail
 | 
			
		||||
    | "6" as digit <> tail
 | 
			
		||||
    | "7" as digit <> tail
 | 
			
		||||
    | "8" as digit <> tail
 | 
			
		||||
    | "9" as digit <> tail -> Ok(#(digit, tail))
 | 
			
		||||
    _ -> Error(Nil)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn do_parse_alpha(doc: String) -> Result(#(String, String), Nil) {
 | 
			
		||||
  case doc {
 | 
			
		||||
    "a" as char <> tail
 | 
			
		||||
    | "b" as char <> tail
 | 
			
		||||
    | "c" as char <> tail
 | 
			
		||||
    | "d" as char <> tail
 | 
			
		||||
    | "e" as char <> tail
 | 
			
		||||
    | "f" as char <> tail
 | 
			
		||||
    | "g" as char <> tail
 | 
			
		||||
    | "h" as char <> tail
 | 
			
		||||
    | "i" as char <> tail
 | 
			
		||||
    | "j" as char <> tail
 | 
			
		||||
    | "k" as char <> tail
 | 
			
		||||
    | "l" as char <> tail
 | 
			
		||||
    | "m" as char <> tail
 | 
			
		||||
    | "n" as char <> tail
 | 
			
		||||
    | "o" as char <> tail
 | 
			
		||||
    | "p" as char <> tail
 | 
			
		||||
    | "q" as char <> tail
 | 
			
		||||
    | "r" as char <> tail
 | 
			
		||||
    | "s" as char <> tail
 | 
			
		||||
    | "t" as char <> tail
 | 
			
		||||
    | "u" as char <> tail
 | 
			
		||||
    | "v" as char <> tail
 | 
			
		||||
    | "w" as char <> tail
 | 
			
		||||
    | "x" as char <> tail
 | 
			
		||||
    | "y" as char <> tail
 | 
			
		||||
    | "z" as char <> tail
 | 
			
		||||
    | "A" as char <> tail
 | 
			
		||||
    | "B" as char <> tail
 | 
			
		||||
    | "C" as char <> tail
 | 
			
		||||
    | "D" as char <> tail
 | 
			
		||||
    | "E" as char <> tail
 | 
			
		||||
    | "F" as char <> tail
 | 
			
		||||
    | "G" as char <> tail
 | 
			
		||||
    | "H" as char <> tail
 | 
			
		||||
    | "I" as char <> tail
 | 
			
		||||
    | "J" as char <> tail
 | 
			
		||||
    | "K" as char <> tail
 | 
			
		||||
    | "L" as char <> tail
 | 
			
		||||
    | "M" as char <> tail
 | 
			
		||||
    | "N" as char <> tail
 | 
			
		||||
    | "O" as char <> tail
 | 
			
		||||
    | "P" as char <> tail
 | 
			
		||||
    | "Q" as char <> tail
 | 
			
		||||
    | "R" as char <> tail
 | 
			
		||||
    | "S" as char <> tail
 | 
			
		||||
    | "T" as char <> tail
 | 
			
		||||
    | "U" as char <> tail
 | 
			
		||||
    | "V" as char <> tail
 | 
			
		||||
    | "W" as char <> tail
 | 
			
		||||
    | "X" as char <> tail
 | 
			
		||||
    | "Y" as char <> tail
 | 
			
		||||
    | "Z" as char <> tail -> Ok(#(char, tail))
 | 
			
		||||
    _ -> Error(Nil)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn trim_space(doc: String) -> String {
 | 
			
		||||
  case parse_space(doc) {
 | 
			
		||||
    Ok(#(_, doc)) -> trim_space(doc)
 | 
			
		||||
    Error(_) -> doc
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn parse_space(doc: String) -> Result(#(String, String), Nil) {
 | 
			
		||||
  case doc {
 | 
			
		||||
    " " as ws <> tail
 | 
			
		||||
    | "\t" as ws <> tail
 | 
			
		||||
    | "\n" as ws <> tail
 | 
			
		||||
    | "\r" as ws <> tail -> Ok(#(ws, tail))
 | 
			
		||||
    _ -> Error(Nil)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn try_parsers(
 | 
			
		||||
  over list: List(fn(String) -> Result(#(a, String), Nil)),
 | 
			
		||||
  against static_data: String,
 | 
			
		||||
) -> Result(#(a, String), Nil) {
 | 
			
		||||
  case list {
 | 
			
		||||
    [] -> Error(Nil)
 | 
			
		||||
    [first, ..rest] ->
 | 
			
		||||
      case first(static_data) {
 | 
			
		||||
        Error(_) -> try_parsers(rest, static_data)
 | 
			
		||||
        Ok(r) -> Ok(r)
 | 
			
		||||
      }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn parse_multiple(
 | 
			
		||||
  to_parse str: String,
 | 
			
		||||
  with to_run: fn(String) -> Result(#(String, String), Nil),
 | 
			
		||||
) -> Result(#(String, String), Nil) {
 | 
			
		||||
  case parse_multiple_optional(str, to_run, "") {
 | 
			
		||||
    #("", _) -> Error(Nil)
 | 
			
		||||
    #(r, rest) -> Ok(#(r, rest))
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn parse_multiple_optional(
 | 
			
		||||
  to_parse str: String,
 | 
			
		||||
  with to_run: fn(String) -> Result(#(String, String), Nil),
 | 
			
		||||
  acc ret: String,
 | 
			
		||||
) -> #(String, String) {
 | 
			
		||||
  case str {
 | 
			
		||||
    "" -> #(ret, str)
 | 
			
		||||
    _ ->
 | 
			
		||||
      case to_run(str) {
 | 
			
		||||
        Ok(#(r, rest)) -> parse_multiple_optional(rest, to_run, ret <> r)
 | 
			
		||||
        Error(_) -> #(ret, str)
 | 
			
		||||
      }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user