/// https://github.com/alexkappa/mustache/blob/master/lex.go
///|
priv struct Token {
type_ : TokenType
value : String
line : Int
col : Int
} derive(Show)
///|
priv enum TokenType {
Error
EOF
Identifier
LeftDelim
RightDelim
Text
Comment
SectionStart
SectionInverse
SectionEnd
RawStart
RawEnd
RawAlt
Partial
SetDelim
} derive(Eq, Show)
///|
priv struct Lexer {
name : String // the name of the input; used only for error reports.
input : String // the string being scanned.
mut leftDelim : String // start of action.
mut rightDelim : String // end of action.
mut state : StateFn // the next lexing function to enter.
mut pos : Int // current position in the input.
mut start : Int // start position of this token.
tokens : Array[Token] // array of scanned tokens.
} derive(Show)
///|
fn Lexer::new(
name : String,
input : String,
leftDelim? : String = "{{",
rightDelim? : String = "}}",
) -> Lexer {
{
name,
input,
leftDelim,
rightDelim,
state: StateFn::Func(s => s.state_text()),
pos: 0,
start: 0,
tokens: [],
}
}
///|
priv enum StateFn {
Func((Lexer) -> StateFn)
None
}
///|
fn Lexer::state_text(self : Lexer) -> StateFn {
for {
if (try? self.input[self.pos:].has_prefix(self.leftDelim)).or(false) {
if self.pos > self.start {
self.emit(TokenType::Text)
}
return StateFn::Func(s => s.state_left_delim())
}
if self.next() == Rune::EOF {
break
}
}
if self.pos > self.start {
self.emit(TokenType::Text)
}
self.emit(TokenType::EOF)
return StateFn::None
}
///|
fn Lexer::state_left_delim(self : Lexer) -> StateFn {
self.seek(self.leftDelim.length())
if self.peek() == Rune::Char('=') {
ignore(self.next())
return StateFn::Func(s => s.state_set_delim())
}
self.emit(TokenType::LeftDelim)
StateFn::Func(s => s.state_tag())
}
///|
fn Lexer::state_right_delim(self : Lexer) -> StateFn {
self.seek(self.rightDelim.length())
self.emit(TokenType::RightDelim)
StateFn::Func(s => s.state_text())
}
///|
fn Lexer::ignore_(self : Lexer) -> Unit {
self.start = self.pos
}
///|
fn Lexer::state_set_delim(self : Lexer) -> StateFn {
let end = "=" + self.rightDelim
let endIndex = (try? self.input[self.pos:].find(end)).or(None)
match endIndex {
None => return self.error("Unterminated set delimiter")
Some(index) => {
let delimis = (try? self.input[self.pos:self.pos + index])
.or("")
.split(" ")
if delimis.count() < 2 {
self.error("set delimiters should be separated by a space")
} else {
let mut delimFn : DelimFn = DelimFn::Func((l, s) => l.left_fn(s))
for delim in delimis {
if delim != "" {
if not(delimFn.is_none_delim_fn()) {
delimFn = match delimFn {
DelimFn::Func(f) => f(self, delim.to_string())
DelimFn::None => self.right_fn(delim.to_string())
}
}
}
}
ignore(self.seek(index + end.length()))
self.ignore_()
self.emit(TokenType::SetDelim)
StateFn::Func(s => s.state_text())
}
}
}
}
///|
priv enum DelimFn {
Func((Lexer, String) -> DelimFn)
None
}
///|
fn DelimFn::is_none_delim_fn(self : DelimFn) -> Bool {
match self {
DelimFn::None => true
_ => false
}
}
///|
fn Lexer::left_fn(self : Lexer, s : String) -> DelimFn {
self.leftDelim = s
DelimFn::Func((l, s) => l.right_fn(s))
}
///|
fn Lexer::right_fn(self : Lexer, s : String) -> DelimFn {
self.rightDelim = s
DelimFn::None
}
///|
fn Lexer::state_tag(self : Lexer) -> StateFn {
if (try? self.input[self.pos:].has_prefix("}" + self.rightDelim)).or(false) {
ignore(self.seek(1))
self.emit(TokenType::RawEnd)
return StateFn::Func(s => s.state_right_delim())
}
if (try? self.input[self.pos:].has_prefix(self.rightDelim)).or(false) {
return StateFn::Func(s => s.state_right_delim())
}
match self.next() {
r if r.is_whitespaces() => {
self.ignore_()
StateFn::Func(s => s.state_tag())
}
Rune::Char(char) =>
match char {
'!' => {
self.emit(TokenType::Comment)
StateFn::Func(s => s.state_comment())
}
'#' => {
self.emit(TokenType::SectionStart)
StateFn::Func(s => s.state_tag())
}
'^' => {
self.emit(TokenType::SectionInverse)
StateFn::Func(s => s.state_tag())
}
'/' => {
self.emit(TokenType::SectionEnd)
return StateFn::Func(s => s.state_tag())
}
'>' => {
self.emit(TokenType::Partial)
StateFn::Func(s => s.state_tag())
}
'{' => {
self.emit(TokenType::RawStart)
StateFn::Func(s => s.state_tag())
}
'&' => {
self.emit(TokenType::RawAlt)
StateFn::Func(s => s.state_tag())
}
'\n' => self.error("unclosed actin")
c =>
if Rune::Char(c).is_alphanum() {
self.backup()
StateFn::Func(s => s.state_indent())
} else {
self.error("unexpected character in tag: " + char.to_string())
}
}
EOF => return self.error("unclosed action")
}
}
///|
fn Lexer::state_indent(self : Lexer) -> StateFn {
for {
let r = self.next()
if not(r.is_alphanum()) {
self.backup()
self.emit(TokenType::Identifier)
break
}
}
return StateFn::Func(s => s.state_tag())
}
///|
fn Lexer::state_comment(self : Lexer) -> StateFn {
let indexOfCommentEnd = (try? self.input[self.pos:].find(self.rightDelim)).or(
None,
)
match indexOfCommentEnd {
None => self.error("unclosed tag for comment")
Some(index) => {
ignore(self.seek(index))
self.emit(TokenType::Text)
StateFn::Func(s => s.state_right_delim())
}
}
}
///|
fn Rune::is_whitespaces(self : Rune) -> Bool {
match self {
Rune::Char(' ' | '\t' | '\n' | '\r') => true
_ => false
}
}
///|
fn Rune::is_alphanum(self : Rune) -> Bool {
match self {
Rune::Char('_' | '.') => true
Rune::Char(c) if c.is_ascii_digit() => true
Rune::Char(c) if c.is_ascii_alphabetic() => true
_ => false
}
}
///|
impl Show for StateFn with output(_ : StateFn, logger : &Logger) {
logger.write_string("StateFn")
}
///|
priv enum Rune {
EOF
Char(Char)
} derive(Eq)
///|
fn Lexer::next(self : Lexer) -> Rune {
if self.pos >= self.input.length() {
return Rune::EOF
} else {
let c = self.input.get_char(self.pos)
match c {
None => return Rune::EOF
Some(c) => {
self.pos += 1
return Rune::Char(c)
}
}
}
}
///|
fn Lexer::seek(self : Lexer, n : Int) -> Unit {
self.pos += n
}
///|
fn Lexer::backup(self : Lexer) -> Unit {
if self.pos > 0 {
self.pos -= 1
}
}
///|
fn Lexer::peek(self : Lexer) -> Rune {
let r = self.next()
self.backup()
r
}
///|
fn Lexer::emit(self : Lexer, t : TokenType) -> Unit {
let token = {
type_: t,
value: (try? self.input[self.start:self.pos].to_string()).or(""),
line: self.line_num(),
col: self.column_num(),
}
self.tokens.push(token)
self.start = self.pos
}
///|
fn Lexer::error(self : Lexer, msg : String) -> StateFn {
let token = {
type_: TokenType::Error,
value: msg,
line: self.line_num(),
col: self.column_num(),
}
self.tokens.push(token)
StateFn::None
}
///|
fn Lexer::line_num(self : Lexer) -> Int {
(try? self.input[:self.pos].split("\n").count()).or(0)
}
///|
test {
let input = "hello\nworld"
let lexer = {
name: "test",
input,
leftDelim: "{{",
rightDelim: "}}",
state: StateFn::None,
pos: 0,
start: 0,
tokens: [],
}
lexer.seek(5)
println(lexer) // move to the end of "hello";
assert_eq(lexer.line_num(), 1)
lexer.seek(6) // move to the start of "world"
assert_eq(lexer.line_num(), 2)
}
///|
fn Lexer::column_num(self : Lexer) -> Int {
let last_lf = (try? self.input[:self.pos].rev_find("\n")).or(None)
match last_lf {
Some(lf) => {
if lf == self.pos {
return 0
}
(try? self.input[lf + 1:self.pos].length()).or(0)
}
None => (try? self.input[:self.pos].length()).or(0)
}
}
///|
test {
let input = "hello\nworld"
let lexer = {
name: "test",
input,
leftDelim: "{{",
rightDelim: "}}",
state: None,
pos: 0,
start: 0,
tokens: [],
}
lexer.seek(5) // move to the end of "hello"
assert_eq(lexer.column_num(), 5)
lexer.seek(1) // move to the start of "\n"
assert_eq(lexer.column_num(), 0)
lexer.seek(1) // move to the start of "world"
assert_eq(lexer.column_num(), 1)
}
///|
test {
let greeting = "hello, 世界"
for k in greeting.iter() {
println("\{k}")
}
println("---")
println(greeting.get_char(7)) // prints 'h'
}
///|
test {
let lexer = {
name: "test",
input: "hello, 世界",
leftDelim: "{{",
rightDelim: "}}",
state: None,
pos: 0,
start: 0,
tokens: [],
}
while true {
let c = lexer.next()
match c {
Rune::EOF => break
Rune::Char(ch) => println("Read character: \{ch}")
}
}
}
///|
fn Lexer::token(self : Lexer) -> Token {
for {
let token = if self.tokens.is_empty() {
Option::None
} else {
self.tokens.drain(0, 1).pop()
}
match token {
Some(t) => return t
None =>
self.state = match self.state {
StateFn::Func(f) => f(self)
StateFn::None => None
}
}
}
}
///|
fn Lexer::tokens(self : Lexer) -> Array[Token] raise Error {
let tokens = Array::new()
for {
let token = self.token()
match token.type_ {
TokenType::EOF => break
TokenType::Error => fail("Lexer error: " + token.value)
_ => tokens.push(token)
}
}
tokens
}
///|
test {
let input = "hello {{world}}"
let lexer = Lexer::new("test", input)
let expected = [
(TokenType::Text, "hello "),
(TokenType::LeftDelim, "{{"),
(TokenType::Identifier, "world"),
(TokenType::RightDelim, "}}"),
]
assert_eq(lexer.tokens().map(t => (t.type_, t.value)), expected)
}
///|
test {
let input = "hello\n{{world}}"
let lexer = Lexer::new("test", input)
let expected = [
(TokenType::Text, "hello\n"),
(TokenType::LeftDelim, "{{"),
(TokenType::Identifier, "world"),
(TokenType::RightDelim, "}}"),
]
assert_eq(lexer.tokens().map(t => (t.type_, t.value)), expected)
}
///|
test {
let input = "foo {{{bar}}}\nbaz {{! this is ignored }}"
let lexer = Lexer::new("test", input)
let expected = [
(TokenType::Text, "foo "),
(TokenType::LeftDelim, "{{"),
(TokenType::RawStart, "{"),
(TokenType::Identifier, "bar"),
(TokenType::RawEnd, "}"),
(TokenType::RightDelim, "}}"),
(TokenType::Text, "\nbaz "),
(TokenType::LeftDelim, "{{"),
(TokenType::Comment, "!"),
(TokenType::Text, " this is ignored "),
(TokenType::RightDelim, "}}"),
]
assert_eq(lexer.tokens().map(t => (t.type_, t.value)), expected)
}
///|
test {
let input = "\nfoo {{bar}} baz {{=| |=}}\r\n |foo| |={{ }}=| {{bar}}"
let lexer = Lexer::new("test", input)
let expected = [
(TokenType::Text, "\nfoo "),
(TokenType::LeftDelim, "{{"),
(TokenType::Identifier, "bar"),
(TokenType::RightDelim, "}}"),
(TokenType::Text, " baz "),
(TokenType::SetDelim, ""),
(TokenType::Text, "\r\n "),
(TokenType::LeftDelim, "|"),
(TokenType::Identifier, "foo"),
(TokenType::RightDelim, "|"),
(TokenType::Text, " "),
(TokenType::SetDelim, ""),
(TokenType::Text, " "),
(TokenType::LeftDelim, "{{"),
(TokenType::Identifier, "bar"),
(TokenType::RightDelim, "}}"),
]
assert_eq(lexer.tokens().map(t => (t.type_, t.value)), expected)
}
///|
test {
let input = "{{=| |=}}\r\n"
let lexer = Lexer::new("test", input)
let expected = [(TokenType::SetDelim, ""), (TokenType::Text, "\r\n")]
assert_eq(lexer.tokens().map(t => (t.type_, t.value)), expected)
}
///|
test {
// TODO: 落ちるテスト改行の扱いがバグっている
let input =
#|Begin.
#|{{! Comment Block! }}
#|End.
let lexer = Lexer::new("test", input)
assert_eq(lexer.tokens().map(t => (t.type_, t.value)), [
(Text, "Begin.\n"),
(LeftDelim, "{{"),
(Comment, "!"),
(Text, " Comment Block! "),
(RightDelim, "}}"),
(Text, "End."),
])
}