#ifndef SASS_PARSER_H
#define SASS_PARSER_H
// sass.hpp must go before all system headers to get the
// __EXTENSIONS__ fix on Solaris.
#include "sass.hpp"
#include <string>
#include <vector>
#include "ast.hpp"
#include "position.hpp"
#include "context.hpp"
#include "position.hpp"
#include "prelexer.hpp"
#include "source.hpp"
#ifndef MAX_NESTING
// Note that this limit is not an exact science
// it depends on various factors, which some are
// not under our control (compile time or even OS
// dependent settings on the available stack size)
// It should fix most common segfault cases though.
#define MAX_NESTING 512
#endif
struct Lookahead {
const char* found;
const char* error;
const char* position;
bool parsable;
bool has_interpolants;
bool is_custom_property;
};
namespace Sass {
class Parser : public SourceSpan {
public:
enum Scope { Root, Mixin, Function, Media, Control, Properties, Rules, AtRoot };
Context& ctx;
sass::vector<Block_Obj> block_stack;
sass::vector<Scope> stack;
SourceDataObj source;
const char* begin;
const char* position;
const char* end;
Offset before_token;
Offset after_token;
SourceSpan pstate;
Backtraces traces;
size_t indentation;
size_t nestings;
bool allow_parent;
Token lexed;
Parser(SourceData* source, Context& ctx, Backtraces, bool allow_parent = true);
// special static parsers to convert strings into certain selectors
static SelectorListObj parse_selector(SourceData* source, Context& ctx, Backtraces, bool allow_parent = true);
#ifdef __clang__
// lex and peak uses the template parameter to branch on the action, which
// triggers clangs tautological comparison on the single-comparison
// branches. This is not a bug, just a merging of behaviour into
// one function
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wtautological-compare"
#endif
// skip current token and next whitespace
// moves SourceSpan right before next token
void advanceToNextToken();
bool peek_newline(const char* start = 0);
// skip over spaces, tabs and line comments
template <Prelexer::prelexer mx>
const char* sneak(const char* start = 0)
{
using namespace Prelexer;
// maybe use optional start position from arguments?
const char* it_position = start ? start : position;
// skip white-space?
if (mx == spaces ||
mx == no_spaces ||
mx == css_comments ||
mx == css_whitespace ||
mx == optional_spaces ||
mx == optional_css_comments ||
mx == optional_css_whitespace
) {
return it_position;
}
// skip over spaces, tabs and sass line comments
const char* pos = optional_css_whitespace(it_position);
// always return a valid position
return pos ? pos : it_position;
}
// match will not skip over space, tabs and line comment
// return the position where the lexer match will occur
template <Prelexer::prelexer mx>
const char* match(const char* start = 0)
{
// match the given prelexer
return mx(position);
}
// peek will only skip over space, tabs and line comment
// return the position where the lexer match will occur
template <Prelexer::prelexer mx>
const char* peek(const char* start = 0)
{
// sneak up to the actual token we want to lex
// this should skip over white-space if desired
const char* it_before_token = sneak < mx >(start);
// match the given prelexer
const char* match = mx(it_before_token);
// check if match is in valid range
return match <= end ? match : 0;
}
// white-space handling is built into the lexer
// this way you do not need to parse it yourself
// some matchers don't accept certain white-space
// we do not support start arg, since we manipulate
// sourcemap offset and we modify the position pointer!
// lex will only skip over space, tabs and line comment
template <Prelexer::prelexer mx>
const char* lex(bool lazy = true, bool force = false)
{
if (*position == 0) return 0;
// position considered before lexed token
// we can skip whitespace or comments for
// lazy developers (but we need control)
const char* it_before_token = position;
// sneak up to the actual token we want to lex
// this should skip over white-space if desired
if (lazy) it_before_token = sneak < mx >(position);
// now call matcher to get position after token
const char* it_after_token = mx(it_before_token);
// check if match is in valid range
if (it_after_token > end) return 0;
// maybe we want to update the parser state anyway?
if (force == false) {
// assertion that we got a valid match
if (it_after_token == 0) return 0;
// assertion that we actually lexed something
if (it_after_token == it_before_token) return 0;
}
// create new lexed token object (holds the parse results)
lexed = Token(position, it_before_token, it_after_token);
// advance position (add whitespace before current token)
before_token = after_token.add(position, it_before_token);
// update after_token position for current token
after_token.add(it_before_token, it_after_token);
// ToDo: could probably do this incremental on original object (API wants offset?)
pstate = SourceSpan(source, before_token, after_token - before_token);
// advance internal char iterator
return position = it_after_token;
}
// lex_css skips over space, tabs, line and block comment
// all block comments will be consumed and thrown away
// source-map position will point to token after the comment
template <Prelexer::prelexer mx>
const char* lex_css()
{
// copy old token
Token prev = lexed;
// store previous pointer
const char* oldpos = position;
Offset bt = before_token;
Offset at = after_token;
SourceSpan op = pstate;
// throw away comments
// update srcmap position
lex < Prelexer::css_comments >();
// now lex a new token
const char* pos = lex< mx >();
// maybe restore prev state
if (pos == 0) {
pstate = op;
lexed = prev;
position = oldpos;
after_token = at;
before_token = bt;
}
// return match
return pos;
}
// all block comments will be skipped and thrown away
template <Prelexer::prelexer mx>
const char* peek_css(const char* start = 0)
{
// now peek a token (skip comments first)
return peek< mx >(peek < Prelexer::css_comments >(start));
}
#ifdef __clang__
#pragma clang diagnostic pop
#endif
void error(sass::string msg);
// generate message with given and expected sample
// text before and in the middle are configurable
void css_error(const sass::string& msg,
const sass::string& prefix = " after ",
const sass::string& middle = ", was: ",
const bool trim = true);
void read_bom();
Block_Obj parse();
Import_Obj parse_import();
Definition_Obj parse_definition(Definition::Type which_type);
Parameters_Obj parse_parameters();
Parameter_Obj parse_parameter();
Mixin_Call_Obj parse_include_directive();
Arguments_Obj parse_arguments();
Argument_Obj parse_argument();
Assignment_Obj parse_assignment();
StyleRuleObj parse_ruleset(Lookahead lookahead);
SelectorListObj parseSelectorList(bool chroot);
ComplexSelectorObj parseComplexSelector(bool chroot);
Selector_Schema_Obj parse_selector_schema(const char* end_of_selector, bool chroot);
CompoundSelectorObj parseCompoundSelector();
SimpleSelectorObj parse_simple_selector();
PseudoSelectorObj parse_negated_selector2();
Expression* parse_binominal();
SimpleSelectorObj parse_pseudo_selector();
AttributeSelectorObj parse_attribute_selector();
Block_Obj parse_block(bool is_root = false);
Block_Obj parse_css_block(bool is_root = false);
bool parse_block_nodes(bool is_root = false);
bool parse_block_node(bool is_root = false);
Declaration_Obj parse_declaration();
ExpressionObj parse_map();
ExpressionObj parse_bracket_list();
ExpressionObj parse_list(bool delayed = false);
ExpressionObj parse_comma_list(bool delayed = false);
ExpressionObj parse_space_list();
ExpressionObj parse_disjunction();
ExpressionObj parse_conjunction();
ExpressionObj parse_relation();
ExpressionObj parse_expression();
ExpressionObj parse_operators();
ExpressionObj parse_factor();
ExpressionObj parse_value();
Function_Call_Obj parse_calc_function();
Function_Call_Obj parse_function_call();
Function_Call_Obj parse_function_call_schema();
String_Obj parse_url_function_string();
String_Obj parse_url_function_argument();
String_Obj parse_interpolated_chunk(Token, bool constant = false, bool css = true);
String_Obj parse_string();
ValueObj parse_static_value();
String_Schema_Obj parse_css_variable_value();
String_Obj parse_ie_property();
String_Obj parse_ie_keyword_arg();
String_Schema_Obj parse_value_schema(const char* stop);
String_Obj parse_identifier_schema();
If_Obj parse_if_directive(bool else_if = false);
ForRuleObj parse_for_directive();
EachRuleObj parse_each_directive();
WhileRuleObj parse_while_directive();
MediaRule_Obj parseMediaRule();
sass::vector<CssMediaQuery_Obj> parseCssMediaQueries();
sass::string parseIdentifier();
CssMediaQuery_Obj parseCssMediaQuery();
Return_Obj parse_return_directive();
Content_Obj parse_content_directive();
void parse_charset_directive();
List_Obj parse_media_queries();
Media_Query_Obj parse_media_query();
Media_Query_ExpressionObj parse_media_expression();
SupportsRuleObj parse_supports_directive();
SupportsConditionObj parse_supports_condition(bool top_level);
SupportsConditionObj parse_supports_negation();
SupportsConditionObj parse_supports_operator(bool top_level);
SupportsConditionObj parse_supports_interpolation();
SupportsConditionObj parse_supports_declaration();
SupportsConditionObj parse_supports_condition_in_parens(bool parens_required);
AtRootRuleObj parse_at_root_block();
At_Root_Query_Obj parse_at_root_query();
String_Schema_Obj parse_almost_any_value();
AtRuleObj parse_directive();
WarningRuleObj parse_warning();
ErrorRuleObj parse_error();
DebugRuleObj parse_debug();
Value* color_or_string(const sass::string& lexed) const;
// be more like ruby sass
ExpressionObj lex_almost_any_value_token();
ExpressionObj lex_almost_any_value_chars();
ExpressionObj lex_interp_string();
ExpressionObj lex_interp_uri();
ExpressionObj lex_interpolation();
// these will throw errors
Token lex_variable();
Token lex_identifier();
void parse_block_comments(bool store = true);
Lookahead lookahead_for_value(const char* start = 0);
Lookahead lookahead_for_selector(const char* start = 0);
Lookahead lookahead_for_include(const char* start = 0);
ExpressionObj fold_operands(ExpressionObj base, sass::vector<ExpressionObj>& operands, Operand op);
ExpressionObj fold_operands(ExpressionObj base, sass::vector<ExpressionObj>& operands, sass::vector<Operand>& ops, size_t i = 0);
void throw_syntax_error(sass::string message, size_t ln = 0);
void throw_read_error(sass::string message, size_t ln = 0);
template <Prelexer::prelexer open, Prelexer::prelexer close>
ExpressionObj lex_interp()
{
if (lex < open >(false)) {
String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate);
// std::cerr << "LEX [[" << sass::string(lexed) << "]]\n";
schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed));
if (position[0] == '#' && position[1] == '{') {
ExpressionObj itpl = lex_interpolation();
if (!itpl.isNull()) schema->append(itpl);
while (lex < close >(false)) {
// std::cerr << "LEX [[" << sass::string(lexed) << "]]\n";
schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed));
if (position[0] == '#' && position[1] == '{') {
ExpressionObj itpl = lex_interpolation();
if (!itpl.isNull()) schema->append(itpl);
} else {
return schema;
}
}
} else {
return SASS_MEMORY_NEW(String_Constant, pstate, lexed);
}
}
return {};
}
public:
static Number* lexed_number(const SourceSpan& pstate, const sass::string& parsed);
static Number* lexed_dimension(const SourceSpan& pstate, const sass::string& parsed);
static Number* lexed_percentage(const SourceSpan& pstate, const sass::string& parsed);
static Value* lexed_hex_color(const SourceSpan& pstate, const sass::string& parsed);
private:
Number* lexed_number(const sass::string& parsed) { return lexed_number(pstate, parsed); };
Number* lexed_dimension(const sass::string& parsed) { return lexed_dimension(pstate, parsed); };
Number* lexed_percentage(const sass::string& parsed) { return lexed_percentage(pstate, parsed); };
Value* lexed_hex_color(const sass::string& parsed) { return lexed_hex_color(pstate, parsed); };
static const char* re_attr_sensitive_close(const char* src);
static const char* re_attr_insensitive_close(const char* src);
};
size_t check_bom_chars(const char* src, const char *end, const unsigned char* bom, size_t len);
}
#endif