///|
/// The kind of a token in a cucumber expression.
pub(all) enum TokenType {
StartOfLine
EndOfLine
WhiteSpace
BeginOptional
EndOptional
BeginParameter
EndParameter
Alternation
Text
} derive(Debug, Eq, ToJson, FromJson)
///|
/// A token of a cucumber expression.
///
/// `start` and `end` are code point offsets into the expression. For a text
/// token, the range includes the escape characters, but `text` does not.
pub(all) struct Token {
type_ : TokenType
text : String
start : Int
end : Int
} derive(Debug, Eq, ToJson, FromJson)
///|
let escape_character : Char = '\\'
///|
fn can_escape(ch : Char) -> Bool {
match ch {
' ' | '\\' | '/' | '{' | '}' | '(' | ')' => true
_ => false
}
}
///|
fn token_type_of(ch : Char) -> TokenType {
match ch {
' ' => WhiteSpace
'/' => Alternation
'{' => BeginParameter
'}' => EndParameter
'(' => BeginOptional
')' => EndOptional
_ => Text
}
}
///|
/// The character a token type stands for, or "" for other token types.
fn symbol_of(type_ : TokenType) -> String {
match type_ {
BeginOptional => "("
EndOptional => ")"
BeginParameter => "{"
EndParameter => "}"
Alternation => "/"
_ => ""
}
}
///|
/// What a token type is used for, as text for error messages.
fn purpose_of(type_ : TokenType) -> String {
match type_ {
BeginOptional | EndOptional => "optional text"
BeginParameter | EndParameter => "a parameter"
Alternation => "alternation"
_ => ""
}
}
///|
/// Tokenize a cucumber expression.
///
/// The result starts with a `StartOfLine` token and ends with an `EndOfLine`
/// token. Adjacent text and adjacent white space join into one token. Only a
/// space is white space.
pub fn tokenize(expression : String) -> Array[Token] raise ExpressionError {
let chars = expression.to_array()
let tokens : Array[Token] = []
let buffer = StringBuilder()
let mut buffer_length = 0
let mut buffer_start = 0
let mut previous_type : TokenType = StartOfLine
let mut treat_as_text = false
let mut escaped = 0
fn flush(type_ : TokenType) -> Unit {
let mut escape_tokens = 0
if type_ == Text {
escape_tokens = escaped
escaped = 0
}
let end = buffer_start + buffer_length + escape_tokens
tokens.push({ type_, text: buffer.to_string(), start: buffer_start, end, })
buffer.reset()
buffer_length = 0
buffer_start = end
}
if chars.is_empty() {
tokens.push({ type_: StartOfLine, text: "", start: 0, end: 0, })
}
for ch in chars {
if !treat_as_text && ch == escape_character {
escaped = escaped + 1
treat_as_text = true
continue
}
let current_type : TokenType = if treat_as_text {
if !can_escape(ch) {
raise cant_escape_error(
expression,
buffer_start + buffer_length + escaped,
)
}
Text
} else {
token_type_of(ch)
}
treat_as_text = false
let new_token = current_type != previous_type ||
(current_type != WhiteSpace && current_type != Text)
if new_token {
flush(previous_type)
}
previous_type = current_type
buffer.write_char(ch)
buffer_length = buffer_length + 1
}
if buffer_length > 0 {
flush(previous_type)
}
if treat_as_text {
raise end_of_line_escaped_error(expression)
}
tokens.push({
type_: EndOfLine,
text: "",
start: chars.length(),
end: chars.length(),
})
tokens
}
///|
pub extend TokenType with ToJson::{to_json}
///|
pub extend TokenType with @json.FromJson::{from_json}
///|
#deprecated
pub extend TokenType with Eq::{not_equal, equal}
///|
#deprecated
pub extend TokenType with @debug.Debug::{to_repr}
///|
pub extend Token with ToJson::{to_json}
///|
pub extend Token with @json.FromJson::{from_json}
///|
#deprecated
pub extend Token with Eq::{not_equal, equal}
///|
#deprecated
pub extend Token with @debug.Debug::{to_repr}