// Primitive parsers — the basic building blocks
///|
pub fn tag(expected : String, input : ParseInput) -> (ParseInput, String)? {
let len = expected.length()
if input.pos + len > input.source.length() {
return None
}
if input.source.substring(start=input.pos, end=input.pos + len) == expected {
Some((input.advance_by(len), expected))
} else {
None
}
}
///|
pub fn tag_ci(expected : String, input : ParseInput) -> (ParseInput, String)? {
let len = expected.length()
if input.pos + len > input.source.length() {
return None
}
let actual = input.source.substring(start=input.pos, end=input.pos + len)
if actual.to_lower() == expected.to_lower() {
Some((input.advance_by(len), actual))
} else {
None
}
}
///|
pub fn any_char(input : ParseInput) -> (ParseInput, String)? {
if input.is_eof() {
return None
}
let ch = input.current()
Some((input.advance(), ch))
}
///|
pub fn satisfy(
pred : (String) -> Bool,
input : ParseInput,
) -> (ParseInput, String)? {
if input.is_eof() {
return None
}
let ch = input.current()
if pred(ch) {
Some((input.advance(), ch))
} else {
None
}
}
///|
pub fn one_of(chars : String, input : ParseInput) -> (ParseInput, String)? {
satisfy(fn(ch : String) -> Bool { chars.contains(ch) }, input)
}
///|
pub fn none_of(chars : String, input : ParseInput) -> (ParseInput, String)? {
satisfy(fn(ch : String) -> Bool { chars.contains(ch) == false }, input)
}
///|
pub fn digit(input : ParseInput) -> (ParseInput, String)? {
satisfy(is_digit, input)
}
///|
pub fn hex_digit(input : ParseInput) -> (ParseInput, String)? {
satisfy(is_hex, input)
}
///|
pub fn alpha(input : ParseInput) -> (ParseInput, String)? {
satisfy(is_alpha, input)
}
///|
pub fn alphanumeric(input : ParseInput) -> (ParseInput, String)? {
satisfy(fn(ch : String) -> Bool { is_alpha(ch) || is_digit(ch) }, input)
}
///|
pub fn space(input : ParseInput) -> (ParseInput, String)? {
satisfy(is_space, input)
}
///|
pub fn space0(input : ParseInput) -> ParseInput {
skip_while(is_space, input)
}
///|
pub fn space1(input : ParseInput) -> (ParseInput, String)? {
many1_space(input, 0, "")
}
///|
fn many1_space(
input : ParseInput,
count : Int,
result : String,
) -> (ParseInput, String)? {
if input.is_eof() {
if count > 0 {
Some((input, result))
} else {
None
}
} else {
let ch = input.current()
if is_space(ch) {
many1_space(input.advance(), count + 1, result + ch)
} else if count > 0 {
Some((input, result))
} else {
None
}
}
}
///|
pub fn multispace0(input : ParseInput) -> ParseInput {
skip_while(is_multispace, input)
}
///|
pub fn multispace1(input : ParseInput) -> (ParseInput, String)? {
many1_multispace(input, 0, "")
}
///|
fn many1_multispace(
input : ParseInput,
count : Int,
result : String,
) -> (ParseInput, String)? {
if input.is_eof() {
if count > 0 {
Some((input, result))
} else {
None
}
} else {
let ch = input.current()
if is_multispace(ch) {
many1_multispace(input.advance(), count + 1, result + ch)
} else if count > 0 {
Some((input, result))
} else {
None
}
}
}
///|
pub fn newline(input : ParseInput) -> (ParseInput, String)? {
alt(tag_fn("\r\n"), tag_fn("\n"), input)
}
///|
pub fn eof(input : ParseInput) -> (ParseInput, String)? {
if input.is_eof() {
Some((input, ""))
} else {
None
}
}
///|
pub fn take_while(
pred : (String) -> Bool,
input : ParseInput,
) -> (ParseInput, String) {
take_while_acc(pred, input, input.pos, "")
}
///|
fn take_while_acc(
pred : (String) -> Bool,
input : ParseInput,
start : Int,
result : String,
) -> (ParseInput, String) {
if input.is_eof() {
(input, result)
} else {
let ch = input.current()
if pred(ch) {
take_while_acc(pred, input.advance(), start, result + ch)
} else {
(input, result)
}
}
}
///|
pub fn take_while1(
pred : (String) -> Bool,
input : ParseInput,
) -> (ParseInput, String)? {
let (rest, result) = take_while(pred, input)
if result == "" {
None
} else {
Some((rest, result))
}
}
///|
pub fn take_until(
tag_str : String,
input : ParseInput,
) -> (ParseInput, String)? {
take_until_acc(tag_str, input, input.pos, "")
}
///|
fn take_until_acc(
tag_str : String,
input : ParseInput,
start : Int,
result : String,
) -> (ParseInput, String)? {
if input.is_eof() {
None
} else if input.source.substring(
start=input.pos,
end=input.pos + tag_str.length(),
) ==
tag_str {
Some((input, result))
} else {
let ch = input.current()
take_until_acc(tag_str, input.advance(), start, result + ch)
}
}
///|
pub fn[T] peek(
parser : (ParseInput) -> (ParseInput, T)?,
input : ParseInput,
) -> (ParseInput, T)? {
parser(input)
}
///|
pub fn rest(input : ParseInput) -> (ParseInput, String)? {
let remaining = input.remaining()
if remaining == "" {
None
} else {
Some((ParseInput::{ ..input, pos: input.source.length() }, remaining))
}
}
// Helper functions
///|
fn tag_fn(expected : String) -> (ParseInput) -> (ParseInput, String)? {
fn(input : ParseInput) -> (ParseInput, String)? { tag(expected, input) }
}
///|
fn is_digit(ch : String) -> Bool {
ch >= "0" && ch <= "9"
}
///|
fn is_hex(ch : String) -> Bool {
is_digit(ch) || (ch >= "a" && ch <= "f") || (ch >= "A" && ch <= "F")
}
///|
fn is_alpha(ch : String) -> Bool {
(ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z")
}
///|
fn is_space(ch : String) -> Bool {
ch == " " || ch == "\t"
}
///|
fn is_multispace(ch : String) -> Bool {
is_space(ch) || ch == "\n" || ch == "\r"
}
///|
pub fn skip_while(pred : (String) -> Bool, input : ParseInput) -> ParseInput {
if input.is_eof() {
input
} else {
let ch = input.current()
if pred(ch) {
skip_while(pred, input.advance())
} else {
input
}
}
}
///|
pub fn[T] alt(
p1 : (ParseInput) -> (ParseInput, T)?,
p2 : (ParseInput) -> (ParseInput, T)?,
input : ParseInput,
) -> (ParseInput, T)? {
match p1(input) {
Some((rest, val)) => Some((rest, val))
None => p2(input)
}
}
///|
pub fn[T] opt(
parser : (ParseInput) -> (ParseInput, T)?,
input : ParseInput,
) -> (ParseInput, String)? {
match parser(input) {
Some((rest, _)) => Some((rest, ""))
None => Some((input, ""))
}
}