Add whitespace control options: trim_blocks, lstrip_blocks (#116)

This commit is contained in:
JP Cimalando
2019-09-08 16:03:31 +02:00
committed by pantor
parent ba2aa0477c
commit 66196fdd6b
3 changed files with 61 additions and 6 deletions
+3
View File
@@ -24,6 +24,9 @@ struct LexerConfig {
std::string comment_close {"#}"};
std::string open_chars {"#{"};
bool trim_blocks {false};
bool lstrip_blocks {false};
void update_open_chars() {
open_chars = "";
if (open_chars.find(line_statement[0]) == std::string::npos) {
+10
View File
@@ -77,6 +77,16 @@ class Environment {
m_impl->lexer_config.update_open_chars();
}
/// Sets whether to remove the first newline after a block
void set_trim_blocks(bool trim_blocks) {
m_impl->lexer_config.trim_blocks = trim_blocks;
}
/// Sets whether to strip the spaces and tabs from the start of a line to a block
void set_lstrip_blocks(bool lstrip_blocks) {
m_impl->lexer_config.lstrip_blocks = lstrip_blocks;
}
/// Sets the element notation syntax
void set_element_notation(ElementNotation notation) {
m_impl->parser_config.notation = notation;
+48 -6
View File
@@ -59,12 +59,15 @@ class Lexer {
// try to match one of the opening sequences, and get the close
nonstd::string_view open_str = m_in.substr(m_pos);
bool must_lstrip = false;
if (inja::string_view::starts_with(open_str, m_config.expression_open)) {
m_state = State::ExpressionStart;
} else if (inja::string_view::starts_with(open_str, m_config.statement_open)) {
m_state = State::StatementStart;
must_lstrip = m_config.lstrip_blocks;
} else if (inja::string_view::starts_with(open_str, m_config.comment_open)) {
m_state = State::CommentStart;
must_lstrip = m_config.lstrip_blocks;
} else if ((m_pos == 0 || m_in[m_pos - 1] == '\n') &&
inja::string_view::starts_with(open_str, m_config.line_statement)) {
m_state = State::LineStart;
@@ -72,8 +75,13 @@ class Lexer {
m_pos += 1; // wasn't actually an opening sequence
goto again;
}
if (m_pos == m_tok_start) goto again; // don't generate empty token
return make_token(Token::Kind::Text);
nonstd::string_view text = string_view::slice(m_in, m_tok_start, m_pos);
if (must_lstrip)
text = clear_final_line_if_whitespace(text);
if (text.empty()) goto again; // don't generate empty token
return Token(Token::Kind::Text, text);
}
case State::ExpressionStart: {
m_state = State::ExpressionBody;
@@ -100,7 +108,7 @@ class Lexer {
case State::LineBody:
return scan_body("\n", Token::Kind::LineStatementClose);
case State::StatementBody:
return scan_body(m_config.statement_close, Token::Kind::StatementClose);
return scan_body(m_config.statement_close, Token::Kind::StatementClose, m_config.trim_blocks);
case State::CommentBody: {
// fast-scan to comment close
size_t end = m_in.substr(m_pos).find(m_config.comment_close);
@@ -111,7 +119,10 @@ class Lexer {
// return the entire comment in the close token
m_state = State::Text;
m_pos += end + m_config.comment_close.size();
return make_token(Token::Kind::CommentClose);
Token tok = make_token(Token::Kind::CommentClose);
if (m_config.trim_blocks)
skip_newline();
return tok;
}
}
}
@@ -119,7 +130,7 @@ class Lexer {
const LexerConfig& get_config() const { return m_config; }
private:
Token scan_body(nonstd::string_view close, Token::Kind closeKind) {
Token scan_body(nonstd::string_view close, Token::Kind closeKind, bool trim = false) {
again:
// skip whitespace (except for \n as it might be a close)
if (m_tok_start >= m_in.size()) return make_token(Token::Kind::Eof);
@@ -133,7 +144,10 @@ class Lexer {
if (inja::string_view::starts_with(m_in.substr(m_tok_start), close)) {
m_state = State::Text;
m_pos = m_tok_start + close.size();
return make_token(closeKind);
Token tok = make_token(closeKind);
if (trim)
skip_newline();
return tok;
}
// skip \n
@@ -254,6 +268,34 @@ class Lexer {
Token make_token(Token::Kind kind) const {
return Token(kind, string_view::slice(m_in, m_tok_start, m_pos));
}
void skip_newline() {
if (m_pos < m_in.size()) {
char ch = m_in[m_pos];
if (ch == '\n')
m_pos += 1;
else if (ch == '\r') {
m_pos += 1;
if (m_pos < m_in.size() && m_in[m_pos] == '\n')
m_pos += 1;
}
}
}
static nonstd::string_view clear_final_line_if_whitespace(nonstd::string_view text)
{
nonstd::string_view result = text;
while (!result.empty()) {
char ch = result.back();
if (ch == ' ' || ch == '\t')
result.remove_suffix(1);
else if (ch == '\n' || ch == '\r')
break;
else
return text;
}
return result;
}
};
}