sassc4 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.gitmodules +3 -0
- data/.travis.yml +16 -0
- data/CHANGELOG.md +97 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +22 -0
- data/README.md +80 -0
- data/Rakefile +51 -0
- data/ext/depend +4 -0
- data/ext/extconf.rb +92 -0
- data/ext/libsass/VERSION +1 -0
- data/ext/libsass/contrib/plugin.cpp +60 -0
- data/ext/libsass/include/sass/base.h +97 -0
- data/ext/libsass/include/sass/context.h +174 -0
- data/ext/libsass/include/sass/functions.h +139 -0
- data/ext/libsass/include/sass/values.h +145 -0
- data/ext/libsass/include/sass/version.h +12 -0
- data/ext/libsass/include/sass.h +15 -0
- data/ext/libsass/include/sass2scss.h +120 -0
- data/ext/libsass/src/MurmurHash2.hpp +91 -0
- data/ext/libsass/src/ast.cpp +953 -0
- data/ext/libsass/src/ast.hpp +1064 -0
- data/ext/libsass/src/ast2c.cpp +80 -0
- data/ext/libsass/src/ast2c.hpp +39 -0
- data/ext/libsass/src/ast_def_macros.hpp +140 -0
- data/ext/libsass/src/ast_fwd_decl.cpp +31 -0
- data/ext/libsass/src/ast_fwd_decl.hpp +274 -0
- data/ext/libsass/src/ast_helpers.hpp +316 -0
- data/ext/libsass/src/ast_sel_cmp.cpp +396 -0
- data/ext/libsass/src/ast_sel_super.cpp +539 -0
- data/ext/libsass/src/ast_sel_unify.cpp +275 -0
- data/ext/libsass/src/ast_sel_weave.cpp +616 -0
- data/ext/libsass/src/ast_selectors.cpp +1070 -0
- data/ext/libsass/src/ast_selectors.hpp +523 -0
- data/ext/libsass/src/ast_supports.cpp +114 -0
- data/ext/libsass/src/ast_supports.hpp +121 -0
- data/ext/libsass/src/ast_values.cpp +1154 -0
- data/ext/libsass/src/ast_values.hpp +498 -0
- data/ext/libsass/src/b64/cencode.h +32 -0
- data/ext/libsass/src/b64/encode.h +79 -0
- data/ext/libsass/src/backtrace.cpp +50 -0
- data/ext/libsass/src/backtrace.hpp +29 -0
- data/ext/libsass/src/base64vlq.cpp +47 -0
- data/ext/libsass/src/base64vlq.hpp +30 -0
- data/ext/libsass/src/bind.cpp +312 -0
- data/ext/libsass/src/bind.hpp +15 -0
- data/ext/libsass/src/c2ast.cpp +64 -0
- data/ext/libsass/src/c2ast.hpp +14 -0
- data/ext/libsass/src/c99func.c +54 -0
- data/ext/libsass/src/cencode.c +106 -0
- data/ext/libsass/src/check_nesting.cpp +393 -0
- data/ext/libsass/src/check_nesting.hpp +70 -0
- data/ext/libsass/src/color_maps.cpp +652 -0
- data/ext/libsass/src/color_maps.hpp +323 -0
- data/ext/libsass/src/color_spaces.cpp +241 -0
- data/ext/libsass/src/color_spaces.hpp +227 -0
- data/ext/libsass/src/constants.cpp +199 -0
- data/ext/libsass/src/constants.hpp +200 -0
- data/ext/libsass/src/context.cpp +870 -0
- data/ext/libsass/src/context.hpp +140 -0
- data/ext/libsass/src/cssize.cpp +521 -0
- data/ext/libsass/src/cssize.hpp +71 -0
- data/ext/libsass/src/dart_helpers.hpp +199 -0
- data/ext/libsass/src/debug.hpp +43 -0
- data/ext/libsass/src/debugger.hpp +964 -0
- data/ext/libsass/src/emitter.cpp +297 -0
- data/ext/libsass/src/emitter.hpp +101 -0
- data/ext/libsass/src/environment.cpp +260 -0
- data/ext/libsass/src/environment.hpp +124 -0
- data/ext/libsass/src/error_handling.cpp +239 -0
- data/ext/libsass/src/error_handling.hpp +248 -0
- data/ext/libsass/src/eval.cpp +1543 -0
- data/ext/libsass/src/eval.hpp +110 -0
- data/ext/libsass/src/eval_selectors.cpp +75 -0
- data/ext/libsass/src/expand.cpp +875 -0
- data/ext/libsass/src/expand.hpp +98 -0
- data/ext/libsass/src/extender.cpp +1226 -0
- data/ext/libsass/src/extender.hpp +399 -0
- data/ext/libsass/src/extension.cpp +43 -0
- data/ext/libsass/src/extension.hpp +89 -0
- data/ext/libsass/src/file.cpp +531 -0
- data/ext/libsass/src/file.hpp +124 -0
- data/ext/libsass/src/fn_colors.cpp +836 -0
- data/ext/libsass/src/fn_colors.hpp +99 -0
- data/ext/libsass/src/fn_lists.cpp +285 -0
- data/ext/libsass/src/fn_lists.hpp +34 -0
- data/ext/libsass/src/fn_maps.cpp +94 -0
- data/ext/libsass/src/fn_maps.hpp +30 -0
- data/ext/libsass/src/fn_miscs.cpp +248 -0
- data/ext/libsass/src/fn_miscs.hpp +40 -0
- data/ext/libsass/src/fn_numbers.cpp +246 -0
- data/ext/libsass/src/fn_numbers.hpp +45 -0
- data/ext/libsass/src/fn_selectors.cpp +205 -0
- data/ext/libsass/src/fn_selectors.hpp +35 -0
- data/ext/libsass/src/fn_strings.cpp +268 -0
- data/ext/libsass/src/fn_strings.hpp +34 -0
- data/ext/libsass/src/fn_utils.cpp +159 -0
- data/ext/libsass/src/fn_utils.hpp +62 -0
- data/ext/libsass/src/inspect.cpp +1126 -0
- data/ext/libsass/src/inspect.hpp +101 -0
- data/ext/libsass/src/json.cpp +1436 -0
- data/ext/libsass/src/json.hpp +117 -0
- data/ext/libsass/src/kwd_arg_macros.hpp +28 -0
- data/ext/libsass/src/lexer.cpp +122 -0
- data/ext/libsass/src/lexer.hpp +304 -0
- data/ext/libsass/src/listize.cpp +70 -0
- data/ext/libsass/src/listize.hpp +37 -0
- data/ext/libsass/src/mapping.hpp +19 -0
- data/ext/libsass/src/memory/allocator.cpp +48 -0
- data/ext/libsass/src/memory/allocator.hpp +138 -0
- data/ext/libsass/src/memory/config.hpp +20 -0
- data/ext/libsass/src/memory/memory_pool.hpp +186 -0
- data/ext/libsass/src/memory/shared_ptr.cpp +33 -0
- data/ext/libsass/src/memory/shared_ptr.hpp +332 -0
- data/ext/libsass/src/memory.hpp +12 -0
- data/ext/libsass/src/operation.hpp +223 -0
- data/ext/libsass/src/operators.cpp +267 -0
- data/ext/libsass/src/operators.hpp +30 -0
- data/ext/libsass/src/ordered_map.hpp +112 -0
- data/ext/libsass/src/output.cpp +320 -0
- data/ext/libsass/src/output.hpp +47 -0
- data/ext/libsass/src/parser.cpp +3059 -0
- data/ext/libsass/src/parser.hpp +395 -0
- data/ext/libsass/src/parser_selectors.cpp +189 -0
- data/ext/libsass/src/permutate.hpp +164 -0
- data/ext/libsass/src/plugins.cpp +188 -0
- data/ext/libsass/src/plugins.hpp +57 -0
- data/ext/libsass/src/position.cpp +163 -0
- data/ext/libsass/src/position.hpp +147 -0
- data/ext/libsass/src/prelexer.cpp +1780 -0
- data/ext/libsass/src/prelexer.hpp +484 -0
- data/ext/libsass/src/remove_placeholders.cpp +86 -0
- data/ext/libsass/src/remove_placeholders.hpp +37 -0
- data/ext/libsass/src/sass.cpp +156 -0
- data/ext/libsass/src/sass.hpp +147 -0
- data/ext/libsass/src/sass2scss.cpp +895 -0
- data/ext/libsass/src/sass_context.cpp +742 -0
- data/ext/libsass/src/sass_context.hpp +129 -0
- data/ext/libsass/src/sass_functions.cpp +210 -0
- data/ext/libsass/src/sass_functions.hpp +50 -0
- data/ext/libsass/src/sass_values.cpp +362 -0
- data/ext/libsass/src/sass_values.hpp +82 -0
- data/ext/libsass/src/settings.hpp +19 -0
- data/ext/libsass/src/source.cpp +69 -0
- data/ext/libsass/src/source.hpp +95 -0
- data/ext/libsass/src/source_data.hpp +32 -0
- data/ext/libsass/src/source_map.cpp +202 -0
- data/ext/libsass/src/source_map.hpp +65 -0
- data/ext/libsass/src/stylesheet.cpp +22 -0
- data/ext/libsass/src/stylesheet.hpp +57 -0
- data/ext/libsass/src/to_value.cpp +114 -0
- data/ext/libsass/src/to_value.hpp +46 -0
- data/ext/libsass/src/units.cpp +507 -0
- data/ext/libsass/src/units.hpp +110 -0
- data/ext/libsass/src/utf8/checked.h +336 -0
- data/ext/libsass/src/utf8/core.h +332 -0
- data/ext/libsass/src/utf8/unchecked.h +235 -0
- data/ext/libsass/src/utf8.h +34 -0
- data/ext/libsass/src/utf8_string.cpp +104 -0
- data/ext/libsass/src/utf8_string.hpp +38 -0
- data/ext/libsass/src/util.cpp +723 -0
- data/ext/libsass/src/util.hpp +105 -0
- data/ext/libsass/src/util_string.cpp +125 -0
- data/ext/libsass/src/util_string.hpp +73 -0
- data/ext/libsass/src/values.cpp +140 -0
- data/ext/libsass/src/values.hpp +12 -0
- data/lib/sassc/dependency.rb +17 -0
- data/lib/sassc/engine.rb +141 -0
- data/lib/sassc/error.rb +37 -0
- data/lib/sassc/functions_handler.rb +73 -0
- data/lib/sassc/import_handler.rb +50 -0
- data/lib/sassc/importer.rb +31 -0
- data/lib/sassc/native/native_context_api.rb +147 -0
- data/lib/sassc/native/native_functions_api.rb +159 -0
- data/lib/sassc/native/sass2scss_api.rb +10 -0
- data/lib/sassc/native/sass_input_style.rb +13 -0
- data/lib/sassc/native/sass_output_style.rb +12 -0
- data/lib/sassc/native/sass_value.rb +97 -0
- data/lib/sassc/native/string_list.rb +10 -0
- data/lib/sassc/native.rb +64 -0
- data/lib/sassc/sass_2_scss.rb +9 -0
- data/lib/sassc/script/functions.rb +8 -0
- data/lib/sassc/script/value/bool.rb +32 -0
- data/lib/sassc/script/value/color.rb +95 -0
- data/lib/sassc/script/value/list.rb +136 -0
- data/lib/sassc/script/value/map.rb +69 -0
- data/lib/sassc/script/value/number.rb +389 -0
- data/lib/sassc/script/value/string.rb +96 -0
- data/lib/sassc/script/value.rb +137 -0
- data/lib/sassc/script/value_conversion/base.rb +13 -0
- data/lib/sassc/script/value_conversion/bool.rb +13 -0
- data/lib/sassc/script/value_conversion/color.rb +18 -0
- data/lib/sassc/script/value_conversion/list.rb +25 -0
- data/lib/sassc/script/value_conversion/map.rb +21 -0
- data/lib/sassc/script/value_conversion/number.rb +13 -0
- data/lib/sassc/script/value_conversion/string.rb +17 -0
- data/lib/sassc/script/value_conversion.rb +69 -0
- data/lib/sassc/script.rb +17 -0
- data/lib/sassc/util/normalized_map.rb +117 -0
- data/lib/sassc/util.rb +231 -0
- data/lib/sassc/version.rb +5 -0
- data/lib/sassc.rb +57 -0
- data/sassc.gemspec +69 -0
- data/test/css_color_level4_test.rb +168 -0
- data/test/custom_importer_test.rb +127 -0
- data/test/engine_test.rb +314 -0
- data/test/error_test.rb +29 -0
- data/test/fixtures/paths.scss +10 -0
- data/test/functions_test.rb +340 -0
- data/test/native_test.rb +213 -0
- data/test/output_style_test.rb +107 -0
- data/test/sass_2_scss_test.rb +14 -0
- data/test/test_helper.rb +45 -0
- metadata +396 -0
|
@@ -0,0 +1,3059 @@
|
|
|
1
|
+
// sass.hpp must go before all system headers to get the
|
|
2
|
+
// __EXTENSIONS__ fix on Solaris.
|
|
3
|
+
#include "sass.hpp"
|
|
4
|
+
|
|
5
|
+
#include "parser.hpp"
|
|
6
|
+
#include "color_maps.hpp"
|
|
7
|
+
#include "util_string.hpp"
|
|
8
|
+
|
|
9
|
+
// Notes about delayed: some ast nodes can have delayed evaluation so
|
|
10
|
+
// they can preserve their original semantics if needed. This is most
|
|
11
|
+
// prominently exhibited by the division operation, since it is not
|
|
12
|
+
// only a valid operation, but also a valid css statement (i.e. for
|
|
13
|
+
// fonts, as in `16px/24px`). When parsing lists and expression we
|
|
14
|
+
// unwrap single items from lists and other operations. A nested list
|
|
15
|
+
// must not be delayed, only the items of the first level sometimes
|
|
16
|
+
// are delayed (as with argument lists). To achieve this we need to
|
|
17
|
+
// pass status to the list parser, so this can be set correctly.
|
|
18
|
+
// Another case with delayed values are colors. In compressed mode
|
|
19
|
+
// only processed values get compressed (other are left as written).
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
namespace Sass {
|
|
23
|
+
using namespace Constants;
|
|
24
|
+
using namespace Prelexer;
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
Parser::Parser(SourceData* source, Context& ctx, Backtraces traces, bool allow_parent) :
|
|
28
|
+
SourceSpan(source),
|
|
29
|
+
ctx(ctx),
|
|
30
|
+
source(source),
|
|
31
|
+
begin(source->begin()),
|
|
32
|
+
position(source->begin()),
|
|
33
|
+
end(source->end()),
|
|
34
|
+
before_token(0, 0),
|
|
35
|
+
after_token(0, 0),
|
|
36
|
+
pstate(source->getSourceSpan()),
|
|
37
|
+
traces(traces),
|
|
38
|
+
indentation(0),
|
|
39
|
+
nestings(0),
|
|
40
|
+
allow_parent(allow_parent)
|
|
41
|
+
{
|
|
42
|
+
Block_Obj root = SASS_MEMORY_NEW(Block, pstate);
|
|
43
|
+
stack.push_back(Scope::Root);
|
|
44
|
+
block_stack.push_back(root);
|
|
45
|
+
root->is_root(true);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
void Parser::advanceToNextToken() {
|
|
49
|
+
lex < css_comments >(false);
|
|
50
|
+
// advance to position
|
|
51
|
+
pstate.position += pstate.offset;
|
|
52
|
+
pstate.offset.column = 0;
|
|
53
|
+
pstate.offset.line = 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
SelectorListObj Parser::parse_selector(SourceData* source, Context& ctx, Backtraces traces, bool allow_parent)
|
|
57
|
+
{
|
|
58
|
+
Parser p(source, ctx, traces, allow_parent);
|
|
59
|
+
// ToDo: remap the source-map entries somehow
|
|
60
|
+
return p.parseSelectorList(false);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
bool Parser::peek_newline(const char* start)
|
|
64
|
+
{
|
|
65
|
+
return peek_linefeed(start ? start : position)
|
|
66
|
+
&& ! peek_css<exactly<'{'>>(start);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* main entry point to parse root block */
|
|
70
|
+
Block_Obj Parser::parse()
|
|
71
|
+
{
|
|
72
|
+
|
|
73
|
+
// consume unicode BOM
|
|
74
|
+
read_bom();
|
|
75
|
+
|
|
76
|
+
// scan the input to find invalid utf8 sequences
|
|
77
|
+
const char* it = utf8::find_invalid(position, end);
|
|
78
|
+
|
|
79
|
+
// report invalid utf8
|
|
80
|
+
if (it != end) {
|
|
81
|
+
pstate.position += Offset::init(position, it);
|
|
82
|
+
traces.push_back(Backtrace(pstate));
|
|
83
|
+
throw Exception::InvalidSass(pstate, traces, "Invalid UTF-8 sequence");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// create a block AST node to hold children
|
|
87
|
+
Block_Obj root = SASS_MEMORY_NEW(Block, pstate, 0, true);
|
|
88
|
+
|
|
89
|
+
// check seems a bit esoteric but works
|
|
90
|
+
if (ctx.resources.size() == 1) {
|
|
91
|
+
// apply headers only on very first include
|
|
92
|
+
ctx.apply_custom_headers(root, getPath(), pstate);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// parse children nodes
|
|
96
|
+
block_stack.push_back(root);
|
|
97
|
+
parse_block_nodes(true);
|
|
98
|
+
block_stack.pop_back();
|
|
99
|
+
|
|
100
|
+
// update final position
|
|
101
|
+
root->update_pstate(pstate);
|
|
102
|
+
|
|
103
|
+
if (position != end) {
|
|
104
|
+
css_error("Invalid CSS", " after ", ": expected selector or at-rule, was ");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return root;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
// convenience function for block parsing
|
|
112
|
+
// will create a new block ad-hoc for you
|
|
113
|
+
// this is the base block parsing function
|
|
114
|
+
Block_Obj Parser::parse_css_block(bool is_root)
|
|
115
|
+
{
|
|
116
|
+
|
|
117
|
+
// parse comments before block
|
|
118
|
+
// lex < optional_css_comments >();
|
|
119
|
+
|
|
120
|
+
// lex mandatory opener or error out
|
|
121
|
+
if (!lex_css < exactly<'{'> >()) {
|
|
122
|
+
css_error("Invalid CSS", " after ", ": expected \"{\", was ");
|
|
123
|
+
}
|
|
124
|
+
// create new block and push to the selector stack
|
|
125
|
+
Block_Obj block = SASS_MEMORY_NEW(Block, pstate, 0, is_root);
|
|
126
|
+
block_stack.push_back(block);
|
|
127
|
+
|
|
128
|
+
if (!parse_block_nodes(is_root)) css_error("Invalid CSS", " after ", ": expected \"}\", was ");
|
|
129
|
+
|
|
130
|
+
if (!lex_css < exactly<'}'> >()) {
|
|
131
|
+
css_error("Invalid CSS", " after ", ": expected \"}\", was ");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// update for end position
|
|
135
|
+
// this seems to be done somewhere else
|
|
136
|
+
// but that fixed selector schema issue
|
|
137
|
+
// block->update_pstate(pstate);
|
|
138
|
+
|
|
139
|
+
// parse comments after block
|
|
140
|
+
// lex < optional_css_comments >();
|
|
141
|
+
|
|
142
|
+
block_stack.pop_back();
|
|
143
|
+
|
|
144
|
+
return block;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// convenience function for block parsing
|
|
148
|
+
// will create a new block ad-hoc for you
|
|
149
|
+
// also updates the `in_at_root` flag
|
|
150
|
+
Block_Obj Parser::parse_block(bool is_root)
|
|
151
|
+
{
|
|
152
|
+
return parse_css_block(is_root);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// the main block parsing function
|
|
156
|
+
// parses stuff between `{` and `}`
|
|
157
|
+
bool Parser::parse_block_nodes(bool is_root)
|
|
158
|
+
{
|
|
159
|
+
|
|
160
|
+
// loop until end of string
|
|
161
|
+
while (position < end) {
|
|
162
|
+
|
|
163
|
+
// we should be able to refactor this
|
|
164
|
+
parse_block_comments();
|
|
165
|
+
lex < css_whitespace >();
|
|
166
|
+
|
|
167
|
+
if (lex < exactly<';'> >()) continue;
|
|
168
|
+
if (peek < end_of_file >()) return true;
|
|
169
|
+
if (peek < exactly<'}'> >()) return true;
|
|
170
|
+
|
|
171
|
+
if (parse_block_node(is_root)) continue;
|
|
172
|
+
|
|
173
|
+
parse_block_comments();
|
|
174
|
+
|
|
175
|
+
if (lex_css < exactly<';'> >()) continue;
|
|
176
|
+
if (peek_css < end_of_file >()) return true;
|
|
177
|
+
if (peek_css < exactly<'}'> >()) return true;
|
|
178
|
+
|
|
179
|
+
// illegal sass
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
// return success
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// parser for a single node in a block
|
|
187
|
+
// semicolons must be lexed beforehand
|
|
188
|
+
bool Parser::parse_block_node(bool is_root) {
|
|
189
|
+
|
|
190
|
+
Block_Obj block = block_stack.back();
|
|
191
|
+
|
|
192
|
+
parse_block_comments();
|
|
193
|
+
|
|
194
|
+
// throw away white-space
|
|
195
|
+
// includes line comments
|
|
196
|
+
lex < css_whitespace >();
|
|
197
|
+
|
|
198
|
+
Lookahead lookahead_result;
|
|
199
|
+
|
|
200
|
+
// also parse block comments
|
|
201
|
+
|
|
202
|
+
// first parse everything that is allowed in functions
|
|
203
|
+
if (lex < variable >(true)) { block->append(parse_assignment()); }
|
|
204
|
+
else if (lex < kwd_err >(true)) { block->append(parse_error()); }
|
|
205
|
+
else if (lex < kwd_dbg >(true)) { block->append(parse_debug()); }
|
|
206
|
+
else if (lex < kwd_warn >(true)) { block->append(parse_warning()); }
|
|
207
|
+
else if (lex < kwd_if_directive >(true)) { block->append(parse_if_directive()); }
|
|
208
|
+
else if (lex < kwd_for_directive >(true)) { block->append(parse_for_directive()); }
|
|
209
|
+
else if (lex < kwd_each_directive >(true)) { block->append(parse_each_directive()); }
|
|
210
|
+
else if (lex < kwd_while_directive >(true)) { block->append(parse_while_directive()); }
|
|
211
|
+
else if (lex < kwd_return_directive >(true)) { block->append(parse_return_directive()); }
|
|
212
|
+
|
|
213
|
+
// parse imports to process later
|
|
214
|
+
else if (lex < kwd_import >(true)) {
|
|
215
|
+
Scope parent = stack.empty() ? Scope::Rules : stack.back();
|
|
216
|
+
if (parent != Scope::Function && parent != Scope::Root && parent != Scope::Rules && parent != Scope::Media) {
|
|
217
|
+
if (! peek_css< uri_prefix >(position)) { // this seems to go in ruby sass 3.4.20
|
|
218
|
+
error("Import directives may not be used within control directives or mixins.");
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// this puts the parsed doc into sheets
|
|
222
|
+
// import stub will fetch this in expand
|
|
223
|
+
Import_Obj imp = parse_import();
|
|
224
|
+
// if it is a url, we only add the statement
|
|
225
|
+
if (!imp->urls().empty()) block->append(imp);
|
|
226
|
+
// process all resources now (add Import_Stub nodes)
|
|
227
|
+
for (size_t i = 0, S = imp->incs().size(); i < S; ++i) {
|
|
228
|
+
block->append(SASS_MEMORY_NEW(Import_Stub, pstate, imp->incs()[i]));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
else if (lex < kwd_extend >(true)) {
|
|
233
|
+
Lookahead lookahead = lookahead_for_include(position);
|
|
234
|
+
if (!lookahead.found) css_error("Invalid CSS", " after ", ": expected selector, was ");
|
|
235
|
+
SelectorListObj target;
|
|
236
|
+
if (!lookahead.has_interpolants) {
|
|
237
|
+
LOCAL_FLAG(allow_parent, false);
|
|
238
|
+
auto selector = parseSelectorList(true);
|
|
239
|
+
auto extender = SASS_MEMORY_NEW(ExtendRule, pstate, selector);
|
|
240
|
+
extender->isOptional(selector && selector->is_optional());
|
|
241
|
+
block->append(extender);
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
LOCAL_FLAG(allow_parent, false);
|
|
245
|
+
auto selector = parse_selector_schema(lookahead.found, true);
|
|
246
|
+
auto extender = SASS_MEMORY_NEW(ExtendRule, pstate, selector);
|
|
247
|
+
// A schema is not optional yet, check once it is evaluated
|
|
248
|
+
// extender->isOptional(selector && selector->is_optional());
|
|
249
|
+
block->append(extender);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// selector may contain interpolations which need delayed evaluation
|
|
255
|
+
else if (
|
|
256
|
+
!(lookahead_result = lookahead_for_selector(position)).error &&
|
|
257
|
+
!lookahead_result.is_custom_property
|
|
258
|
+
)
|
|
259
|
+
{
|
|
260
|
+
block->append(parse_ruleset(lookahead_result));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// parse multiple specific keyword directives
|
|
264
|
+
else if (lex < kwd_media >(true)) { block->append(parseMediaRule()); }
|
|
265
|
+
else if (lex < kwd_at_root >(true)) { block->append(parse_at_root_block()); }
|
|
266
|
+
else if (lex < kwd_include_directive >(true)) { block->append(parse_include_directive()); }
|
|
267
|
+
else if (lex < kwd_content_directive >(true)) { block->append(parse_content_directive()); }
|
|
268
|
+
else if (lex < kwd_supports_directive >(true)) { block->append(parse_supports_directive()); }
|
|
269
|
+
else if (lex < kwd_mixin >(true)) { block->append(parse_definition(Definition::MIXIN)); }
|
|
270
|
+
else if (lex < kwd_function >(true)) { block->append(parse_definition(Definition::FUNCTION)); }
|
|
271
|
+
|
|
272
|
+
// ignore the @charset directive for now
|
|
273
|
+
else if (lex< kwd_charset_directive >(true)) { parse_charset_directive(); }
|
|
274
|
+
|
|
275
|
+
else if (lex < exactly < else_kwd >>(true)) { error("Invalid CSS: @else must come after @if"); }
|
|
276
|
+
|
|
277
|
+
// generic at keyword (keep last)
|
|
278
|
+
else if (lex< at_keyword >(true)) { block->append(parse_directive()); }
|
|
279
|
+
|
|
280
|
+
else if (is_root && stack.back() != Scope::AtRoot /* && block->is_root() */) {
|
|
281
|
+
lex< css_whitespace >();
|
|
282
|
+
if (position >= end) return true;
|
|
283
|
+
css_error("Invalid CSS", " after ", ": expected 1 selector or at-rule, was ");
|
|
284
|
+
}
|
|
285
|
+
// parse a declaration
|
|
286
|
+
else
|
|
287
|
+
{
|
|
288
|
+
// ToDo: how does it handle parse errors?
|
|
289
|
+
// maybe we are expected to parse something?
|
|
290
|
+
Declaration_Obj decl = parse_declaration();
|
|
291
|
+
decl->tabs(indentation);
|
|
292
|
+
block->append(decl);
|
|
293
|
+
// maybe we have a "sub-block"
|
|
294
|
+
if (peek< exactly<'{'> >()) {
|
|
295
|
+
if (decl->is_indented()) ++ indentation;
|
|
296
|
+
// parse a propset that rides on the declaration's property
|
|
297
|
+
stack.push_back(Scope::Properties);
|
|
298
|
+
decl->block(parse_block());
|
|
299
|
+
stack.pop_back();
|
|
300
|
+
if (decl->is_indented()) -- indentation;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
// something matched
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
// EO parse_block_nodes
|
|
307
|
+
|
|
308
|
+
// parse imports inside the
|
|
309
|
+
Import_Obj Parser::parse_import()
|
|
310
|
+
{
|
|
311
|
+
Import_Obj imp = SASS_MEMORY_NEW(Import, pstate);
|
|
312
|
+
sass::vector<std::pair<sass::string,Function_Call_Obj>> to_import;
|
|
313
|
+
bool first = true;
|
|
314
|
+
do {
|
|
315
|
+
while (lex< block_comment >());
|
|
316
|
+
if (lex< quoted_string >()) {
|
|
317
|
+
to_import.push_back(std::pair<sass::string,Function_Call_Obj>(sass::string(lexed), {}));
|
|
318
|
+
}
|
|
319
|
+
else if (lex< uri_prefix >()) {
|
|
320
|
+
Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate);
|
|
321
|
+
Function_Call_Obj result = SASS_MEMORY_NEW(Function_Call, pstate, sass::string("url"), args);
|
|
322
|
+
|
|
323
|
+
if (lex< quoted_string >()) {
|
|
324
|
+
ExpressionObj quoted_url = parse_string();
|
|
325
|
+
args->append(SASS_MEMORY_NEW(Argument, quoted_url->pstate(), quoted_url));
|
|
326
|
+
}
|
|
327
|
+
else if (String_Obj string_url = parse_url_function_argument()) {
|
|
328
|
+
args->append(SASS_MEMORY_NEW(Argument, string_url->pstate(), string_url));
|
|
329
|
+
}
|
|
330
|
+
else if (peek < skip_over_scopes < exactly < '(' >, exactly < ')' > > >(position)) {
|
|
331
|
+
ExpressionObj braced_url = parse_list(); // parse_interpolated_chunk(lexed);
|
|
332
|
+
args->append(SASS_MEMORY_NEW(Argument, braced_url->pstate(), braced_url));
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
error("malformed URL");
|
|
336
|
+
}
|
|
337
|
+
if (!lex< exactly<')'> >()) error("URI is missing ')'");
|
|
338
|
+
to_import.push_back(std::pair<sass::string, Function_Call_Obj>("", result));
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
if (first) error("@import directive requires a url or quoted path");
|
|
342
|
+
else error("expecting another url or quoted path in @import list");
|
|
343
|
+
}
|
|
344
|
+
first = false;
|
|
345
|
+
} while (lex_css< exactly<','> >());
|
|
346
|
+
|
|
347
|
+
if (!peek_css< alternatives< exactly<';'>, exactly<'}'>, end_of_file > >()) {
|
|
348
|
+
List_Obj import_queries = parse_media_queries();
|
|
349
|
+
imp->import_queries(import_queries);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
for(auto location : to_import) {
|
|
353
|
+
if (location.second) {
|
|
354
|
+
imp->urls().push_back(location.second);
|
|
355
|
+
}
|
|
356
|
+
// check if custom importers want to take over the handling
|
|
357
|
+
else if (!ctx.call_importers(unquote(location.first), getPath(), pstate, imp)) {
|
|
358
|
+
// nobody wants it, so we do our import
|
|
359
|
+
ctx.import_url(imp, location.first, getPath());
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return imp;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
Definition_Obj Parser::parse_definition(Definition::Type which_type)
|
|
367
|
+
{
|
|
368
|
+
sass::string which_str(lexed);
|
|
369
|
+
if (!lex< identifier >()) error("invalid name in " + which_str + " definition");
|
|
370
|
+
sass::string name(Util::normalize_underscores(lexed));
|
|
371
|
+
if (which_type == Definition::FUNCTION && (name == "and" || name == "or" || name == "not"))
|
|
372
|
+
{ error("Invalid function name \"" + name + "\"."); }
|
|
373
|
+
SourceSpan source_position_of_def = pstate;
|
|
374
|
+
Parameters_Obj params = parse_parameters();
|
|
375
|
+
if (which_type == Definition::MIXIN) stack.push_back(Scope::Mixin);
|
|
376
|
+
else stack.push_back(Scope::Function);
|
|
377
|
+
Block_Obj body = parse_block();
|
|
378
|
+
stack.pop_back();
|
|
379
|
+
return SASS_MEMORY_NEW(Definition, source_position_of_def, name, params, body, which_type);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
Parameters_Obj Parser::parse_parameters()
|
|
383
|
+
{
|
|
384
|
+
Parameters_Obj params = SASS_MEMORY_NEW(Parameters, pstate);
|
|
385
|
+
if (lex_css< exactly<'('> >()) {
|
|
386
|
+
// if there's anything there at all
|
|
387
|
+
if (!peek_css< exactly<')'> >()) {
|
|
388
|
+
do {
|
|
389
|
+
if (peek< exactly<')'> >()) break;
|
|
390
|
+
params->append(parse_parameter());
|
|
391
|
+
} while (lex_css< exactly<','> >());
|
|
392
|
+
}
|
|
393
|
+
if (!lex_css< exactly<')'> >()) {
|
|
394
|
+
css_error("Invalid CSS", " after ", ": expected \")\", was ");
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return params;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
Parameter_Obj Parser::parse_parameter()
|
|
401
|
+
{
|
|
402
|
+
if (peek< alternatives< exactly<','>, exactly< '{' >, exactly<';'> > >()) {
|
|
403
|
+
css_error("Invalid CSS", " after ", ": expected variable (e.g. $foo), was ");
|
|
404
|
+
}
|
|
405
|
+
while (lex< alternatives < spaces, block_comment > >());
|
|
406
|
+
lex < variable >();
|
|
407
|
+
sass::string name(Util::normalize_underscores(lexed));
|
|
408
|
+
SourceSpan pos = pstate;
|
|
409
|
+
ExpressionObj val;
|
|
410
|
+
bool is_rest = false;
|
|
411
|
+
while (lex< alternatives < spaces, block_comment > >());
|
|
412
|
+
if (lex< exactly<':'> >()) { // there's a default value
|
|
413
|
+
while (lex< block_comment >());
|
|
414
|
+
val = parse_space_list();
|
|
415
|
+
}
|
|
416
|
+
else if (lex< exactly< ellipsis > >()) {
|
|
417
|
+
is_rest = true;
|
|
418
|
+
}
|
|
419
|
+
return SASS_MEMORY_NEW(Parameter, pos, name, val, is_rest);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
Arguments_Obj Parser::parse_arguments()
|
|
423
|
+
{
|
|
424
|
+
Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate);
|
|
425
|
+
if (lex_css< exactly<'('> >()) {
|
|
426
|
+
// if there's anything there at all
|
|
427
|
+
if (!peek_css< exactly<')'> >()) {
|
|
428
|
+
do {
|
|
429
|
+
if (peek< exactly<')'> >()) break;
|
|
430
|
+
args->append(parse_argument());
|
|
431
|
+
} while (lex_css< exactly<','> >());
|
|
432
|
+
}
|
|
433
|
+
if (!lex_css< exactly<')'> >()) {
|
|
434
|
+
css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return args;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Parse arguments for CSS Color Level 4 functions (rgb, rgba, hsl, hsla)
|
|
441
|
+
// Supports both old comma-separated syntax and new space-separated syntax
|
|
442
|
+
// Examples:
|
|
443
|
+
// rgb(255, 0, 0) - old syntax with commas
|
|
444
|
+
// rgb(255 0 0) - new syntax with spaces
|
|
445
|
+
// rgb(255 0 0 / 0.5) - new syntax with alpha using slash
|
|
446
|
+
// rgba(255, 0, 0, 0.5) - old syntax with commas
|
|
447
|
+
Arguments_Obj Parser::parse_color_arguments()
|
|
448
|
+
{
|
|
449
|
+
Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate);
|
|
450
|
+
if (!lex_css< exactly<'('> >()) {
|
|
451
|
+
return args;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Check if there's anything inside
|
|
455
|
+
if (peek_css< exactly<')'> >()) {
|
|
456
|
+
lex_css< exactly<')'> >();
|
|
457
|
+
return args;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Parse first value (can't use parse_space_list because it treats / as division)
|
|
461
|
+
ExpressionObj val1 = parse_value();
|
|
462
|
+
if (!val1) {
|
|
463
|
+
css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Check if we have comma-separated (old syntax) or space-separated (new syntax)
|
|
467
|
+
bool has_comma = lex_css< exactly<','> >();
|
|
468
|
+
|
|
469
|
+
if (has_comma) {
|
|
470
|
+
// Old comma-separated syntax: rgb(255, 0, 0) or rgba(255, 0, 0, 0.5)
|
|
471
|
+
args->append(SASS_MEMORY_NEW(Argument, pstate, val1, "", false, false));
|
|
472
|
+
|
|
473
|
+
// Parse remaining comma-separated arguments
|
|
474
|
+
while (!peek_css< exactly<')'> >()) {
|
|
475
|
+
ExpressionObj val = parse_space_list();
|
|
476
|
+
if (!val) {
|
|
477
|
+
css_error("Invalid CSS", " after ", ": expected expression, was ");
|
|
478
|
+
}
|
|
479
|
+
args->append(SASS_MEMORY_NEW(Argument, pstate, val, "", false, false));
|
|
480
|
+
|
|
481
|
+
if (!lex_css< exactly<','> >()) {
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
} else {
|
|
486
|
+
// New space-separated syntax: rgb(255 0 0) or rgb(255 0 0 / 0.5)
|
|
487
|
+
// Parse individual space-separated values until we hit / or )
|
|
488
|
+
args->append(SASS_MEMORY_NEW(Argument, pstate, val1, "", false, false));
|
|
489
|
+
|
|
490
|
+
// Continue parsing space-separated values
|
|
491
|
+
while (!peek_css< exactly<'/'> >() && !peek_css< exactly<')'> >()) {
|
|
492
|
+
// Skip whitespace but don't consume comma or closing paren
|
|
493
|
+
lex < css_comments >();
|
|
494
|
+
|
|
495
|
+
if (peek_css< exactly<'/'> >() || peek_css< exactly<')'> >()) {
|
|
496
|
+
break;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
ExpressionObj val = parse_value();
|
|
500
|
+
if (!val) {
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
503
|
+
args->append(SASS_MEMORY_NEW(Argument, pstate, val, "", false, false));
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Check for slash (alpha separator in new syntax)
|
|
507
|
+
if (lex_css< exactly<'/'> >()) {
|
|
508
|
+
ExpressionObj alpha_val = parse_value();
|
|
509
|
+
if (!alpha_val) {
|
|
510
|
+
css_error("Invalid CSS", " after ", ": expected alpha value, was ");
|
|
511
|
+
}
|
|
512
|
+
args->append(SASS_MEMORY_NEW(Argument, pstate, alpha_val, "", false, false));
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (!lex_css< exactly<')'> >()) {
|
|
517
|
+
css_error("Invalid CSS", " after ", ": expected \")\", was ");
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
return args;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
Argument_Obj Parser::parse_argument()
|
|
524
|
+
{
|
|
525
|
+
if (peek< alternatives< exactly<','>, exactly< '{' >, exactly<';'> > >()) {
|
|
526
|
+
css_error("Invalid CSS", " after ", ": expected \")\", was ");
|
|
527
|
+
}
|
|
528
|
+
if (peek_css< sequence < exactly< hash_lbrace >, exactly< rbrace > > >()) {
|
|
529
|
+
position += 2;
|
|
530
|
+
css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
Argument_Obj arg;
|
|
534
|
+
if (peek_css< sequence < variable, optional_css_comments, exactly<':'> > >()) {
|
|
535
|
+
lex_css< variable >();
|
|
536
|
+
sass::string name(Util::normalize_underscores(lexed));
|
|
537
|
+
SourceSpan p = pstate;
|
|
538
|
+
lex_css< exactly<':'> >();
|
|
539
|
+
ExpressionObj val = parse_space_list();
|
|
540
|
+
arg = SASS_MEMORY_NEW(Argument, p, val, name);
|
|
541
|
+
}
|
|
542
|
+
else {
|
|
543
|
+
bool is_arglist = false;
|
|
544
|
+
bool is_keyword = false;
|
|
545
|
+
ExpressionObj val = parse_space_list();
|
|
546
|
+
List* l = Cast<List>(val);
|
|
547
|
+
if (lex_css< exactly< ellipsis > >()) {
|
|
548
|
+
if (val->concrete_type() == Expression::MAP || (
|
|
549
|
+
(l != NULL && l->separator() == SASS_HASH)
|
|
550
|
+
)) is_keyword = true;
|
|
551
|
+
else is_arglist = true;
|
|
552
|
+
}
|
|
553
|
+
arg = SASS_MEMORY_NEW(Argument, pstate, val, "", is_arglist, is_keyword);
|
|
554
|
+
}
|
|
555
|
+
return arg;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
Assignment_Obj Parser::parse_assignment()
|
|
559
|
+
{
|
|
560
|
+
sass::string name(Util::normalize_underscores(lexed));
|
|
561
|
+
SourceSpan var_source_position = pstate;
|
|
562
|
+
if (!lex< exactly<':'> >()) error("expected ':' after " + name + " in assignment statement");
|
|
563
|
+
if (peek_css< alternatives < exactly<';'>, end_of_file > >()) {
|
|
564
|
+
css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");
|
|
565
|
+
}
|
|
566
|
+
ExpressionObj val;
|
|
567
|
+
Lookahead lookahead = lookahead_for_value(position);
|
|
568
|
+
if (lookahead.has_interpolants && lookahead.found) {
|
|
569
|
+
val = parse_value_schema(lookahead.found);
|
|
570
|
+
} else {
|
|
571
|
+
val = parse_list();
|
|
572
|
+
}
|
|
573
|
+
bool is_default = false;
|
|
574
|
+
bool is_global = false;
|
|
575
|
+
while (peek< alternatives < default_flag, global_flag > >()) {
|
|
576
|
+
if (lex< default_flag >()) is_default = true;
|
|
577
|
+
else if (lex< global_flag >()) is_global = true;
|
|
578
|
+
}
|
|
579
|
+
return SASS_MEMORY_NEW(Assignment, var_source_position, name, val, is_default, is_global);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// a ruleset connects a selector and a block
|
|
583
|
+
StyleRuleObj Parser::parse_ruleset(Lookahead lookahead)
|
|
584
|
+
{
|
|
585
|
+
NESTING_GUARD(nestings);
|
|
586
|
+
// inherit is_root from parent block
|
|
587
|
+
Block_Obj parent = block_stack.back();
|
|
588
|
+
bool is_root = parent && parent->is_root();
|
|
589
|
+
// make sure to move up the the last position
|
|
590
|
+
lex < optional_css_whitespace >(false, true);
|
|
591
|
+
// create the connector object (add parts later)
|
|
592
|
+
StyleRuleObj ruleset = SASS_MEMORY_NEW(StyleRule, pstate);
|
|
593
|
+
// parse selector static or as schema to be evaluated later
|
|
594
|
+
if (lookahead.parsable) {
|
|
595
|
+
ruleset->selector(parseSelectorList(false));
|
|
596
|
+
}
|
|
597
|
+
else {
|
|
598
|
+
SelectorListObj list = SASS_MEMORY_NEW(SelectorList, pstate);
|
|
599
|
+
auto sc = parse_selector_schema(lookahead.position, false);
|
|
600
|
+
ruleset->schema(sc);
|
|
601
|
+
ruleset->selector(list);
|
|
602
|
+
}
|
|
603
|
+
// then parse the inner block
|
|
604
|
+
stack.push_back(Scope::Rules);
|
|
605
|
+
ruleset->block(parse_block());
|
|
606
|
+
stack.pop_back();
|
|
607
|
+
// update for end position
|
|
608
|
+
ruleset->update_pstate(pstate);
|
|
609
|
+
ruleset->block()->update_pstate(pstate);
|
|
610
|
+
// need this info for coherence checks
|
|
611
|
+
ruleset->is_root(is_root);
|
|
612
|
+
// return AST Node
|
|
613
|
+
return ruleset;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// parse a selector schema that will be evaluated in the eval stage
|
|
617
|
+
// uses a string schema internally to do the actual schema handling
|
|
618
|
+
// in the eval stage we will be re-parse it into an actual selector
|
|
619
|
+
Selector_Schema_Obj Parser::parse_selector_schema(const char* end_of_selector, bool chroot)
|
|
620
|
+
{
|
|
621
|
+
NESTING_GUARD(nestings);
|
|
622
|
+
// move up to the start
|
|
623
|
+
lex< optional_spaces >();
|
|
624
|
+
const char* i = position;
|
|
625
|
+
// selector schema re-uses string schema implementation
|
|
626
|
+
String_Schema* schema = SASS_MEMORY_NEW(String_Schema, pstate);
|
|
627
|
+
// the selector schema is pretty much just a wrapper for the string schema
|
|
628
|
+
Selector_Schema_Obj selector_schema = SASS_MEMORY_NEW(Selector_Schema, pstate, schema);
|
|
629
|
+
selector_schema->connect_parent(chroot == false);
|
|
630
|
+
|
|
631
|
+
// process until end
|
|
632
|
+
while (i < end_of_selector) {
|
|
633
|
+
// try to parse multiple interpolants
|
|
634
|
+
if (const char* p = find_first_in_interval< exactly<hash_lbrace>, block_comment >(i, end_of_selector)) {
|
|
635
|
+
// accumulate the preceding segment if the position has advanced
|
|
636
|
+
if (i < p) {
|
|
637
|
+
sass::string parsed(i, p);
|
|
638
|
+
String_Constant_Obj str = SASS_MEMORY_NEW(String_Constant, pstate, parsed);
|
|
639
|
+
pstate.position += Offset(parsed);
|
|
640
|
+
str->update_pstate(pstate);
|
|
641
|
+
schema->append(str);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// skip over all nested inner interpolations up to our own delimiter
|
|
645
|
+
const char* j = skip_over_scopes< exactly<hash_lbrace>, exactly<rbrace> >(p + 2, end_of_selector);
|
|
646
|
+
// check if the interpolation never ends of only contains white-space (error out)
|
|
647
|
+
if (!j || peek < sequence < optional_spaces, exactly<rbrace> > >(p+2)) {
|
|
648
|
+
position = p+2;
|
|
649
|
+
css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");
|
|
650
|
+
}
|
|
651
|
+
// pass inner expression to the parser to resolve nested interpolations
|
|
652
|
+
LocalOption<const char*> partEnd(end, j);
|
|
653
|
+
LocalOption<const char*> partBeg(position, p + 2);
|
|
654
|
+
ExpressionObj interpolant = parse_list();
|
|
655
|
+
// set status on the list expression
|
|
656
|
+
interpolant->is_interpolant(true);
|
|
657
|
+
// schema->has_interpolants(true);
|
|
658
|
+
// add to the string schema
|
|
659
|
+
schema->append(interpolant);
|
|
660
|
+
// advance parser state
|
|
661
|
+
pstate.position.add(p+2, j);
|
|
662
|
+
// advance position
|
|
663
|
+
i = j;
|
|
664
|
+
}
|
|
665
|
+
// no more interpolants have been found
|
|
666
|
+
// add the last segment if there is one
|
|
667
|
+
else {
|
|
668
|
+
// make sure to add the last bits of the string up to the end (if any)
|
|
669
|
+
if (i < end_of_selector) {
|
|
670
|
+
sass::string parsed(i, end_of_selector);
|
|
671
|
+
String_Constant_Obj str = SASS_MEMORY_NEW(String_Constant, pstate, parsed);
|
|
672
|
+
pstate.position += Offset(parsed);
|
|
673
|
+
str->update_pstate(pstate);
|
|
674
|
+
i = end_of_selector;
|
|
675
|
+
schema->append(str);
|
|
676
|
+
}
|
|
677
|
+
// exit loop
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
// EO until eos
|
|
681
|
+
|
|
682
|
+
// update position
|
|
683
|
+
position = i;
|
|
684
|
+
|
|
685
|
+
// update for end position
|
|
686
|
+
selector_schema->update_pstate(pstate);
|
|
687
|
+
schema->update_pstate(pstate);
|
|
688
|
+
|
|
689
|
+
after_token = before_token = pstate.position;
|
|
690
|
+
|
|
691
|
+
// return parsed result
|
|
692
|
+
return selector_schema.detach();
|
|
693
|
+
}
|
|
694
|
+
// EO parse_selector_schema
|
|
695
|
+
|
|
696
|
+
void Parser::parse_charset_directive()
|
|
697
|
+
{
|
|
698
|
+
lex <
|
|
699
|
+
sequence <
|
|
700
|
+
quoted_string,
|
|
701
|
+
optional_spaces,
|
|
702
|
+
exactly <';'>
|
|
703
|
+
>
|
|
704
|
+
>();
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// called after parsing `kwd_include_directive`
|
|
708
|
+
Mixin_Call_Obj Parser::parse_include_directive()
|
|
709
|
+
{
|
|
710
|
+
// lex identifier into `lexed` var
|
|
711
|
+
lex_identifier(); // may error out
|
|
712
|
+
// normalize underscores to hyphens
|
|
713
|
+
sass::string name(Util::normalize_underscores(lexed));
|
|
714
|
+
// create the initial mixin call object
|
|
715
|
+
Mixin_Call_Obj call = SASS_MEMORY_NEW(Mixin_Call, pstate, name, Arguments_Obj{});
|
|
716
|
+
// parse mandatory arguments
|
|
717
|
+
call->arguments(parse_arguments());
|
|
718
|
+
// parse using and optional block parameters
|
|
719
|
+
bool has_parameters = lex< kwd_using >() != nullptr;
|
|
720
|
+
|
|
721
|
+
if (has_parameters) {
|
|
722
|
+
if (!peek< exactly<'('> >()) css_error("Invalid CSS", " after ", ": expected \"(\", was ");
|
|
723
|
+
} else {
|
|
724
|
+
if (peek< exactly<'('> >()) css_error("Invalid CSS", " after ", ": expected \";\", was ");
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
if (has_parameters) call->block_parameters(parse_parameters());
|
|
728
|
+
|
|
729
|
+
// parse optional block
|
|
730
|
+
if (peek < exactly <'{'> >()) {
|
|
731
|
+
call->block(parse_block());
|
|
732
|
+
}
|
|
733
|
+
else if (has_parameters) {
|
|
734
|
+
css_error("Invalid CSS", " after ", ": expected \"{\", was ");
|
|
735
|
+
}
|
|
736
|
+
// return ast node
|
|
737
|
+
return call.detach();
|
|
738
|
+
}
|
|
739
|
+
// EO parse_include_directive
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
SimpleSelectorObj Parser::parse_simple_selector()
|
|
743
|
+
{
|
|
744
|
+
lex < css_comments >(false);
|
|
745
|
+
if (lex< class_name >()) {
|
|
746
|
+
return SASS_MEMORY_NEW(ClassSelector, pstate, lexed);
|
|
747
|
+
}
|
|
748
|
+
else if (lex< id_name >()) {
|
|
749
|
+
return SASS_MEMORY_NEW(IDSelector, pstate, lexed);
|
|
750
|
+
}
|
|
751
|
+
else if (lex< alternatives < variable, number, static_reference_combinator > >()) {
|
|
752
|
+
return SASS_MEMORY_NEW(TypeSelector, pstate, lexed);
|
|
753
|
+
}
|
|
754
|
+
else if (peek< pseudo_not >()) {
|
|
755
|
+
return parse_negated_selector2();
|
|
756
|
+
}
|
|
757
|
+
else if (peek< re_pseudo_selector >()) {
|
|
758
|
+
return parse_pseudo_selector();
|
|
759
|
+
}
|
|
760
|
+
else if (peek< exactly<':'> >()) {
|
|
761
|
+
return parse_pseudo_selector();
|
|
762
|
+
}
|
|
763
|
+
else if (lex < exactly<'['> >()) {
|
|
764
|
+
return parse_attribute_selector();
|
|
765
|
+
}
|
|
766
|
+
else if (lex< placeholder >()) {
|
|
767
|
+
return SASS_MEMORY_NEW(PlaceholderSelector, pstate, lexed);
|
|
768
|
+
}
|
|
769
|
+
else {
|
|
770
|
+
css_error("Invalid CSS", " after ", ": expected selector, was ");
|
|
771
|
+
}
|
|
772
|
+
// failed
|
|
773
|
+
return {};
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
PseudoSelectorObj Parser::parse_negated_selector2()
|
|
777
|
+
{
|
|
778
|
+
lex< pseudo_not >();
|
|
779
|
+
sass::string name(lexed);
|
|
780
|
+
SourceSpan nsource_position = pstate;
|
|
781
|
+
SelectorListObj negated = parseSelectorList(true);
|
|
782
|
+
if (!lex< exactly<')'> >()) {
|
|
783
|
+
error("negated selector is missing ')'");
|
|
784
|
+
}
|
|
785
|
+
name.erase(name.size() - 1);
|
|
786
|
+
|
|
787
|
+
PseudoSelector* sel = SASS_MEMORY_NEW(PseudoSelector, nsource_position, name.substr(1));
|
|
788
|
+
sel->selector(negated);
|
|
789
|
+
return sel;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// Helper to clean binominal string
|
|
793
|
+
bool BothAreSpaces(char lhs, char rhs) { return isspace(lhs) && isspace(rhs); }
|
|
794
|
+
|
|
795
|
+
// a pseudo selector often starts with one or two colons
|
|
796
|
+
// it can contain more selectors inside parentheses
|
|
797
|
+
SimpleSelectorObj Parser::parse_pseudo_selector() {
|
|
798
|
+
|
|
799
|
+
// Lex one or two colon characters
|
|
800
|
+
if (lex<pseudo_prefix>()) {
|
|
801
|
+
sass::string colons(lexed);
|
|
802
|
+
// Check if it is a pseudo element
|
|
803
|
+
bool element = colons.size() == 2;
|
|
804
|
+
|
|
805
|
+
if (lex< sequence<
|
|
806
|
+
// we keep the space within the name, strange enough
|
|
807
|
+
// ToDo: refactor output to schedule the space for it
|
|
808
|
+
// or do we really want to keep the real white-space?
|
|
809
|
+
sequence< identifier, optional < block_comment >, exactly<'('> >
|
|
810
|
+
> >())
|
|
811
|
+
{
|
|
812
|
+
|
|
813
|
+
sass::string name(lexed);
|
|
814
|
+
name.erase(name.size() - 1);
|
|
815
|
+
SourceSpan p = pstate;
|
|
816
|
+
|
|
817
|
+
// specially parse nth-child pseudo selectors
|
|
818
|
+
if (lex_css < sequence < binomial, word_boundary >>()) {
|
|
819
|
+
sass::string parsed(lexed); // always compacting binominals (as dart-sass)
|
|
820
|
+
parsed.erase(std::unique(parsed.begin(), parsed.end(), BothAreSpaces), parsed.end());
|
|
821
|
+
String_Constant_Obj arg = SASS_MEMORY_NEW(String_Constant, pstate, parsed);
|
|
822
|
+
PseudoSelector* pseudo = SASS_MEMORY_NEW(PseudoSelector, p, name, element);
|
|
823
|
+
if (lex < sequence < css_whitespace, insensitive < of_kwd >>>(false)) {
|
|
824
|
+
pseudo->selector(parseSelectorList(true));
|
|
825
|
+
}
|
|
826
|
+
pseudo->argument(arg);
|
|
827
|
+
if (lex_css< exactly<')'> >()) {
|
|
828
|
+
return pseudo;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
else {
|
|
832
|
+
if (peek_css< exactly<')'>>() && Util::equalsLiteral("nth-", name.substr(0, 4))) {
|
|
833
|
+
css_error("Invalid CSS", " after ", ": expected An+B expression, was ");
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
sass::string unvendored = Util::unvendor(name);
|
|
837
|
+
|
|
838
|
+
if (unvendored == "not" || unvendored == "matches" || unvendored == "current" || unvendored == "any" || unvendored == "has" || unvendored == "host" || unvendored == "host-context" || unvendored == "slotted") {
|
|
839
|
+
if (SelectorListObj wrapped = parseSelectorList(true)) {
|
|
840
|
+
if (wrapped && lex_css< exactly<')'> >()) {
|
|
841
|
+
PseudoSelector* pseudo = SASS_MEMORY_NEW(PseudoSelector, p, name, element);
|
|
842
|
+
pseudo->selector(wrapped);
|
|
843
|
+
return pseudo;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
} else {
|
|
847
|
+
String_Schema_Obj arg = parse_css_variable_value();
|
|
848
|
+
PseudoSelector* pseudo = SASS_MEMORY_NEW(PseudoSelector, p, name, element);
|
|
849
|
+
pseudo->argument(arg);
|
|
850
|
+
|
|
851
|
+
if (lex_css< exactly<')'> >()) {
|
|
852
|
+
return pseudo;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
}
|
|
858
|
+
// EO if pseudo selector
|
|
859
|
+
|
|
860
|
+
else if (lex < sequence< optional < pseudo_prefix >, identifier > >()) {
|
|
861
|
+
return SASS_MEMORY_NEW(PseudoSelector, pstate, lexed, element);
|
|
862
|
+
}
|
|
863
|
+
else if (lex < pseudo_prefix >()) {
|
|
864
|
+
css_error("Invalid CSS", " after ", ": expected pseudoclass or pseudoelement, was ");
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
}
|
|
868
|
+
else {
|
|
869
|
+
lex < identifier >(); // needed for error message?
|
|
870
|
+
css_error("Invalid CSS", " after ", ": expected selector, was ");
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
|
|
874
|
+
css_error("Invalid CSS", " after ", ": expected \")\", was ");
|
|
875
|
+
|
|
876
|
+
// unreachable statement
|
|
877
|
+
return {};
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
const char* Parser::re_attr_sensitive_close(const char* src)
|
|
881
|
+
{
|
|
882
|
+
return alternatives < exactly<']'>, exactly<'/'> >(src);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
const char* Parser::re_attr_insensitive_close(const char* src)
|
|
886
|
+
{
|
|
887
|
+
return sequence < insensitive<'i'>, re_attr_sensitive_close >(src);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
AttributeSelectorObj Parser::parse_attribute_selector()
|
|
891
|
+
{
|
|
892
|
+
SourceSpan p = pstate;
|
|
893
|
+
if (!lex_css< attribute_name >()) error("invalid attribute name in attribute selector");
|
|
894
|
+
sass::string name(lexed);
|
|
895
|
+
if (lex_css< re_attr_sensitive_close >()) {
|
|
896
|
+
return SASS_MEMORY_NEW(AttributeSelector, p, name, "", String_Obj{});
|
|
897
|
+
}
|
|
898
|
+
else if (lex_css< re_attr_insensitive_close >()) {
|
|
899
|
+
char modifier = lexed.begin[0];
|
|
900
|
+
return SASS_MEMORY_NEW(AttributeSelector, p, name, "", String_Obj{}, modifier);
|
|
901
|
+
}
|
|
902
|
+
if (!lex_css< alternatives< exact_match, class_match, dash_match,
|
|
903
|
+
prefix_match, suffix_match, substring_match > >()) {
|
|
904
|
+
error("invalid operator in attribute selector for " + name);
|
|
905
|
+
}
|
|
906
|
+
sass::string matcher(lexed);
|
|
907
|
+
|
|
908
|
+
String_Obj value;
|
|
909
|
+
if (lex_css< identifier >()) {
|
|
910
|
+
value = SASS_MEMORY_NEW(String_Constant, p, lexed);
|
|
911
|
+
}
|
|
912
|
+
else if (lex_css< quoted_string >()) {
|
|
913
|
+
value = parse_interpolated_chunk(lexed, true); // needed!
|
|
914
|
+
}
|
|
915
|
+
else {
|
|
916
|
+
error("expected a string constant or identifier in attribute selector for " + name);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
if (lex_css< re_attr_sensitive_close >()) {
|
|
920
|
+
return SASS_MEMORY_NEW(AttributeSelector, p, name, matcher, value, 0);
|
|
921
|
+
}
|
|
922
|
+
else if (lex_css< re_attr_insensitive_close >()) {
|
|
923
|
+
char modifier = lexed.begin[0];
|
|
924
|
+
return SASS_MEMORY_NEW(AttributeSelector, p, name, matcher, value, modifier);
|
|
925
|
+
}
|
|
926
|
+
error("unterminated attribute selector for " + name);
|
|
927
|
+
return {}; // to satisfy compilers (error must not return)
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
/* parse block comment and add to block */
|
|
931
|
+
void Parser::parse_block_comments(bool store)
|
|
932
|
+
{
|
|
933
|
+
Block_Obj block = block_stack.back();
|
|
934
|
+
|
|
935
|
+
while (lex< block_comment >()) {
|
|
936
|
+
bool is_important = lexed.begin[2] == '!';
|
|
937
|
+
// flag on second param is to skip loosely over comments
|
|
938
|
+
String_Obj contents = parse_interpolated_chunk(lexed, true, false);
|
|
939
|
+
if (store) block->append(SASS_MEMORY_NEW(Comment, pstate, contents, is_important));
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
Declaration_Obj Parser::parse_declaration() {
|
|
944
|
+
String_Obj prop;
|
|
945
|
+
bool is_custom_property = false;
|
|
946
|
+
if (lex< sequence< optional< exactly<'*'> >, identifier_schema > >()) {
|
|
947
|
+
const sass::string property(lexed);
|
|
948
|
+
is_custom_property = property.compare(0, 2, "--") == 0;
|
|
949
|
+
prop = parse_identifier_schema();
|
|
950
|
+
}
|
|
951
|
+
else if (lex< sequence< optional< exactly<'*'> >, identifier, zero_plus< block_comment > > >()) {
|
|
952
|
+
const sass::string property(lexed);
|
|
953
|
+
is_custom_property = property.compare(0, 2, "--") == 0;
|
|
954
|
+
prop = SASS_MEMORY_NEW(String_Constant, pstate, lexed);
|
|
955
|
+
}
|
|
956
|
+
else {
|
|
957
|
+
css_error("Invalid CSS", " after ", ": expected \"}\", was ");
|
|
958
|
+
}
|
|
959
|
+
bool is_indented = true;
|
|
960
|
+
const sass::string property(lexed);
|
|
961
|
+
if (!lex_css< one_plus< exactly<':'> > >()) error("property \"" + escape_string(property) + "\" must be followed by a ':'");
|
|
962
|
+
if (!is_custom_property && match< sequence< optional_css_comments, exactly<';'> > >()) error("style declaration must contain a value");
|
|
963
|
+
if (match< sequence< optional_css_comments, exactly<'{'> > >()) is_indented = false; // don't indent if value is empty
|
|
964
|
+
if (is_custom_property) {
|
|
965
|
+
return SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, parse_css_variable_value(), false, true);
|
|
966
|
+
}
|
|
967
|
+
lex < css_comments >(false);
|
|
968
|
+
if (peek_css< static_value >()) {
|
|
969
|
+
return SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, parse_static_value()/*, lex<kwd_important>()*/);
|
|
970
|
+
}
|
|
971
|
+
else {
|
|
972
|
+
ExpressionObj value;
|
|
973
|
+
Lookahead lookahead = lookahead_for_value(position);
|
|
974
|
+
if (lookahead.found) {
|
|
975
|
+
if (lookahead.has_interpolants) {
|
|
976
|
+
value = parse_value_schema(lookahead.found);
|
|
977
|
+
} else {
|
|
978
|
+
value = parse_list(DELAYED);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
else {
|
|
982
|
+
value = parse_list(DELAYED);
|
|
983
|
+
if (List* list = Cast<List>(value)) {
|
|
984
|
+
if (!list->is_bracketed() && list->length() == 0 && !peek< exactly <'{'> >()) {
|
|
985
|
+
css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
lex < css_comments >(false);
|
|
990
|
+
Declaration_Obj decl = SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, value/*, lex<kwd_important>()*/);
|
|
991
|
+
decl->is_indented(is_indented);
|
|
992
|
+
decl->update_pstate(pstate);
|
|
993
|
+
return decl;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
ExpressionObj Parser::parse_map()
|
|
998
|
+
{
|
|
999
|
+
NESTING_GUARD(nestings);
|
|
1000
|
+
ExpressionObj key = parse_list();
|
|
1001
|
+
List_Obj map = SASS_MEMORY_NEW(List, pstate, 0, SASS_HASH);
|
|
1002
|
+
|
|
1003
|
+
// it's not a map so return the lexed value as a list value
|
|
1004
|
+
if (!lex_css< exactly<':'> >())
|
|
1005
|
+
{ return key; }
|
|
1006
|
+
|
|
1007
|
+
List_Obj l = Cast<List>(key);
|
|
1008
|
+
if (l && l->separator() == SASS_COMMA) {
|
|
1009
|
+
css_error("Invalid CSS", " after ", ": expected \")\", was ");
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
ExpressionObj value = parse_space_list();
|
|
1013
|
+
|
|
1014
|
+
map->append(key);
|
|
1015
|
+
map->append(value);
|
|
1016
|
+
|
|
1017
|
+
while (lex_css< exactly<','> >())
|
|
1018
|
+
{
|
|
1019
|
+
// allow trailing commas - #495
|
|
1020
|
+
if (peek_css< exactly<')'> >(position))
|
|
1021
|
+
{ break; }
|
|
1022
|
+
|
|
1023
|
+
key = parse_space_list();
|
|
1024
|
+
|
|
1025
|
+
if (!(lex< exactly<':'> >()))
|
|
1026
|
+
{ css_error("Invalid CSS", " after ", ": expected \":\", was "); }
|
|
1027
|
+
|
|
1028
|
+
value = parse_space_list();
|
|
1029
|
+
|
|
1030
|
+
map->append(key);
|
|
1031
|
+
map->append(value);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
SourceSpan ps = map->pstate();
|
|
1035
|
+
ps.offset = pstate.position - ps.position + pstate.offset;
|
|
1036
|
+
map->pstate(ps);
|
|
1037
|
+
|
|
1038
|
+
return map;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
ExpressionObj Parser::parse_bracket_list()
|
|
1042
|
+
{
|
|
1043
|
+
NESTING_GUARD(nestings);
|
|
1044
|
+
// check if we have an empty list
|
|
1045
|
+
// return the empty list as such
|
|
1046
|
+
if (peek_css< list_terminator >(position))
|
|
1047
|
+
{
|
|
1048
|
+
// return an empty list (nothing to delay)
|
|
1049
|
+
return SASS_MEMORY_NEW(List, pstate, 0, SASS_SPACE, false, true);
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
bool has_paren = peek_css< exactly<'('> >() != NULL;
|
|
1053
|
+
|
|
1054
|
+
// now try to parse a space list
|
|
1055
|
+
ExpressionObj list = parse_space_list();
|
|
1056
|
+
// if it's a singleton, return it (don't wrap it)
|
|
1057
|
+
if (!peek_css< exactly<','> >(position)) {
|
|
1058
|
+
List_Obj l = Cast<List>(list);
|
|
1059
|
+
if (!l || l->is_bracketed() || has_paren) {
|
|
1060
|
+
List_Obj bracketed_list = SASS_MEMORY_NEW(List, pstate, 1, SASS_SPACE, false, true);
|
|
1061
|
+
bracketed_list->append(list);
|
|
1062
|
+
return bracketed_list;
|
|
1063
|
+
}
|
|
1064
|
+
l->is_bracketed(true);
|
|
1065
|
+
return l;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
// if we got so far, we actually do have a comma list
|
|
1069
|
+
List_Obj bracketed_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_COMMA, false, true);
|
|
1070
|
+
// wrap the first expression
|
|
1071
|
+
bracketed_list->append(list);
|
|
1072
|
+
|
|
1073
|
+
while (lex_css< exactly<','> >())
|
|
1074
|
+
{
|
|
1075
|
+
// check for abort condition
|
|
1076
|
+
if (peek_css< list_terminator >(position)
|
|
1077
|
+
) { break; }
|
|
1078
|
+
// otherwise add another expression
|
|
1079
|
+
bracketed_list->append(parse_space_list());
|
|
1080
|
+
}
|
|
1081
|
+
// return the list
|
|
1082
|
+
return bracketed_list;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
// parse list returns either a space separated list,
|
|
1086
|
+
// a comma separated list or any bare expression found.
|
|
1087
|
+
// so to speak: we unwrap items from lists if possible here!
|
|
1088
|
+
ExpressionObj Parser::parse_list(bool delayed)
|
|
1089
|
+
{
|
|
1090
|
+
NESTING_GUARD(nestings);
|
|
1091
|
+
return parse_comma_list(delayed);
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// will return singletons unwrapped
|
|
1095
|
+
ExpressionObj Parser::parse_comma_list(bool delayed)
|
|
1096
|
+
{
|
|
1097
|
+
NESTING_GUARD(nestings);
|
|
1098
|
+
// check if we have an empty list
|
|
1099
|
+
// return the empty list as such
|
|
1100
|
+
if (peek_css< list_terminator >(position))
|
|
1101
|
+
{
|
|
1102
|
+
// return an empty list (nothing to delay)
|
|
1103
|
+
return SASS_MEMORY_NEW(List, pstate, 0);
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
// now try to parse a space list
|
|
1107
|
+
ExpressionObj list = parse_space_list();
|
|
1108
|
+
// if it's a singleton, return it (don't wrap it)
|
|
1109
|
+
if (!peek_css< exactly<','> >(position)) {
|
|
1110
|
+
// set_delay doesn't apply to list children
|
|
1111
|
+
// so this will only undelay single values
|
|
1112
|
+
if (!delayed) list->set_delayed(false);
|
|
1113
|
+
return list;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// if we got so far, we actually do have a comma list
|
|
1117
|
+
List_Obj comma_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_COMMA);
|
|
1118
|
+
// wrap the first expression
|
|
1119
|
+
comma_list->append(list);
|
|
1120
|
+
|
|
1121
|
+
while (lex_css< exactly<','> >())
|
|
1122
|
+
{
|
|
1123
|
+
// check for abort condition
|
|
1124
|
+
if (peek_css< list_terminator >(position)
|
|
1125
|
+
) { break; }
|
|
1126
|
+
// otherwise add another expression
|
|
1127
|
+
comma_list->append(parse_space_list());
|
|
1128
|
+
}
|
|
1129
|
+
// return the list
|
|
1130
|
+
return comma_list;
|
|
1131
|
+
}
|
|
1132
|
+
// EO parse_comma_list
|
|
1133
|
+
|
|
1134
|
+
// will return singletons unwrapped
|
|
1135
|
+
ExpressionObj Parser::parse_space_list()
|
|
1136
|
+
{
|
|
1137
|
+
NESTING_GUARD(nestings);
|
|
1138
|
+
ExpressionObj disj1 = parse_disjunction();
|
|
1139
|
+
// if it's a singleton, return it (don't wrap it)
|
|
1140
|
+
if (peek_css< space_list_terminator >(position)
|
|
1141
|
+
) {
|
|
1142
|
+
return disj1; }
|
|
1143
|
+
|
|
1144
|
+
List_Obj space_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_SPACE);
|
|
1145
|
+
space_list->append(disj1);
|
|
1146
|
+
|
|
1147
|
+
while (
|
|
1148
|
+
!(peek_css< space_list_terminator >(position)) &&
|
|
1149
|
+
peek_css< optional_css_whitespace >() != end
|
|
1150
|
+
) {
|
|
1151
|
+
// the space is parsed implicitly?
|
|
1152
|
+
space_list->append(parse_disjunction());
|
|
1153
|
+
}
|
|
1154
|
+
// return the list
|
|
1155
|
+
return space_list;
|
|
1156
|
+
}
|
|
1157
|
+
// EO parse_space_list
|
|
1158
|
+
|
|
1159
|
+
// parse logical OR operation
|
|
1160
|
+
ExpressionObj Parser::parse_disjunction()
|
|
1161
|
+
{
|
|
1162
|
+
NESTING_GUARD(nestings);
|
|
1163
|
+
advanceToNextToken();
|
|
1164
|
+
SourceSpan state(pstate);
|
|
1165
|
+
// parse the left hand side conjunction
|
|
1166
|
+
ExpressionObj conj = parse_conjunction();
|
|
1167
|
+
// parse multiple right hand sides
|
|
1168
|
+
sass::vector<ExpressionObj> operands;
|
|
1169
|
+
while (lex_css< kwd_or >())
|
|
1170
|
+
operands.push_back(parse_conjunction());
|
|
1171
|
+
// if it's a singleton, return it directly
|
|
1172
|
+
if (operands.size() == 0) return conj;
|
|
1173
|
+
// fold all operands into one binary expression
|
|
1174
|
+
ExpressionObj ex = fold_operands(conj, operands, { Sass_OP::OR });
|
|
1175
|
+
state.offset = pstate.position - state.position + pstate.offset;
|
|
1176
|
+
ex->pstate(state);
|
|
1177
|
+
return ex;
|
|
1178
|
+
}
|
|
1179
|
+
// EO parse_disjunction
|
|
1180
|
+
|
|
1181
|
+
// parse logical AND operation
|
|
1182
|
+
ExpressionObj Parser::parse_conjunction()
|
|
1183
|
+
{
|
|
1184
|
+
NESTING_GUARD(nestings);
|
|
1185
|
+
advanceToNextToken();
|
|
1186
|
+
SourceSpan state(pstate);
|
|
1187
|
+
// parse the left hand side relation
|
|
1188
|
+
ExpressionObj rel = parse_relation();
|
|
1189
|
+
// parse multiple right hand sides
|
|
1190
|
+
sass::vector<ExpressionObj> operands;
|
|
1191
|
+
while (lex_css< kwd_and >()) {
|
|
1192
|
+
operands.push_back(parse_relation());
|
|
1193
|
+
}
|
|
1194
|
+
// if it's a singleton, return it directly
|
|
1195
|
+
if (operands.size() == 0) return rel;
|
|
1196
|
+
// fold all operands into one binary expression
|
|
1197
|
+
ExpressionObj ex = fold_operands(rel, operands, { Sass_OP::AND });
|
|
1198
|
+
state.offset = pstate.position - state.position + pstate.offset;
|
|
1199
|
+
ex->pstate(state);
|
|
1200
|
+
return ex;
|
|
1201
|
+
}
|
|
1202
|
+
// EO parse_conjunction
|
|
1203
|
+
|
|
1204
|
+
// parse comparison operations
|
|
1205
|
+
ExpressionObj Parser::parse_relation()
|
|
1206
|
+
{
|
|
1207
|
+
NESTING_GUARD(nestings);
|
|
1208
|
+
advanceToNextToken();
|
|
1209
|
+
SourceSpan state(pstate);
|
|
1210
|
+
// parse the left hand side expression
|
|
1211
|
+
ExpressionObj lhs = parse_expression();
|
|
1212
|
+
sass::vector<ExpressionObj> operands;
|
|
1213
|
+
sass::vector<Operand> operators;
|
|
1214
|
+
// if it's a singleton, return it (don't wrap it)
|
|
1215
|
+
while (peek< alternatives <
|
|
1216
|
+
kwd_eq,
|
|
1217
|
+
kwd_neq,
|
|
1218
|
+
kwd_gte,
|
|
1219
|
+
kwd_gt,
|
|
1220
|
+
kwd_lte,
|
|
1221
|
+
kwd_lt
|
|
1222
|
+
> >(position))
|
|
1223
|
+
{
|
|
1224
|
+
// is directly adjancent to expression?
|
|
1225
|
+
bool left_ws = peek < css_comments >() != NULL;
|
|
1226
|
+
// parse the operator
|
|
1227
|
+
enum Sass_OP op
|
|
1228
|
+
= lex<kwd_eq>() ? Sass_OP::EQ
|
|
1229
|
+
: lex<kwd_neq>() ? Sass_OP::NEQ
|
|
1230
|
+
: lex<kwd_gte>() ? Sass_OP::GTE
|
|
1231
|
+
: lex<kwd_lte>() ? Sass_OP::LTE
|
|
1232
|
+
: lex<kwd_gt>() ? Sass_OP::GT
|
|
1233
|
+
: lex<kwd_lt>() ? Sass_OP::LT
|
|
1234
|
+
// we checked the possibilities on top of fn
|
|
1235
|
+
: Sass_OP::EQ;
|
|
1236
|
+
// is directly adjacent to expression?
|
|
1237
|
+
bool right_ws = peek < css_comments >() != NULL;
|
|
1238
|
+
operators.push_back({ op, left_ws, right_ws });
|
|
1239
|
+
operands.push_back(parse_expression());
|
|
1240
|
+
}
|
|
1241
|
+
// we are called recursively for list, so we first
|
|
1242
|
+
// fold inner binary expression which has delayed
|
|
1243
|
+
// correctly set to zero. After folding we also unwrap
|
|
1244
|
+
// single nested items. So we cannot set delay on the
|
|
1245
|
+
// returned result here, as we have lost nestings ...
|
|
1246
|
+
ExpressionObj ex = fold_operands(lhs, operands, operators);
|
|
1247
|
+
state.offset = pstate.position - state.position + pstate.offset;
|
|
1248
|
+
ex->pstate(state);
|
|
1249
|
+
return ex;
|
|
1250
|
+
}
|
|
1251
|
+
// parse_relation
|
|
1252
|
+
|
|
1253
|
+
// parse expression valid for operations
|
|
1254
|
+
// called from parse_relation
|
|
1255
|
+
// called from parse_for_directive
|
|
1256
|
+
// called from parse_media_expression
|
|
1257
|
+
// parse addition and subtraction operations
|
|
1258
|
+
ExpressionObj Parser::parse_expression()
|
|
1259
|
+
{
|
|
1260
|
+
NESTING_GUARD(nestings);
|
|
1261
|
+
advanceToNextToken();
|
|
1262
|
+
SourceSpan state(pstate);
|
|
1263
|
+
// parses multiple add and subtract operations
|
|
1264
|
+
// NOTE: make sure that identifiers starting with
|
|
1265
|
+
// NOTE: dashes do NOT count as subtract operation
|
|
1266
|
+
ExpressionObj lhs = parse_operators();
|
|
1267
|
+
// if it's a singleton, return it (don't wrap it)
|
|
1268
|
+
if (!(peek_css< exactly<'+'> >(position) ||
|
|
1269
|
+
// condition is a bit mysterious, but some combinations should not be counted as operations
|
|
1270
|
+
(peek< no_spaces >(position) && peek< sequence< negate< unsigned_number >, exactly<'-'>, negate< space > > >(position)) ||
|
|
1271
|
+
(peek< sequence< negate< unsigned_number >, exactly<'-'>, negate< unsigned_number > > >(position))) ||
|
|
1272
|
+
peek< sequence < zero_plus < exactly <'-' > >, identifier > >(position))
|
|
1273
|
+
{ return lhs; }
|
|
1274
|
+
|
|
1275
|
+
sass::vector<ExpressionObj> operands;
|
|
1276
|
+
sass::vector<Operand> operators;
|
|
1277
|
+
bool left_ws = peek < css_comments >() != NULL;
|
|
1278
|
+
while (
|
|
1279
|
+
lex_css< exactly<'+'> >() ||
|
|
1280
|
+
|
|
1281
|
+
(
|
|
1282
|
+
! peek_css< sequence < zero_plus < exactly <'-' > >, identifier > >(position)
|
|
1283
|
+
&& lex_css< sequence< negate< digit >, exactly<'-'> > >()
|
|
1284
|
+
)
|
|
1285
|
+
|
|
1286
|
+
) {
|
|
1287
|
+
|
|
1288
|
+
bool right_ws = peek < css_comments >() != NULL;
|
|
1289
|
+
operators.push_back({ lexed.to_string() == "+" ? Sass_OP::ADD : Sass_OP::SUB, left_ws, right_ws });
|
|
1290
|
+
operands.push_back(parse_operators());
|
|
1291
|
+
left_ws = peek < css_comments >() != NULL;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
if (operands.size() == 0) return lhs;
|
|
1295
|
+
ExpressionObj ex = fold_operands(lhs, operands, operators);
|
|
1296
|
+
state.offset = pstate.position - state.position + pstate.offset;
|
|
1297
|
+
ex->pstate(state);
|
|
1298
|
+
return ex;
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
// parse addition and subtraction operations
|
|
1302
|
+
ExpressionObj Parser::parse_operators()
|
|
1303
|
+
{
|
|
1304
|
+
NESTING_GUARD(nestings);
|
|
1305
|
+
advanceToNextToken();
|
|
1306
|
+
SourceSpan state(pstate);
|
|
1307
|
+
ExpressionObj factor = parse_factor();
|
|
1308
|
+
// if it's a singleton, return it (don't wrap it)
|
|
1309
|
+
sass::vector<ExpressionObj> operands; // factors
|
|
1310
|
+
sass::vector<Operand> operators; // ops
|
|
1311
|
+
// lex operations to apply to lhs
|
|
1312
|
+
const char* left_ws = peek < css_comments >();
|
|
1313
|
+
while (lex_css< class_char< static_ops > >()) {
|
|
1314
|
+
const char* right_ws = peek < css_comments >();
|
|
1315
|
+
switch(*lexed.begin) {
|
|
1316
|
+
case '*': operators.push_back({ Sass_OP::MUL, left_ws != 0, right_ws != 0 }); break;
|
|
1317
|
+
case '/': operators.push_back({ Sass_OP::DIV, left_ws != 0, right_ws != 0 }); break;
|
|
1318
|
+
case '%': operators.push_back({ Sass_OP::MOD, left_ws != 0, right_ws != 0 }); break;
|
|
1319
|
+
default: throw std::runtime_error("unknown static op parsed");
|
|
1320
|
+
}
|
|
1321
|
+
operands.push_back(parse_factor());
|
|
1322
|
+
left_ws = peek < css_comments >();
|
|
1323
|
+
}
|
|
1324
|
+
// operands and operators to binary expression
|
|
1325
|
+
ExpressionObj ex = fold_operands(factor, operands, operators);
|
|
1326
|
+
state.offset = pstate.position - state.position + pstate.offset;
|
|
1327
|
+
ex->pstate(state);
|
|
1328
|
+
return ex;
|
|
1329
|
+
}
|
|
1330
|
+
// EO parse_operators
|
|
1331
|
+
|
|
1332
|
+
|
|
1333
|
+
// called from parse_operators
|
|
1334
|
+
// called from parse_value_schema
|
|
1335
|
+
ExpressionObj Parser::parse_factor()
|
|
1336
|
+
{
|
|
1337
|
+
NESTING_GUARD(nestings);
|
|
1338
|
+
lex < css_comments >(false);
|
|
1339
|
+
if (lex_css< exactly<'('> >()) {
|
|
1340
|
+
// parse_map may return a list
|
|
1341
|
+
ExpressionObj value = parse_map();
|
|
1342
|
+
// lex the expected closing parenthesis
|
|
1343
|
+
if (!lex_css< exactly<')'> >()) error("unclosed parenthesis");
|
|
1344
|
+
// expression can be evaluated
|
|
1345
|
+
return value;
|
|
1346
|
+
}
|
|
1347
|
+
else if (lex_css< exactly<'['> >()) {
|
|
1348
|
+
// explicit bracketed
|
|
1349
|
+
ExpressionObj value = parse_bracket_list();
|
|
1350
|
+
// lex the expected closing square bracket
|
|
1351
|
+
if (!lex_css< exactly<']'> >()) error("unclosed squared bracket");
|
|
1352
|
+
return value;
|
|
1353
|
+
}
|
|
1354
|
+
// string may be interpolated
|
|
1355
|
+
// if (lex< quoted_string >()) {
|
|
1356
|
+
// return &parse_string();
|
|
1357
|
+
// }
|
|
1358
|
+
else if (peek< ie_property >()) {
|
|
1359
|
+
return parse_ie_property();
|
|
1360
|
+
}
|
|
1361
|
+
else if (peek< ie_keyword_arg >()) {
|
|
1362
|
+
return parse_ie_keyword_arg();
|
|
1363
|
+
}
|
|
1364
|
+
else if (peek< sequence < calc_fn_call, exactly <'('> > >()) {
|
|
1365
|
+
return parse_calc_function();
|
|
1366
|
+
}
|
|
1367
|
+
else if (lex < functional_schema >()) {
|
|
1368
|
+
return parse_function_call_schema();
|
|
1369
|
+
}
|
|
1370
|
+
else if (lex< identifier_schema >()) {
|
|
1371
|
+
String_Obj string = parse_identifier_schema();
|
|
1372
|
+
if (String_Schema* schema = Cast<String_Schema>(string)) {
|
|
1373
|
+
if (lex < exactly < '(' > >()) {
|
|
1374
|
+
schema->append(parse_list());
|
|
1375
|
+
lex < exactly < ')' > >();
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
return string;
|
|
1379
|
+
}
|
|
1380
|
+
else if (peek< sequence< uri_prefix, W, real_uri_value > >()) {
|
|
1381
|
+
return parse_url_function_string();
|
|
1382
|
+
}
|
|
1383
|
+
else if (peek< re_functional >()) {
|
|
1384
|
+
return parse_function_call();
|
|
1385
|
+
}
|
|
1386
|
+
else if (lex< exactly<'+'> >()) {
|
|
1387
|
+
Unary_Expression* ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::PLUS, parse_factor());
|
|
1388
|
+
if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed());
|
|
1389
|
+
return ex;
|
|
1390
|
+
}
|
|
1391
|
+
else if (lex< exactly<'-'> >()) {
|
|
1392
|
+
Unary_Expression* ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::MINUS, parse_factor());
|
|
1393
|
+
if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed());
|
|
1394
|
+
return ex;
|
|
1395
|
+
}
|
|
1396
|
+
else if (lex< exactly<'/'> >()) {
|
|
1397
|
+
Unary_Expression* ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::SLASH, parse_factor());
|
|
1398
|
+
if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed());
|
|
1399
|
+
return ex;
|
|
1400
|
+
}
|
|
1401
|
+
else if (lex< sequence< kwd_not > >()) {
|
|
1402
|
+
Unary_Expression* ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::NOT, parse_factor());
|
|
1403
|
+
if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed());
|
|
1404
|
+
return ex;
|
|
1405
|
+
}
|
|
1406
|
+
else {
|
|
1407
|
+
return parse_value();
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
bool number_has_zero(const sass::string& parsed)
|
|
1412
|
+
{
|
|
1413
|
+
size_t L = parsed.length();
|
|
1414
|
+
return !( (L > 0 && parsed.substr(0, 1) == ".") ||
|
|
1415
|
+
(L > 1 && parsed.substr(0, 2) == "0.") ||
|
|
1416
|
+
(L > 1 && parsed.substr(0, 2) == "-.") ||
|
|
1417
|
+
(L > 2 && parsed.substr(0, 3) == "-0.") );
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
Number* Parser::lexed_number(const SourceSpan& pstate, const sass::string& parsed)
|
|
1421
|
+
{
|
|
1422
|
+
Number* nr = SASS_MEMORY_NEW(Number,
|
|
1423
|
+
pstate,
|
|
1424
|
+
sass_strtod(parsed.c_str()),
|
|
1425
|
+
"",
|
|
1426
|
+
number_has_zero(parsed));
|
|
1427
|
+
nr->is_interpolant(false);
|
|
1428
|
+
nr->is_delayed(true);
|
|
1429
|
+
return nr;
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
Number* Parser::lexed_percentage(const SourceSpan& pstate, const sass::string& parsed)
|
|
1433
|
+
{
|
|
1434
|
+
Number* nr = SASS_MEMORY_NEW(Number,
|
|
1435
|
+
pstate,
|
|
1436
|
+
sass_strtod(parsed.c_str()),
|
|
1437
|
+
"%",
|
|
1438
|
+
true);
|
|
1439
|
+
nr->is_interpolant(false);
|
|
1440
|
+
nr->is_delayed(true);
|
|
1441
|
+
return nr;
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
Number* Parser::lexed_dimension(const SourceSpan& pstate, const sass::string& parsed)
|
|
1445
|
+
{
|
|
1446
|
+
size_t L = parsed.length();
|
|
1447
|
+
size_t num_pos = parsed.find_first_not_of(" \n\r\t");
|
|
1448
|
+
if (num_pos == sass::string::npos) num_pos = L;
|
|
1449
|
+
size_t unit_pos = parsed.find_first_not_of("-+0123456789.", num_pos);
|
|
1450
|
+
if (parsed[unit_pos] == 'e' && is_number(parsed[unit_pos+1]) ) {
|
|
1451
|
+
unit_pos = parsed.find_first_not_of("-+0123456789.", ++ unit_pos);
|
|
1452
|
+
}
|
|
1453
|
+
if (unit_pos == sass::string::npos) unit_pos = L;
|
|
1454
|
+
const sass::string& num = parsed.substr(num_pos, unit_pos - num_pos);
|
|
1455
|
+
Number* nr = SASS_MEMORY_NEW(Number,
|
|
1456
|
+
pstate,
|
|
1457
|
+
sass_strtod(num.c_str()),
|
|
1458
|
+
Token(number(parsed.c_str())),
|
|
1459
|
+
number_has_zero(parsed));
|
|
1460
|
+
nr->is_interpolant(false);
|
|
1461
|
+
nr->is_delayed(true);
|
|
1462
|
+
return nr;
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
Value* Parser::lexed_hex_color(const SourceSpan& pstate, const sass::string& parsed)
|
|
1466
|
+
{
|
|
1467
|
+
Color_RGBA* color = NULL;
|
|
1468
|
+
if (parsed[0] != '#') {
|
|
1469
|
+
return SASS_MEMORY_NEW(String_Quoted, pstate, parsed);
|
|
1470
|
+
}
|
|
1471
|
+
// chop off the '#'
|
|
1472
|
+
sass::string hext(parsed.substr(1));
|
|
1473
|
+
if (parsed.length() == 4) {
|
|
1474
|
+
sass::string r(2, parsed[1]);
|
|
1475
|
+
sass::string g(2, parsed[2]);
|
|
1476
|
+
sass::string b(2, parsed[3]);
|
|
1477
|
+
color = SASS_MEMORY_NEW(Color_RGBA,
|
|
1478
|
+
pstate,
|
|
1479
|
+
static_cast<double>(strtol(r.c_str(), NULL, 16)),
|
|
1480
|
+
static_cast<double>(strtol(g.c_str(), NULL, 16)),
|
|
1481
|
+
static_cast<double>(strtol(b.c_str(), NULL, 16)),
|
|
1482
|
+
1, // alpha channel
|
|
1483
|
+
parsed);
|
|
1484
|
+
}
|
|
1485
|
+
else if (parsed.length() == 5) {
|
|
1486
|
+
sass::string r(2, parsed[1]);
|
|
1487
|
+
sass::string g(2, parsed[2]);
|
|
1488
|
+
sass::string b(2, parsed[3]);
|
|
1489
|
+
sass::string a(2, parsed[4]);
|
|
1490
|
+
color = SASS_MEMORY_NEW(Color_RGBA,
|
|
1491
|
+
pstate,
|
|
1492
|
+
static_cast<double>(strtol(r.c_str(), NULL, 16)),
|
|
1493
|
+
static_cast<double>(strtol(g.c_str(), NULL, 16)),
|
|
1494
|
+
static_cast<double>(strtol(b.c_str(), NULL, 16)),
|
|
1495
|
+
static_cast<double>(strtol(a.c_str(), NULL, 16)) / 255,
|
|
1496
|
+
parsed);
|
|
1497
|
+
}
|
|
1498
|
+
else if (parsed.length() == 7) {
|
|
1499
|
+
sass::string r(parsed.substr(1,2));
|
|
1500
|
+
sass::string g(parsed.substr(3,2));
|
|
1501
|
+
sass::string b(parsed.substr(5,2));
|
|
1502
|
+
color = SASS_MEMORY_NEW(Color_RGBA,
|
|
1503
|
+
pstate,
|
|
1504
|
+
static_cast<double>(strtol(r.c_str(), NULL, 16)),
|
|
1505
|
+
static_cast<double>(strtol(g.c_str(), NULL, 16)),
|
|
1506
|
+
static_cast<double>(strtol(b.c_str(), NULL, 16)),
|
|
1507
|
+
1, // alpha channel
|
|
1508
|
+
parsed);
|
|
1509
|
+
}
|
|
1510
|
+
else if (parsed.length() == 9) {
|
|
1511
|
+
sass::string r(parsed.substr(1,2));
|
|
1512
|
+
sass::string g(parsed.substr(3,2));
|
|
1513
|
+
sass::string b(parsed.substr(5,2));
|
|
1514
|
+
sass::string a(parsed.substr(7,2));
|
|
1515
|
+
color = SASS_MEMORY_NEW(Color_RGBA,
|
|
1516
|
+
pstate,
|
|
1517
|
+
static_cast<double>(strtol(r.c_str(), NULL, 16)),
|
|
1518
|
+
static_cast<double>(strtol(g.c_str(), NULL, 16)),
|
|
1519
|
+
static_cast<double>(strtol(b.c_str(), NULL, 16)),
|
|
1520
|
+
static_cast<double>(strtol(a.c_str(), NULL, 16)) / 255,
|
|
1521
|
+
parsed);
|
|
1522
|
+
}
|
|
1523
|
+
color->is_interpolant(false);
|
|
1524
|
+
color->is_delayed(false);
|
|
1525
|
+
return color;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
Value* Parser::color_or_string(const sass::string& lexed) const
|
|
1529
|
+
{
|
|
1530
|
+
if (auto color = name_to_color(lexed)) {
|
|
1531
|
+
auto c = SASS_MEMORY_NEW(Color_RGBA, color);
|
|
1532
|
+
c->is_delayed(true);
|
|
1533
|
+
c->pstate(pstate);
|
|
1534
|
+
c->disp(lexed);
|
|
1535
|
+
return c;
|
|
1536
|
+
} else {
|
|
1537
|
+
return SASS_MEMORY_NEW(String_Constant, pstate, lexed);
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
// parse one value for a list
|
|
1542
|
+
ExpressionObj Parser::parse_value()
|
|
1543
|
+
{
|
|
1544
|
+
lex< css_comments >(false);
|
|
1545
|
+
if (lex< ampersand >())
|
|
1546
|
+
{
|
|
1547
|
+
if (match< ampersand >()) {
|
|
1548
|
+
warning("In Sass, \"&&\" means two copies of the parent selector. You probably want to use \"and\" instead.", pstate);
|
|
1549
|
+
}
|
|
1550
|
+
return SASS_MEMORY_NEW(Parent_Reference, pstate); }
|
|
1551
|
+
|
|
1552
|
+
if (lex< kwd_important >())
|
|
1553
|
+
{ return SASS_MEMORY_NEW(String_Constant, pstate, "!important"); }
|
|
1554
|
+
|
|
1555
|
+
// parse `10%4px` into separated items and not a schema
|
|
1556
|
+
if (lex< sequence < percentage, lookahead < number > > >())
|
|
1557
|
+
{ return lexed_percentage(lexed); }
|
|
1558
|
+
|
|
1559
|
+
if (lex< sequence < number, lookahead< sequence < op, number > > > >())
|
|
1560
|
+
{ return lexed_number(lexed); }
|
|
1561
|
+
|
|
1562
|
+
// string may be interpolated
|
|
1563
|
+
if (lex< sequence < quoted_string, lookahead < exactly <'-'> > > >())
|
|
1564
|
+
{ return parse_string(); }
|
|
1565
|
+
|
|
1566
|
+
if (const char* stop = peek< value_schema >())
|
|
1567
|
+
{ return parse_value_schema(stop); }
|
|
1568
|
+
|
|
1569
|
+
// string may be interpolated
|
|
1570
|
+
if (lex< quoted_string >())
|
|
1571
|
+
{ return parse_string(); }
|
|
1572
|
+
|
|
1573
|
+
if (lex< kwd_true >())
|
|
1574
|
+
{ return SASS_MEMORY_NEW(Boolean, pstate, true); }
|
|
1575
|
+
|
|
1576
|
+
if (lex< kwd_false >())
|
|
1577
|
+
{ return SASS_MEMORY_NEW(Boolean, pstate, false); }
|
|
1578
|
+
|
|
1579
|
+
if (lex< kwd_null >())
|
|
1580
|
+
{ return SASS_MEMORY_NEW(Null, pstate); }
|
|
1581
|
+
|
|
1582
|
+
if (lex< identifier >()) {
|
|
1583
|
+
return color_or_string(lexed);
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
if (lex< percentage >())
|
|
1587
|
+
{ return lexed_percentage(lexed); }
|
|
1588
|
+
|
|
1589
|
+
// match hex number first because 0x000 looks like a number followed by an identifier
|
|
1590
|
+
if (lex< sequence < alternatives< hex, hex0 >, negate < exactly<'-'> > > >())
|
|
1591
|
+
{ return lexed_hex_color(lexed); }
|
|
1592
|
+
|
|
1593
|
+
if (lex< hexa >())
|
|
1594
|
+
{ return lexed_hex_color(lexed); }
|
|
1595
|
+
|
|
1596
|
+
if (lex< sequence < exactly <'#'>, identifier > >())
|
|
1597
|
+
{ return SASS_MEMORY_NEW(String_Quoted, pstate, lexed); }
|
|
1598
|
+
|
|
1599
|
+
// also handle the 10em- foo special case
|
|
1600
|
+
// alternatives < exactly < '.' >, .. > -- `1.5em-.75em` is split into a list, not a binary expression
|
|
1601
|
+
if (lex< sequence< dimension, optional< sequence< exactly<'-'>, lookahead< alternatives < space > > > > > >())
|
|
1602
|
+
{ return lexed_dimension(lexed); }
|
|
1603
|
+
|
|
1604
|
+
if (lex< sequence< static_component, one_plus< strict_identifier > > >())
|
|
1605
|
+
{ return SASS_MEMORY_NEW(String_Constant, pstate, lexed); }
|
|
1606
|
+
|
|
1607
|
+
if (lex< number >())
|
|
1608
|
+
{ return lexed_number(lexed); }
|
|
1609
|
+
|
|
1610
|
+
if (lex< variable >())
|
|
1611
|
+
{ return SASS_MEMORY_NEW(Variable, pstate, Util::normalize_underscores(lexed)); }
|
|
1612
|
+
|
|
1613
|
+
css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");
|
|
1614
|
+
|
|
1615
|
+
// unreachable statement
|
|
1616
|
+
return {};
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
// this parses interpolation inside other strings
|
|
1620
|
+
// means the result should later be quoted again
|
|
1621
|
+
String_Obj Parser::parse_interpolated_chunk(Token chunk, bool constant, bool css)
|
|
1622
|
+
{
|
|
1623
|
+
const char* i = chunk.begin;
|
|
1624
|
+
// see if there any interpolants
|
|
1625
|
+
const char* p = constant ? find_first_in_interval< exactly<hash_lbrace> >(i, chunk.end) :
|
|
1626
|
+
find_first_in_interval< exactly<hash_lbrace>, block_comment >(i, chunk.end);
|
|
1627
|
+
|
|
1628
|
+
if (!p) {
|
|
1629
|
+
String_Quoted* str_quoted = SASS_MEMORY_NEW(String_Quoted, pstate, sass::string(i, chunk.end), 0, false, false, true, css);
|
|
1630
|
+
if (!constant && str_quoted->quote_mark()) str_quoted->quote_mark('*');
|
|
1631
|
+
return str_quoted;
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate, 0, css);
|
|
1635
|
+
schema->is_interpolant(true);
|
|
1636
|
+
while (i < chunk.end) {
|
|
1637
|
+
p = constant ? find_first_in_interval< exactly<hash_lbrace> >(i, chunk.end) :
|
|
1638
|
+
find_first_in_interval< exactly<hash_lbrace>, block_comment >(i, chunk.end);
|
|
1639
|
+
if (p) {
|
|
1640
|
+
if (i < p) {
|
|
1641
|
+
// accumulate the preceding segment if it's nonempty
|
|
1642
|
+
schema->append(SASS_MEMORY_NEW(String_Constant, pstate, sass::string(i, p), css));
|
|
1643
|
+
}
|
|
1644
|
+
// we need to skip anything inside strings
|
|
1645
|
+
// create a new target in parser/prelexer
|
|
1646
|
+
if (peek < sequence < optional_spaces, exactly<rbrace> > >(p+2)) { position = p+2;
|
|
1647
|
+
css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");
|
|
1648
|
+
}
|
|
1649
|
+
const char* j = skip_over_scopes< exactly<hash_lbrace>, exactly<rbrace> >(p + 2, chunk.end); // find the closing brace
|
|
1650
|
+
if (j) { --j;
|
|
1651
|
+
// parse the interpolant and accumulate it
|
|
1652
|
+
LocalOption<const char*> partEnd(end, j);
|
|
1653
|
+
LocalOption<const char*> partBeg(position, p + 2);
|
|
1654
|
+
ExpressionObj interp_node = parse_list();
|
|
1655
|
+
interp_node->is_interpolant(true);
|
|
1656
|
+
schema->append(interp_node);
|
|
1657
|
+
i = j;
|
|
1658
|
+
}
|
|
1659
|
+
else {
|
|
1660
|
+
// throw an error if the interpolant is unterminated
|
|
1661
|
+
error("unterminated interpolant inside string constant " + chunk.to_string());
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
else { // no interpolants left; add the last segment if nonempty
|
|
1665
|
+
// check if we need quotes here (was not sure after merge)
|
|
1666
|
+
if (i < chunk.end) schema->append(SASS_MEMORY_NEW(String_Constant, pstate, sass::string(i, chunk.end), css));
|
|
1667
|
+
break;
|
|
1668
|
+
}
|
|
1669
|
+
++ i;
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
return schema.detach();
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
String_Schema_Obj Parser::parse_css_variable_value()
|
|
1676
|
+
{
|
|
1677
|
+
String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate);
|
|
1678
|
+
sass::vector<char> brackets;
|
|
1679
|
+
while (true) {
|
|
1680
|
+
if (
|
|
1681
|
+
(brackets.empty() && lex< css_variable_top_level_value >(false)) ||
|
|
1682
|
+
(!brackets.empty() && lex< css_variable_value >(false))
|
|
1683
|
+
) {
|
|
1684
|
+
Token str(lexed);
|
|
1685
|
+
schema->append(SASS_MEMORY_NEW(String_Constant, pstate, str));
|
|
1686
|
+
} else if (ExpressionObj tok = lex_interpolation()) {
|
|
1687
|
+
if (String_Schema* s = Cast<String_Schema>(tok)) {
|
|
1688
|
+
if (s->empty()) break;
|
|
1689
|
+
schema->concat(s);
|
|
1690
|
+
} else {
|
|
1691
|
+
schema->append(tok);
|
|
1692
|
+
}
|
|
1693
|
+
} else if (lex< quoted_string >()) {
|
|
1694
|
+
ExpressionObj tok = parse_string();
|
|
1695
|
+
if (tok.isNull()) break;
|
|
1696
|
+
if (String_Schema* s = Cast<String_Schema>(tok)) {
|
|
1697
|
+
if (s->empty()) break;
|
|
1698
|
+
schema->concat(s);
|
|
1699
|
+
} else {
|
|
1700
|
+
schema->append(tok);
|
|
1701
|
+
}
|
|
1702
|
+
} else if (lex< alternatives< exactly<'('>, exactly<'['>, exactly<'{'> > >()) {
|
|
1703
|
+
const char opening_bracket = *(position - 1);
|
|
1704
|
+
brackets.push_back(opening_bracket);
|
|
1705
|
+
schema->append(SASS_MEMORY_NEW(String_Constant, pstate, sass::string(1, opening_bracket)));
|
|
1706
|
+
} else if (const char *match = peek< alternatives< exactly<')'>, exactly<']'>, exactly<'}'> > >()) {
|
|
1707
|
+
if (brackets.empty()) break;
|
|
1708
|
+
const char closing_bracket = *(match - 1);
|
|
1709
|
+
if (brackets.back() != Util::opening_bracket_for(closing_bracket)) {
|
|
1710
|
+
sass::string message = ": expected \"";
|
|
1711
|
+
message += Util::closing_bracket_for(brackets.back());
|
|
1712
|
+
message += "\", was ";
|
|
1713
|
+
css_error("Invalid CSS", " after ", message);
|
|
1714
|
+
}
|
|
1715
|
+
lex< alternatives< exactly<')'>, exactly<']'>, exactly<'}'> > >();
|
|
1716
|
+
schema->append(SASS_MEMORY_NEW(String_Constant, pstate, sass::string(1, closing_bracket)));
|
|
1717
|
+
brackets.pop_back();
|
|
1718
|
+
} else {
|
|
1719
|
+
break;
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
if (!brackets.empty()) {
|
|
1724
|
+
sass::string message = ": expected \"";
|
|
1725
|
+
message += Util::closing_bracket_for(brackets.back());
|
|
1726
|
+
message += "\", was ";
|
|
1727
|
+
css_error("Invalid CSS", " after ", message);
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
if (schema->empty()) error("Custom property values may not be empty.");
|
|
1731
|
+
return schema.detach();
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
ValueObj Parser::parse_static_value()
|
|
1735
|
+
{
|
|
1736
|
+
lex< static_value >();
|
|
1737
|
+
Token str(lexed);
|
|
1738
|
+
// static values always have trailing white-
|
|
1739
|
+
// space and end delimiter (\s*[;]$) included
|
|
1740
|
+
--pstate.offset.column;
|
|
1741
|
+
--after_token.column;
|
|
1742
|
+
--str.end;
|
|
1743
|
+
--position;
|
|
1744
|
+
|
|
1745
|
+
return color_or_string(str.time_wspace());;
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
String_Obj Parser::parse_string()
|
|
1749
|
+
{
|
|
1750
|
+
return parse_interpolated_chunk(Token(lexed));
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
String_Obj Parser::parse_ie_property()
|
|
1754
|
+
{
|
|
1755
|
+
lex< ie_property >();
|
|
1756
|
+
Token str(lexed);
|
|
1757
|
+
const char* i = str.begin;
|
|
1758
|
+
// see if there any interpolants
|
|
1759
|
+
const char* p = find_first_in_interval< exactly<hash_lbrace>, block_comment >(str.begin, str.end);
|
|
1760
|
+
if (!p) {
|
|
1761
|
+
return SASS_MEMORY_NEW(String_Quoted, pstate, sass::string(str.begin, str.end));
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
String_Schema* schema = SASS_MEMORY_NEW(String_Schema, pstate);
|
|
1765
|
+
while (i < str.end) {
|
|
1766
|
+
p = find_first_in_interval< exactly<hash_lbrace>, block_comment >(i, str.end);
|
|
1767
|
+
if (p) {
|
|
1768
|
+
if (i < p) {
|
|
1769
|
+
schema->append(SASS_MEMORY_NEW(String_Constant, pstate, sass::string(i, p))); // accumulate the preceding segment if it's nonempty
|
|
1770
|
+
}
|
|
1771
|
+
if (peek < sequence < optional_spaces, exactly<rbrace> > >(p+2)) { position = p+2;
|
|
1772
|
+
css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");
|
|
1773
|
+
}
|
|
1774
|
+
const char* j = skip_over_scopes< exactly<hash_lbrace>, exactly<rbrace> >(p+2, str.end); // find the closing brace
|
|
1775
|
+
if (j) {
|
|
1776
|
+
// parse the interpolant and accumulate it
|
|
1777
|
+
LocalOption<const char*> partEnd(end, j);
|
|
1778
|
+
LocalOption<const char*> partBeg(position, p + 2);
|
|
1779
|
+
ExpressionObj interp_node = parse_list();
|
|
1780
|
+
interp_node->is_interpolant(true);
|
|
1781
|
+
schema->append(interp_node);
|
|
1782
|
+
i = j;
|
|
1783
|
+
}
|
|
1784
|
+
else {
|
|
1785
|
+
// throw an error if the interpolant is unterminated
|
|
1786
|
+
error("unterminated interpolant inside IE function " + str.to_string());
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
else { // no interpolants left; add the last segment if nonempty
|
|
1790
|
+
if (i < str.end) {
|
|
1791
|
+
schema->append(SASS_MEMORY_NEW(String_Constant, pstate, sass::string(i, str.end)));
|
|
1792
|
+
}
|
|
1793
|
+
break;
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
return schema;
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
String_Obj Parser::parse_ie_keyword_arg()
|
|
1800
|
+
{
|
|
1801
|
+
String_Schema_Obj kwd_arg = SASS_MEMORY_NEW(String_Schema, pstate, 3);
|
|
1802
|
+
if (lex< variable >()) {
|
|
1803
|
+
kwd_arg->append(SASS_MEMORY_NEW(Variable, pstate, Util::normalize_underscores(lexed)));
|
|
1804
|
+
} else {
|
|
1805
|
+
lex< alternatives< identifier_schema, identifier > >();
|
|
1806
|
+
kwd_arg->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed));
|
|
1807
|
+
}
|
|
1808
|
+
lex< exactly<'='> >();
|
|
1809
|
+
kwd_arg->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed));
|
|
1810
|
+
if (peek< variable >()) kwd_arg->append(parse_list());
|
|
1811
|
+
else if (lex< number >()) {
|
|
1812
|
+
sass::string parsed(lexed);
|
|
1813
|
+
Util::normalize_decimals(parsed);
|
|
1814
|
+
kwd_arg->append(lexed_number(parsed));
|
|
1815
|
+
}
|
|
1816
|
+
else if (peek < ie_keyword_arg_value >()) { kwd_arg->append(parse_list()); }
|
|
1817
|
+
return kwd_arg;
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
String_Schema_Obj Parser::parse_value_schema(const char* stop)
|
|
1821
|
+
{
|
|
1822
|
+
// initialize the string schema object to add tokens
|
|
1823
|
+
String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate);
|
|
1824
|
+
|
|
1825
|
+
if (peek<exactly<'}'>>()) {
|
|
1826
|
+
css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
const char* e;
|
|
1830
|
+
const char* ee = end;
|
|
1831
|
+
end = stop;
|
|
1832
|
+
size_t num_items = 0;
|
|
1833
|
+
bool need_space = false;
|
|
1834
|
+
while (position < stop) {
|
|
1835
|
+
// parse space between tokens
|
|
1836
|
+
if (lex< spaces >() && num_items) {
|
|
1837
|
+
need_space = true;
|
|
1838
|
+
}
|
|
1839
|
+
if (need_space) {
|
|
1840
|
+
need_space = false;
|
|
1841
|
+
// schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " "));
|
|
1842
|
+
}
|
|
1843
|
+
if ((e = peek< re_functional >()) && e < stop) {
|
|
1844
|
+
schema->append(parse_function_call());
|
|
1845
|
+
}
|
|
1846
|
+
// lex an interpolant /#{...}/
|
|
1847
|
+
else if (lex< exactly < hash_lbrace > >()) {
|
|
1848
|
+
// Try to lex static expression first
|
|
1849
|
+
if (peek< exactly< rbrace > >()) {
|
|
1850
|
+
css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");
|
|
1851
|
+
}
|
|
1852
|
+
ExpressionObj ex;
|
|
1853
|
+
if (lex< re_static_expression >()) {
|
|
1854
|
+
ex = SASS_MEMORY_NEW(String_Constant, pstate, lexed);
|
|
1855
|
+
} else {
|
|
1856
|
+
ex = parse_list(true);
|
|
1857
|
+
}
|
|
1858
|
+
ex->is_interpolant(true);
|
|
1859
|
+
schema->append(ex);
|
|
1860
|
+
if (!lex < exactly < rbrace > >()) {
|
|
1861
|
+
css_error("Invalid CSS", " after ", ": expected \"}\", was ");
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
// lex some string constants or other valid token
|
|
1865
|
+
// Note: [-+] chars are left over from i.e. `#{3}+3`
|
|
1866
|
+
else if (lex< alternatives < exactly<'%'>, exactly < '-' >, exactly < '+' > > >()) {
|
|
1867
|
+
schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed));
|
|
1868
|
+
}
|
|
1869
|
+
// lex a quoted string
|
|
1870
|
+
else if (lex< quoted_string >()) {
|
|
1871
|
+
// need_space = true;
|
|
1872
|
+
// if (schema->length()) schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " "));
|
|
1873
|
+
// else need_space = true;
|
|
1874
|
+
schema->append(parse_string());
|
|
1875
|
+
if ((*position == '"' || *position == '\'') || peek < alternatives < alpha > >()) {
|
|
1876
|
+
// need_space = true;
|
|
1877
|
+
}
|
|
1878
|
+
if (peek < exactly < '-' > >()) break;
|
|
1879
|
+
}
|
|
1880
|
+
else if (lex< identifier >()) {
|
|
1881
|
+
schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed));
|
|
1882
|
+
if ((*position == '"' || *position == '\'') || peek < alternatives < alpha > >()) {
|
|
1883
|
+
// need_space = true;
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
// lex (normalized) variable
|
|
1887
|
+
else if (lex< variable >()) {
|
|
1888
|
+
sass::string name(Util::normalize_underscores(lexed));
|
|
1889
|
+
schema->append(SASS_MEMORY_NEW(Variable, pstate, name));
|
|
1890
|
+
}
|
|
1891
|
+
// lex percentage value
|
|
1892
|
+
else if (lex< percentage >()) {
|
|
1893
|
+
schema->append(lexed_percentage(lexed));
|
|
1894
|
+
}
|
|
1895
|
+
// lex dimension value
|
|
1896
|
+
else if (lex< dimension >()) {
|
|
1897
|
+
schema->append(lexed_dimension(lexed));
|
|
1898
|
+
}
|
|
1899
|
+
// lex number value
|
|
1900
|
+
else if (lex< number >()) {
|
|
1901
|
+
schema->append(lexed_number(lexed));
|
|
1902
|
+
}
|
|
1903
|
+
// lex hex color value
|
|
1904
|
+
else if (lex< sequence < hex, negate < exactly < '-' > > > >()) {
|
|
1905
|
+
schema->append(lexed_hex_color(lexed));
|
|
1906
|
+
}
|
|
1907
|
+
else if (lex< sequence < exactly <'#'>, identifier > >()) {
|
|
1908
|
+
schema->append(SASS_MEMORY_NEW(String_Quoted, pstate, lexed));
|
|
1909
|
+
}
|
|
1910
|
+
// lex a value in parentheses
|
|
1911
|
+
else if (peek< parenthese_scope >()) {
|
|
1912
|
+
schema->append(parse_factor());
|
|
1913
|
+
}
|
|
1914
|
+
else {
|
|
1915
|
+
break;
|
|
1916
|
+
}
|
|
1917
|
+
++num_items;
|
|
1918
|
+
}
|
|
1919
|
+
if (position != stop) {
|
|
1920
|
+
schema->append(SASS_MEMORY_NEW(String_Constant, pstate, sass::string(position, stop)));
|
|
1921
|
+
position = stop;
|
|
1922
|
+
}
|
|
1923
|
+
end = ee;
|
|
1924
|
+
return schema;
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
// this parses interpolation outside other strings
|
|
1928
|
+
// means the result must not be quoted again later
|
|
1929
|
+
String_Obj Parser::parse_identifier_schema()
|
|
1930
|
+
{
|
|
1931
|
+
Token id(lexed);
|
|
1932
|
+
const char* i = id.begin;
|
|
1933
|
+
// see if there any interpolants
|
|
1934
|
+
const char* p = find_first_in_interval< exactly<hash_lbrace>, block_comment >(id.begin, id.end);
|
|
1935
|
+
if (!p) {
|
|
1936
|
+
return SASS_MEMORY_NEW(String_Constant, pstate, sass::string(id.begin, id.end));
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate);
|
|
1940
|
+
while (i < id.end) {
|
|
1941
|
+
p = find_first_in_interval< exactly<hash_lbrace>, block_comment >(i, id.end);
|
|
1942
|
+
if (p) {
|
|
1943
|
+
if (i < p) {
|
|
1944
|
+
// accumulate the preceding segment if it's nonempty
|
|
1945
|
+
const char* o = position; position = i;
|
|
1946
|
+
schema->append(parse_value_schema(p));
|
|
1947
|
+
position = o;
|
|
1948
|
+
}
|
|
1949
|
+
// we need to skip anything inside strings
|
|
1950
|
+
// create a new target in parser/prelexer
|
|
1951
|
+
if (peek < sequence < optional_spaces, exactly<rbrace> > >(p+2)) { position = p;
|
|
1952
|
+
css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");
|
|
1953
|
+
}
|
|
1954
|
+
const char* j = skip_over_scopes< exactly<hash_lbrace>, exactly<rbrace> >(p+2, id.end); // find the closing brace
|
|
1955
|
+
if (j) {
|
|
1956
|
+
// parse the interpolant and accumulate it
|
|
1957
|
+
LocalOption<const char*> partEnd(end, j);
|
|
1958
|
+
LocalOption<const char*> partBeg(position, p + 2);
|
|
1959
|
+
ExpressionObj interp_node = parse_list(DELAYED);
|
|
1960
|
+
interp_node->is_interpolant(true);
|
|
1961
|
+
schema->append(interp_node);
|
|
1962
|
+
// schema->has_interpolants(true);
|
|
1963
|
+
i = j;
|
|
1964
|
+
}
|
|
1965
|
+
else {
|
|
1966
|
+
// throw an error if the interpolant is unterminated
|
|
1967
|
+
error("unterminated interpolant inside interpolated identifier " + id.to_string());
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
else { // no interpolants left; add the last segment if nonempty
|
|
1971
|
+
if (i < end) {
|
|
1972
|
+
const char* o = position; position = i;
|
|
1973
|
+
schema->append(parse_value_schema(id.end));
|
|
1974
|
+
position = o;
|
|
1975
|
+
}
|
|
1976
|
+
break;
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
return schema ? schema.detach() : 0;
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
// calc functions should preserve arguments
|
|
1983
|
+
Function_Call_Obj Parser::parse_calc_function()
|
|
1984
|
+
{
|
|
1985
|
+
lex< identifier >();
|
|
1986
|
+
sass::string name(lexed);
|
|
1987
|
+
SourceSpan call_pos = pstate;
|
|
1988
|
+
lex< exactly<'('> >();
|
|
1989
|
+
SourceSpan arg_pos = pstate;
|
|
1990
|
+
const char* arg_beg = position;
|
|
1991
|
+
parse_list();
|
|
1992
|
+
const char* arg_end = position;
|
|
1993
|
+
lex< skip_over_scopes <
|
|
1994
|
+
exactly < '(' >,
|
|
1995
|
+
exactly < ')' >
|
|
1996
|
+
> >();
|
|
1997
|
+
|
|
1998
|
+
Argument_Obj arg = SASS_MEMORY_NEW(Argument, arg_pos, parse_interpolated_chunk(Token(arg_beg, arg_end)));
|
|
1999
|
+
Arguments_Obj args = SASS_MEMORY_NEW(Arguments, arg_pos);
|
|
2000
|
+
args->append(arg);
|
|
2001
|
+
return SASS_MEMORY_NEW(Function_Call, call_pos, name, args);
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
String_Obj Parser::parse_url_function_string()
|
|
2005
|
+
{
|
|
2006
|
+
sass::string prefix("");
|
|
2007
|
+
if (lex< uri_prefix >()) {
|
|
2008
|
+
prefix = sass::string(lexed);
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
lex < optional_spaces >();
|
|
2012
|
+
String_Obj url_string = parse_url_function_argument();
|
|
2013
|
+
|
|
2014
|
+
sass::string suffix("");
|
|
2015
|
+
if (lex< real_uri_suffix >()) {
|
|
2016
|
+
suffix = sass::string(lexed);
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
sass::string uri("");
|
|
2020
|
+
if (url_string) {
|
|
2021
|
+
uri = url_string->to_string({ NESTED, 5 });
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
if (String_Schema* schema = Cast<String_Schema>(url_string)) {
|
|
2025
|
+
String_Schema_Obj res = SASS_MEMORY_NEW(String_Schema, pstate);
|
|
2026
|
+
res->append(SASS_MEMORY_NEW(String_Constant, pstate, prefix));
|
|
2027
|
+
res->append(schema);
|
|
2028
|
+
res->append(SASS_MEMORY_NEW(String_Constant, pstate, suffix));
|
|
2029
|
+
return res;
|
|
2030
|
+
} else {
|
|
2031
|
+
sass::string res = prefix + uri + suffix;
|
|
2032
|
+
return SASS_MEMORY_NEW(String_Constant, pstate, res);
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
String_Obj Parser::parse_url_function_argument()
|
|
2037
|
+
{
|
|
2038
|
+
const char* p = position;
|
|
2039
|
+
|
|
2040
|
+
sass::string uri("");
|
|
2041
|
+
if (lex< real_uri_value >(false)) {
|
|
2042
|
+
uri = lexed.to_string();
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
if (peek< exactly< hash_lbrace > >()) {
|
|
2046
|
+
const char* pp = position;
|
|
2047
|
+
// TODO: error checking for unclosed interpolants
|
|
2048
|
+
while (pp && peek< exactly< hash_lbrace > >(pp)) {
|
|
2049
|
+
pp = sequence< interpolant, real_uri_value >(pp);
|
|
2050
|
+
}
|
|
2051
|
+
if (!pp) return {};
|
|
2052
|
+
position = pp;
|
|
2053
|
+
return parse_interpolated_chunk(Token(p, position));
|
|
2054
|
+
}
|
|
2055
|
+
else if (uri != "") {
|
|
2056
|
+
sass::string res = Util::rtrim(uri);
|
|
2057
|
+
return SASS_MEMORY_NEW(String_Constant, pstate, res);
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
return {};
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
Function_Call_Obj Parser::parse_function_call()
|
|
2064
|
+
{
|
|
2065
|
+
lex< identifier >();
|
|
2066
|
+
sass::string name(lexed);
|
|
2067
|
+
|
|
2068
|
+
if (Util::normalize_underscores(name) == "content-exists" && stack.back() != Scope::Mixin)
|
|
2069
|
+
{ error("Cannot call content-exists() except within a mixin."); }
|
|
2070
|
+
|
|
2071
|
+
SourceSpan call_pos = pstate;
|
|
2072
|
+
|
|
2073
|
+
// Use special parser for CSS Color Level 4 functions
|
|
2074
|
+
sass::string normalized_name = Util::normalize_underscores(name);
|
|
2075
|
+
Arguments_Obj args;
|
|
2076
|
+
|
|
2077
|
+
if (normalized_name == "rgb" || normalized_name == "rgba" ||
|
|
2078
|
+
normalized_name == "hsl" || normalized_name == "hsla") {
|
|
2079
|
+
args = parse_color_arguments();
|
|
2080
|
+
} else {
|
|
2081
|
+
args = parse_arguments();
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
return SASS_MEMORY_NEW(Function_Call, call_pos, name, args);
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
Function_Call_Obj Parser::parse_function_call_schema()
|
|
2088
|
+
{
|
|
2089
|
+
String_Obj name = parse_identifier_schema();
|
|
2090
|
+
SourceSpan source_position_of_call = pstate;
|
|
2091
|
+
Arguments_Obj args = parse_arguments();
|
|
2092
|
+
|
|
2093
|
+
return SASS_MEMORY_NEW(Function_Call, source_position_of_call, name, args);
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
Content_Obj Parser::parse_content_directive()
|
|
2097
|
+
{
|
|
2098
|
+
SourceSpan call_pos = pstate;
|
|
2099
|
+
Arguments_Obj args = parse_arguments();
|
|
2100
|
+
|
|
2101
|
+
return SASS_MEMORY_NEW(Content, call_pos, args);
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
If_Obj Parser::parse_if_directive(bool else_if)
|
|
2105
|
+
{
|
|
2106
|
+
stack.push_back(Scope::Control);
|
|
2107
|
+
SourceSpan if_source_position = pstate;
|
|
2108
|
+
bool root = block_stack.back()->is_root();
|
|
2109
|
+
ExpressionObj predicate = parse_list();
|
|
2110
|
+
Block_Obj block = parse_block(root);
|
|
2111
|
+
Block_Obj alternative;
|
|
2112
|
+
|
|
2113
|
+
// only throw away comment if we parse a case
|
|
2114
|
+
// we want all other comments to be parsed
|
|
2115
|
+
if (lex_css< elseif_directive >()) {
|
|
2116
|
+
alternative = SASS_MEMORY_NEW(Block, pstate);
|
|
2117
|
+
alternative->append(parse_if_directive(true));
|
|
2118
|
+
}
|
|
2119
|
+
else if (lex_css< kwd_else_directive >()) {
|
|
2120
|
+
alternative = parse_block(root);
|
|
2121
|
+
}
|
|
2122
|
+
stack.pop_back();
|
|
2123
|
+
return SASS_MEMORY_NEW(If, if_source_position, predicate, block, alternative);
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
ForRuleObj Parser::parse_for_directive()
|
|
2127
|
+
{
|
|
2128
|
+
stack.push_back(Scope::Control);
|
|
2129
|
+
SourceSpan for_source_position = pstate;
|
|
2130
|
+
bool root = block_stack.back()->is_root();
|
|
2131
|
+
lex_variable();
|
|
2132
|
+
sass::string var(Util::normalize_underscores(lexed));
|
|
2133
|
+
if (!lex< kwd_from >()) error("expected 'from' keyword in @for directive");
|
|
2134
|
+
ExpressionObj lower_bound = parse_expression();
|
|
2135
|
+
bool inclusive = false;
|
|
2136
|
+
if (lex< kwd_through >()) inclusive = true;
|
|
2137
|
+
else if (lex< kwd_to >()) inclusive = false;
|
|
2138
|
+
else error("expected 'through' or 'to' keyword in @for directive");
|
|
2139
|
+
ExpressionObj upper_bound = parse_expression();
|
|
2140
|
+
Block_Obj body = parse_block(root);
|
|
2141
|
+
stack.pop_back();
|
|
2142
|
+
return SASS_MEMORY_NEW(ForRule, for_source_position, var, lower_bound, upper_bound, body, inclusive);
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
// helper to parse a var token
|
|
2146
|
+
Token Parser::lex_variable()
|
|
2147
|
+
{
|
|
2148
|
+
// peek for dollar sign first
|
|
2149
|
+
if (!peek< exactly <'$'> >()) {
|
|
2150
|
+
css_error("Invalid CSS", " after ", ": expected \"$\", was ");
|
|
2151
|
+
}
|
|
2152
|
+
// we expect a simple identifier as the call name
|
|
2153
|
+
if (!lex< sequence < exactly <'$'>, identifier > >()) {
|
|
2154
|
+
lex< exactly <'$'> >(); // move pstate and position up
|
|
2155
|
+
css_error("Invalid CSS", " after ", ": expected identifier, was ");
|
|
2156
|
+
}
|
|
2157
|
+
// return object
|
|
2158
|
+
return lexed;
|
|
2159
|
+
}
|
|
2160
|
+
// helper to parse identifier
|
|
2161
|
+
Token Parser::lex_identifier()
|
|
2162
|
+
{
|
|
2163
|
+
// we expect a simple identifier as the call name
|
|
2164
|
+
if (!lex< identifier >()) { // ToDo: pstate wrong?
|
|
2165
|
+
css_error("Invalid CSS", " after ", ": expected identifier, was ");
|
|
2166
|
+
}
|
|
2167
|
+
// return object
|
|
2168
|
+
return lexed;
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
EachRuleObj Parser::parse_each_directive()
|
|
2172
|
+
{
|
|
2173
|
+
stack.push_back(Scope::Control);
|
|
2174
|
+
SourceSpan each_source_position = pstate;
|
|
2175
|
+
bool root = block_stack.back()->is_root();
|
|
2176
|
+
sass::vector<sass::string> vars;
|
|
2177
|
+
lex_variable();
|
|
2178
|
+
vars.push_back(Util::normalize_underscores(lexed));
|
|
2179
|
+
while (lex< exactly<','> >()) {
|
|
2180
|
+
if (!lex< variable >()) error("@each directive requires an iteration variable");
|
|
2181
|
+
vars.push_back(Util::normalize_underscores(lexed));
|
|
2182
|
+
}
|
|
2183
|
+
if (!lex< kwd_in >()) error("expected 'in' keyword in @each directive");
|
|
2184
|
+
ExpressionObj list = parse_list();
|
|
2185
|
+
Block_Obj body = parse_block(root);
|
|
2186
|
+
stack.pop_back();
|
|
2187
|
+
return SASS_MEMORY_NEW(EachRule, each_source_position, vars, list, body);
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
// called after parsing `kwd_while_directive`
|
|
2191
|
+
WhileRuleObj Parser::parse_while_directive()
|
|
2192
|
+
{
|
|
2193
|
+
stack.push_back(Scope::Control);
|
|
2194
|
+
bool root = block_stack.back()->is_root();
|
|
2195
|
+
// create the initial while call object
|
|
2196
|
+
WhileRuleObj call = SASS_MEMORY_NEW(WhileRule, pstate, ExpressionObj{}, Block_Obj{});
|
|
2197
|
+
// parse mandatory predicate
|
|
2198
|
+
ExpressionObj predicate = parse_list();
|
|
2199
|
+
List_Obj l = Cast<List>(predicate);
|
|
2200
|
+
if (!predicate || (l && !l->length())) {
|
|
2201
|
+
css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ", false);
|
|
2202
|
+
}
|
|
2203
|
+
call->predicate(predicate);
|
|
2204
|
+
// parse mandatory block
|
|
2205
|
+
call->block(parse_block(root));
|
|
2206
|
+
// return ast node
|
|
2207
|
+
stack.pop_back();
|
|
2208
|
+
// return ast node
|
|
2209
|
+
return call.detach();
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
|
|
2213
|
+
sass::vector<CssMediaQuery_Obj> Parser::parseCssMediaQueries()
|
|
2214
|
+
{
|
|
2215
|
+
sass::vector<CssMediaQuery_Obj> result;
|
|
2216
|
+
do {
|
|
2217
|
+
if (auto query = parseCssMediaQuery()) {
|
|
2218
|
+
result.push_back(query);
|
|
2219
|
+
}
|
|
2220
|
+
} while (lex<exactly<','>>());
|
|
2221
|
+
return result;
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
sass::string Parser::parseIdentifier()
|
|
2225
|
+
{
|
|
2226
|
+
if (lex < identifier >(false)) {
|
|
2227
|
+
return sass::string(lexed);
|
|
2228
|
+
}
|
|
2229
|
+
return sass::string();
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
CssMediaQuery_Obj Parser::parseCssMediaQuery()
|
|
2233
|
+
{
|
|
2234
|
+
CssMediaQuery_Obj result = SASS_MEMORY_NEW(CssMediaQuery, pstate);
|
|
2235
|
+
lex<css_comments>(false);
|
|
2236
|
+
|
|
2237
|
+
// Check if any tokens are to parse
|
|
2238
|
+
if (!peek_css<exactly<'('>>()) {
|
|
2239
|
+
|
|
2240
|
+
sass::string token1(parseIdentifier());
|
|
2241
|
+
lex<css_comments>(false);
|
|
2242
|
+
|
|
2243
|
+
if (token1.empty()) {
|
|
2244
|
+
return {};
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
sass::string token2(parseIdentifier());
|
|
2248
|
+
lex<css_comments>(false);
|
|
2249
|
+
|
|
2250
|
+
if (Util::equalsLiteral("and", token2)) {
|
|
2251
|
+
result->type(token1);
|
|
2252
|
+
}
|
|
2253
|
+
else {
|
|
2254
|
+
if (token2.empty()) {
|
|
2255
|
+
result->type(token1);
|
|
2256
|
+
}
|
|
2257
|
+
else {
|
|
2258
|
+
result->modifier(token1);
|
|
2259
|
+
result->type(token2);
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
if (lex < kwd_and >()) {
|
|
2263
|
+
lex<css_comments>(false);
|
|
2264
|
+
}
|
|
2265
|
+
else {
|
|
2266
|
+
return result;
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
sass::vector<sass::string> queries;
|
|
2274
|
+
|
|
2275
|
+
do {
|
|
2276
|
+
lex<css_comments>(false);
|
|
2277
|
+
|
|
2278
|
+
if (lex<exactly<'('>>()) {
|
|
2279
|
+
// In dart sass parser returns a pure string
|
|
2280
|
+
if (lex < skip_over_scopes < exactly < '(' >, exactly < ')' > > >()) {
|
|
2281
|
+
sass::string decl("(" + sass::string(lexed));
|
|
2282
|
+
queries.push_back(decl);
|
|
2283
|
+
}
|
|
2284
|
+
// Should be: parseDeclarationValue;
|
|
2285
|
+
if (!lex<exactly<')'>>()) {
|
|
2286
|
+
// Should we throw an error here?
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
} while (lex < kwd_and >());
|
|
2290
|
+
|
|
2291
|
+
result->features(queries);
|
|
2292
|
+
|
|
2293
|
+
if (result->features().empty()) {
|
|
2294
|
+
if (result->type().empty()) {
|
|
2295
|
+
return {};
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
return result;
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
|
|
2303
|
+
// EO parse_while_directive
|
|
2304
|
+
MediaRule_Obj Parser::parseMediaRule()
|
|
2305
|
+
{
|
|
2306
|
+
MediaRule_Obj rule = SASS_MEMORY_NEW(MediaRule, pstate);
|
|
2307
|
+
stack.push_back(Scope::Media);
|
|
2308
|
+
rule->schema(parse_media_queries());
|
|
2309
|
+
parse_block_comments(false);
|
|
2310
|
+
rule->block(parse_css_block());
|
|
2311
|
+
stack.pop_back();
|
|
2312
|
+
return rule;
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2315
|
+
List_Obj Parser::parse_media_queries()
|
|
2316
|
+
{
|
|
2317
|
+
advanceToNextToken();
|
|
2318
|
+
List_Obj queries = SASS_MEMORY_NEW(List, pstate, 0, SASS_COMMA);
|
|
2319
|
+
if (!peek_css < exactly <'{'> >()) queries->append(parse_media_query());
|
|
2320
|
+
while (lex_css < exactly <','> >()) queries->append(parse_media_query());
|
|
2321
|
+
queries->update_pstate(pstate);
|
|
2322
|
+
return queries.detach();
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
// Expression* Parser::parse_media_query()
|
|
2326
|
+
Media_Query_Obj Parser::parse_media_query()
|
|
2327
|
+
{
|
|
2328
|
+
advanceToNextToken();
|
|
2329
|
+
Media_Query_Obj media_query = SASS_MEMORY_NEW(Media_Query, pstate);
|
|
2330
|
+
if (lex < kwd_not >()) { media_query->is_negated(true); lex < css_comments >(false); }
|
|
2331
|
+
else if (lex < kwd_only >()) { media_query->is_restricted(true); lex < css_comments >(false); }
|
|
2332
|
+
|
|
2333
|
+
if (lex < identifier_schema >()) media_query->media_type(parse_identifier_schema());
|
|
2334
|
+
else if (lex < identifier >()) media_query->media_type(parse_interpolated_chunk(lexed));
|
|
2335
|
+
else media_query->append(parse_media_expression());
|
|
2336
|
+
|
|
2337
|
+
while (lex_css < kwd_and >()) media_query->append(parse_media_expression());
|
|
2338
|
+
if (lex < identifier_schema >()) {
|
|
2339
|
+
String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate);
|
|
2340
|
+
if (media_query->media_type()) {
|
|
2341
|
+
schema->append(media_query->media_type());
|
|
2342
|
+
schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " "));
|
|
2343
|
+
}
|
|
2344
|
+
schema->append(parse_identifier_schema());
|
|
2345
|
+
media_query->media_type(schema);
|
|
2346
|
+
}
|
|
2347
|
+
while (lex_css < kwd_and >()) media_query->append(parse_media_expression());
|
|
2348
|
+
|
|
2349
|
+
media_query->update_pstate(pstate);
|
|
2350
|
+
|
|
2351
|
+
return media_query;
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
Media_Query_ExpressionObj Parser::parse_media_expression()
|
|
2355
|
+
{
|
|
2356
|
+
if (lex < identifier_schema >()) {
|
|
2357
|
+
String_Obj ss = parse_identifier_schema();
|
|
2358
|
+
return SASS_MEMORY_NEW(Media_Query_Expression, pstate, ss, ExpressionObj{}, true);
|
|
2359
|
+
}
|
|
2360
|
+
if (!lex_css< exactly<'('> >()) {
|
|
2361
|
+
error("media query expression must begin with '('");
|
|
2362
|
+
}
|
|
2363
|
+
ExpressionObj feature;
|
|
2364
|
+
if (peek_css< exactly<')'> >()) {
|
|
2365
|
+
error("media feature required in media query expression");
|
|
2366
|
+
}
|
|
2367
|
+
feature = parse_expression();
|
|
2368
|
+
ExpressionObj expression;
|
|
2369
|
+
if (lex_css< exactly<':'> >()) {
|
|
2370
|
+
expression = parse_list(DELAYED);
|
|
2371
|
+
}
|
|
2372
|
+
if (!lex_css< exactly<')'> >()) {
|
|
2373
|
+
error("unclosed parenthesis in media query expression");
|
|
2374
|
+
}
|
|
2375
|
+
return SASS_MEMORY_NEW(Media_Query_Expression, feature->pstate(), feature, expression);
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
// lexed after `kwd_supports_directive`
|
|
2379
|
+
// these are very similar to media blocks
|
|
2380
|
+
SupportsRuleObj Parser::parse_supports_directive()
|
|
2381
|
+
{
|
|
2382
|
+
SupportsConditionObj cond = parse_supports_condition(/*top_level=*/true);
|
|
2383
|
+
// create the ast node object for the support queries
|
|
2384
|
+
SupportsRuleObj query = SASS_MEMORY_NEW(SupportsRule, pstate, cond);
|
|
2385
|
+
// additional block is mandatory
|
|
2386
|
+
// parse inner block
|
|
2387
|
+
query->block(parse_block());
|
|
2388
|
+
// return ast node
|
|
2389
|
+
return query;
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
// parse one query operation
|
|
2393
|
+
// may encounter nested queries
|
|
2394
|
+
SupportsConditionObj Parser::parse_supports_condition(bool top_level)
|
|
2395
|
+
{
|
|
2396
|
+
lex < css_whitespace >();
|
|
2397
|
+
SupportsConditionObj cond;
|
|
2398
|
+
if ((cond = parse_supports_negation())) return cond;
|
|
2399
|
+
if ((cond = parse_supports_operator(top_level))) return cond;
|
|
2400
|
+
if ((cond = parse_supports_interpolation())) return cond;
|
|
2401
|
+
return cond;
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
SupportsConditionObj Parser::parse_supports_negation()
|
|
2405
|
+
{
|
|
2406
|
+
if (!lex < kwd_not >()) return {};
|
|
2407
|
+
SupportsConditionObj cond = parse_supports_condition_in_parens(/*parens_required=*/true);
|
|
2408
|
+
return SASS_MEMORY_NEW(SupportsNegation, pstate, cond);
|
|
2409
|
+
}
|
|
2410
|
+
|
|
2411
|
+
SupportsConditionObj Parser::parse_supports_operator(bool top_level)
|
|
2412
|
+
{
|
|
2413
|
+
SupportsConditionObj cond = parse_supports_condition_in_parens(/*parens_required=*/top_level);
|
|
2414
|
+
if (cond.isNull()) return {};
|
|
2415
|
+
|
|
2416
|
+
while (true) {
|
|
2417
|
+
SupportsOperation::Operand op = SupportsOperation::OR;
|
|
2418
|
+
if (lex < kwd_and >()) { op = SupportsOperation::AND; }
|
|
2419
|
+
else if(!lex < kwd_or >()) { break; }
|
|
2420
|
+
|
|
2421
|
+
lex < css_whitespace >();
|
|
2422
|
+
SupportsConditionObj right = parse_supports_condition_in_parens(/*parens_required=*/true);
|
|
2423
|
+
|
|
2424
|
+
// SupportsCondition* cc = SASS_MEMORY_NEW(SupportsCondition, *static_cast<SupportsCondition*>(cond));
|
|
2425
|
+
cond = SASS_MEMORY_NEW(SupportsOperation, pstate, cond, right, op);
|
|
2426
|
+
}
|
|
2427
|
+
return cond;
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
SupportsConditionObj Parser::parse_supports_interpolation()
|
|
2431
|
+
{
|
|
2432
|
+
if (!lex < interpolant >()) return {};
|
|
2433
|
+
|
|
2434
|
+
String_Obj interp = parse_interpolated_chunk(lexed);
|
|
2435
|
+
if (!interp) return {};
|
|
2436
|
+
|
|
2437
|
+
return SASS_MEMORY_NEW(Supports_Interpolation, pstate, interp);
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
// TODO: This needs some major work. Although feature conditions
|
|
2441
|
+
// look like declarations their semantics differ significantly
|
|
2442
|
+
SupportsConditionObj Parser::parse_supports_declaration()
|
|
2443
|
+
{
|
|
2444
|
+
SupportsCondition* cond;
|
|
2445
|
+
// parse something declaration like
|
|
2446
|
+
ExpressionObj feature = parse_expression();
|
|
2447
|
+
ExpressionObj expression;
|
|
2448
|
+
if (lex_css< exactly<':'> >()) {
|
|
2449
|
+
expression = parse_list(DELAYED);
|
|
2450
|
+
}
|
|
2451
|
+
if (!feature || !expression) error("@supports condition expected declaration");
|
|
2452
|
+
cond = SASS_MEMORY_NEW(SupportsDeclaration,
|
|
2453
|
+
feature->pstate(),
|
|
2454
|
+
feature,
|
|
2455
|
+
expression);
|
|
2456
|
+
// ToDo: maybe we need an additional error condition?
|
|
2457
|
+
return cond;
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
SupportsConditionObj Parser::parse_supports_condition_in_parens(bool parens_required)
|
|
2461
|
+
{
|
|
2462
|
+
SupportsConditionObj interp = parse_supports_interpolation();
|
|
2463
|
+
if (interp != nullptr) return interp;
|
|
2464
|
+
|
|
2465
|
+
if (!lex < exactly <'('> >()) {
|
|
2466
|
+
if (parens_required) {
|
|
2467
|
+
css_error("Invalid CSS", " after ", ": expected @supports condition (e.g. (display: flexbox)), was ", /*trim=*/false);
|
|
2468
|
+
} else {
|
|
2469
|
+
return {};
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2472
|
+
lex < css_whitespace >();
|
|
2473
|
+
|
|
2474
|
+
SupportsConditionObj cond = parse_supports_condition(/*top_level=*/false);
|
|
2475
|
+
if (cond.isNull()) cond = parse_supports_declaration();
|
|
2476
|
+
if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration");
|
|
2477
|
+
|
|
2478
|
+
lex < css_whitespace >();
|
|
2479
|
+
return cond;
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
AtRootRuleObj Parser::parse_at_root_block()
|
|
2483
|
+
{
|
|
2484
|
+
stack.push_back(Scope::AtRoot);
|
|
2485
|
+
SourceSpan at_source_position = pstate;
|
|
2486
|
+
Block_Obj body;
|
|
2487
|
+
At_Root_Query_Obj expr;
|
|
2488
|
+
Lookahead lookahead_result;
|
|
2489
|
+
if (lex_css< exactly<'('> >()) {
|
|
2490
|
+
expr = parse_at_root_query();
|
|
2491
|
+
}
|
|
2492
|
+
if (peek_css < exactly<'{'> >()) {
|
|
2493
|
+
lex <optional_spaces>();
|
|
2494
|
+
body = parse_block(true);
|
|
2495
|
+
}
|
|
2496
|
+
else if ((lookahead_result = lookahead_for_selector(position)).found) {
|
|
2497
|
+
StyleRuleObj r = parse_ruleset(lookahead_result);
|
|
2498
|
+
body = SASS_MEMORY_NEW(Block, r->pstate(), 1, true);
|
|
2499
|
+
body->append(r);
|
|
2500
|
+
}
|
|
2501
|
+
AtRootRuleObj at_root = SASS_MEMORY_NEW(AtRootRule, at_source_position, body);
|
|
2502
|
+
if (!expr.isNull()) at_root->expression(expr);
|
|
2503
|
+
stack.pop_back();
|
|
2504
|
+
return at_root;
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
At_Root_Query_Obj Parser::parse_at_root_query()
|
|
2508
|
+
{
|
|
2509
|
+
if (peek< exactly<')'> >()) error("at-root feature required in at-root expression");
|
|
2510
|
+
|
|
2511
|
+
if (!peek< alternatives< kwd_with_directive, kwd_without_directive > >()) {
|
|
2512
|
+
css_error("Invalid CSS", " after ", ": expected \"with\" or \"without\", was ");
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
ExpressionObj feature = parse_list();
|
|
2516
|
+
if (!lex_css< exactly<':'> >()) error("style declaration must contain a value");
|
|
2517
|
+
ExpressionObj expression = parse_list();
|
|
2518
|
+
List_Obj value = SASS_MEMORY_NEW(List, feature->pstate(), 1);
|
|
2519
|
+
|
|
2520
|
+
if (expression->concrete_type() == Expression::LIST) {
|
|
2521
|
+
value = Cast<List>(expression);
|
|
2522
|
+
}
|
|
2523
|
+
else value->append(expression);
|
|
2524
|
+
|
|
2525
|
+
At_Root_Query_Obj cond = SASS_MEMORY_NEW(At_Root_Query,
|
|
2526
|
+
value->pstate(),
|
|
2527
|
+
feature,
|
|
2528
|
+
value);
|
|
2529
|
+
if (!lex_css< exactly<')'> >()) error("unclosed parenthesis in @at-root expression");
|
|
2530
|
+
return cond;
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
AtRuleObj Parser::parse_directive()
|
|
2534
|
+
{
|
|
2535
|
+
AtRuleObj directive = SASS_MEMORY_NEW(AtRule, pstate, lexed);
|
|
2536
|
+
String_Schema_Obj val = parse_almost_any_value();
|
|
2537
|
+
// strip left and right if they are of type string
|
|
2538
|
+
directive->value(val);
|
|
2539
|
+
if (peek< exactly<'{'> >()) {
|
|
2540
|
+
directive->block(parse_block());
|
|
2541
|
+
}
|
|
2542
|
+
return directive;
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2545
|
+
ExpressionObj Parser::lex_interpolation()
|
|
2546
|
+
{
|
|
2547
|
+
if (lex < interpolant >(true) != NULL) {
|
|
2548
|
+
return parse_interpolated_chunk(lexed, true);
|
|
2549
|
+
}
|
|
2550
|
+
return {};
|
|
2551
|
+
}
|
|
2552
|
+
|
|
2553
|
+
ExpressionObj Parser::lex_interp_uri()
|
|
2554
|
+
{
|
|
2555
|
+
// create a string schema by lexing optional interpolations
|
|
2556
|
+
return lex_interp< re_string_uri_open, re_string_uri_close >();
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
ExpressionObj Parser::lex_interp_string()
|
|
2560
|
+
{
|
|
2561
|
+
ExpressionObj rv;
|
|
2562
|
+
if ((rv = lex_interp< re_string_double_open, re_string_double_close >())) return rv;
|
|
2563
|
+
if ((rv = lex_interp< re_string_single_open, re_string_single_close >())) return rv;
|
|
2564
|
+
return rv;
|
|
2565
|
+
}
|
|
2566
|
+
|
|
2567
|
+
ExpressionObj Parser::lex_almost_any_value_chars()
|
|
2568
|
+
{
|
|
2569
|
+
const char* match =
|
|
2570
|
+
lex <
|
|
2571
|
+
one_plus <
|
|
2572
|
+
alternatives <
|
|
2573
|
+
exactly <'>'>,
|
|
2574
|
+
sequence <
|
|
2575
|
+
exactly <'\\'>,
|
|
2576
|
+
any_char
|
|
2577
|
+
>,
|
|
2578
|
+
sequence <
|
|
2579
|
+
negate <
|
|
2580
|
+
sequence <
|
|
2581
|
+
exactly < url_kwd >,
|
|
2582
|
+
exactly <'('>
|
|
2583
|
+
>
|
|
2584
|
+
>,
|
|
2585
|
+
neg_class_char <
|
|
2586
|
+
almost_any_value_class
|
|
2587
|
+
>
|
|
2588
|
+
>,
|
|
2589
|
+
sequence <
|
|
2590
|
+
exactly <'/'>,
|
|
2591
|
+
negate <
|
|
2592
|
+
alternatives <
|
|
2593
|
+
exactly <'/'>,
|
|
2594
|
+
exactly <'*'>
|
|
2595
|
+
>
|
|
2596
|
+
>
|
|
2597
|
+
>,
|
|
2598
|
+
sequence <
|
|
2599
|
+
exactly <'\\'>,
|
|
2600
|
+
exactly <'#'>,
|
|
2601
|
+
negate <
|
|
2602
|
+
exactly <'{'>
|
|
2603
|
+
>
|
|
2604
|
+
>,
|
|
2605
|
+
sequence <
|
|
2606
|
+
exactly <'!'>,
|
|
2607
|
+
negate <
|
|
2608
|
+
alpha
|
|
2609
|
+
>
|
|
2610
|
+
>
|
|
2611
|
+
>
|
|
2612
|
+
>
|
|
2613
|
+
>(false);
|
|
2614
|
+
if (match) {
|
|
2615
|
+
return SASS_MEMORY_NEW(String_Constant, pstate, lexed);
|
|
2616
|
+
}
|
|
2617
|
+
return {};
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
ExpressionObj Parser::lex_almost_any_value_token()
|
|
2621
|
+
{
|
|
2622
|
+
ExpressionObj rv;
|
|
2623
|
+
if (*position == 0) return {};
|
|
2624
|
+
if ((rv = lex_almost_any_value_chars())) return rv;
|
|
2625
|
+
// if ((rv = lex_block_comment())) return rv;
|
|
2626
|
+
// if ((rv = lex_single_line_comment())) return rv;
|
|
2627
|
+
if ((rv = lex_interp_string())) return rv;
|
|
2628
|
+
if ((rv = lex_interp_uri())) return rv;
|
|
2629
|
+
if ((rv = lex_interpolation())) return rv;
|
|
2630
|
+
if (lex< alternatives< hex, hex0 > >())
|
|
2631
|
+
{ return lexed_hex_color(lexed); }
|
|
2632
|
+
return rv;
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
String_Schema_Obj Parser::parse_almost_any_value()
|
|
2636
|
+
{
|
|
2637
|
+
|
|
2638
|
+
String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate);
|
|
2639
|
+
if (*position == 0) return {};
|
|
2640
|
+
lex < spaces >(false);
|
|
2641
|
+
ExpressionObj token = lex_almost_any_value_token();
|
|
2642
|
+
if (!token) return {};
|
|
2643
|
+
schema->append(token);
|
|
2644
|
+
if (*position == 0) {
|
|
2645
|
+
schema->rtrim();
|
|
2646
|
+
return schema.detach();
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2649
|
+
while ((token = lex_almost_any_value_token())) {
|
|
2650
|
+
schema->append(token);
|
|
2651
|
+
}
|
|
2652
|
+
|
|
2653
|
+
lex < css_whitespace >();
|
|
2654
|
+
|
|
2655
|
+
schema->rtrim();
|
|
2656
|
+
|
|
2657
|
+
return schema.detach();
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
WarningRuleObj Parser::parse_warning()
|
|
2661
|
+
{
|
|
2662
|
+
if (stack.back() != Scope::Root &&
|
|
2663
|
+
stack.back() != Scope::Function &&
|
|
2664
|
+
stack.back() != Scope::Mixin &&
|
|
2665
|
+
stack.back() != Scope::Control &&
|
|
2666
|
+
stack.back() != Scope::Rules) {
|
|
2667
|
+
error("Illegal nesting: Only properties may be nested beneath properties.");
|
|
2668
|
+
}
|
|
2669
|
+
return SASS_MEMORY_NEW(WarningRule, pstate, parse_list(DELAYED));
|
|
2670
|
+
}
|
|
2671
|
+
|
|
2672
|
+
ErrorRuleObj Parser::parse_error()
|
|
2673
|
+
{
|
|
2674
|
+
if (stack.back() != Scope::Root &&
|
|
2675
|
+
stack.back() != Scope::Function &&
|
|
2676
|
+
stack.back() != Scope::Mixin &&
|
|
2677
|
+
stack.back() != Scope::Control &&
|
|
2678
|
+
stack.back() != Scope::Rules) {
|
|
2679
|
+
error("Illegal nesting: Only properties may be nested beneath properties.");
|
|
2680
|
+
}
|
|
2681
|
+
return SASS_MEMORY_NEW(ErrorRule, pstate, parse_list(DELAYED));
|
|
2682
|
+
}
|
|
2683
|
+
|
|
2684
|
+
DebugRuleObj Parser::parse_debug()
|
|
2685
|
+
{
|
|
2686
|
+
if (stack.back() != Scope::Root &&
|
|
2687
|
+
stack.back() != Scope::Function &&
|
|
2688
|
+
stack.back() != Scope::Mixin &&
|
|
2689
|
+
stack.back() != Scope::Control &&
|
|
2690
|
+
stack.back() != Scope::Rules) {
|
|
2691
|
+
error("Illegal nesting: Only properties may be nested beneath properties.");
|
|
2692
|
+
}
|
|
2693
|
+
return SASS_MEMORY_NEW(DebugRule, pstate, parse_list(DELAYED));
|
|
2694
|
+
}
|
|
2695
|
+
|
|
2696
|
+
Return_Obj Parser::parse_return_directive()
|
|
2697
|
+
{
|
|
2698
|
+
// check that we do not have an empty list (ToDo: check if we got all cases)
|
|
2699
|
+
if (peek_css < alternatives < exactly < ';' >, exactly < '}' >, end_of_file > >())
|
|
2700
|
+
{ css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); }
|
|
2701
|
+
return SASS_MEMORY_NEW(Return, pstate, parse_list());
|
|
2702
|
+
}
|
|
2703
|
+
|
|
2704
|
+
Lookahead Parser::lookahead_for_selector(const char* start)
|
|
2705
|
+
{
|
|
2706
|
+
// init result struct
|
|
2707
|
+
Lookahead rv = Lookahead();
|
|
2708
|
+
// get start position
|
|
2709
|
+
const char* p = start ? start : position;
|
|
2710
|
+
// match in one big "regex"
|
|
2711
|
+
rv.error = p;
|
|
2712
|
+
if (const char* q =
|
|
2713
|
+
peek <
|
|
2714
|
+
re_selector_list
|
|
2715
|
+
>(p)
|
|
2716
|
+
) {
|
|
2717
|
+
bool could_be_property = peek< sequence< exactly<'-'>, exactly<'-'> > >(p) != 0;
|
|
2718
|
+
bool could_be_escaped = false;
|
|
2719
|
+
while (p < q) {
|
|
2720
|
+
// did we have interpolations?
|
|
2721
|
+
if (*p == '#' && *(p+1) == '{') {
|
|
2722
|
+
rv.has_interpolants = true;
|
|
2723
|
+
p = q; break;
|
|
2724
|
+
}
|
|
2725
|
+
// A property that's ambiguous with a nested selector is interpreted as a
|
|
2726
|
+
// custom property.
|
|
2727
|
+
if (*p == ':' && !could_be_escaped) {
|
|
2728
|
+
rv.is_custom_property = could_be_property || p+1 == q || peek< space >(p+1);
|
|
2729
|
+
}
|
|
2730
|
+
could_be_escaped = *p == '\\';
|
|
2731
|
+
++ p;
|
|
2732
|
+
}
|
|
2733
|
+
// store anyway }
|
|
2734
|
+
|
|
2735
|
+
|
|
2736
|
+
// ToDo: remove
|
|
2737
|
+
rv.error = q;
|
|
2738
|
+
rv.position = q;
|
|
2739
|
+
// check expected opening bracket
|
|
2740
|
+
// only after successful matching
|
|
2741
|
+
if (peek < exactly<'{'> >(q)) rv.found = q;
|
|
2742
|
+
// else if (peek < end_of_file >(q)) rv.found = q;
|
|
2743
|
+
else if (peek < exactly<'('> >(q)) rv.found = q;
|
|
2744
|
+
// else if (peek < exactly<';'> >(q)) rv.found = q;
|
|
2745
|
+
// else if (peek < exactly<'}'> >(q)) rv.found = q;
|
|
2746
|
+
if (rv.found || *p == 0) rv.error = 0;
|
|
2747
|
+
}
|
|
2748
|
+
|
|
2749
|
+
rv.parsable = ! rv.has_interpolants;
|
|
2750
|
+
|
|
2751
|
+
// return result
|
|
2752
|
+
return rv;
|
|
2753
|
+
|
|
2754
|
+
}
|
|
2755
|
+
// EO lookahead_for_selector
|
|
2756
|
+
|
|
2757
|
+
// used in parse_block_nodes and parse_special_directive
|
|
2758
|
+
// ToDo: actual usage is still not really clear to me?
|
|
2759
|
+
Lookahead Parser::lookahead_for_include(const char* start)
|
|
2760
|
+
{
|
|
2761
|
+
// we actually just lookahead for a selector
|
|
2762
|
+
Lookahead rv = lookahead_for_selector(start);
|
|
2763
|
+
// but the "found" rules are different
|
|
2764
|
+
if (const char* p = rv.position) {
|
|
2765
|
+
// check for additional abort condition
|
|
2766
|
+
if (peek < exactly<';'> >(p)) rv.found = p;
|
|
2767
|
+
else if (peek < exactly<'}'> >(p)) rv.found = p;
|
|
2768
|
+
}
|
|
2769
|
+
// return result
|
|
2770
|
+
return rv;
|
|
2771
|
+
}
|
|
2772
|
+
// EO lookahead_for_include
|
|
2773
|
+
|
|
2774
|
+
// look ahead for a token with interpolation in it
|
|
2775
|
+
// we mostly use the result if there is an interpolation
|
|
2776
|
+
// everything that passes here gets parsed as one schema
|
|
2777
|
+
// meaning it will not be parsed as a space separated list
|
|
2778
|
+
Lookahead Parser::lookahead_for_value(const char* start)
|
|
2779
|
+
{
|
|
2780
|
+
// init result struct
|
|
2781
|
+
Lookahead rv = Lookahead();
|
|
2782
|
+
// get start position
|
|
2783
|
+
const char* p = start ? start : position;
|
|
2784
|
+
// match in one big "regex"
|
|
2785
|
+
if (const char* q =
|
|
2786
|
+
peek <
|
|
2787
|
+
non_greedy <
|
|
2788
|
+
alternatives <
|
|
2789
|
+
// consume whitespace
|
|
2790
|
+
block_comment, // spaces,
|
|
2791
|
+
// main tokens
|
|
2792
|
+
sequence <
|
|
2793
|
+
interpolant,
|
|
2794
|
+
optional <
|
|
2795
|
+
quoted_string
|
|
2796
|
+
>
|
|
2797
|
+
>,
|
|
2798
|
+
identifier,
|
|
2799
|
+
variable,
|
|
2800
|
+
// issue #442
|
|
2801
|
+
sequence <
|
|
2802
|
+
parenthese_scope,
|
|
2803
|
+
interpolant,
|
|
2804
|
+
optional <
|
|
2805
|
+
quoted_string
|
|
2806
|
+
>
|
|
2807
|
+
>
|
|
2808
|
+
>,
|
|
2809
|
+
sequence <
|
|
2810
|
+
// optional_spaces,
|
|
2811
|
+
alternatives <
|
|
2812
|
+
// end_of_file,
|
|
2813
|
+
exactly<'{'>,
|
|
2814
|
+
exactly<'}'>,
|
|
2815
|
+
exactly<';'>
|
|
2816
|
+
>
|
|
2817
|
+
>
|
|
2818
|
+
>
|
|
2819
|
+
>(p)
|
|
2820
|
+
) {
|
|
2821
|
+
if (p == q) return rv;
|
|
2822
|
+
while (p < q) {
|
|
2823
|
+
// did we have interpolations?
|
|
2824
|
+
if (*p == '#' && *(p+1) == '{') {
|
|
2825
|
+
rv.has_interpolants = true;
|
|
2826
|
+
p = q; break;
|
|
2827
|
+
}
|
|
2828
|
+
++ p;
|
|
2829
|
+
}
|
|
2830
|
+
// store anyway
|
|
2831
|
+
// ToDo: remove
|
|
2832
|
+
rv.position = q;
|
|
2833
|
+
// check expected opening bracket
|
|
2834
|
+
// only after successful matching
|
|
2835
|
+
if (peek < exactly<'{'> >(q)) rv.found = q;
|
|
2836
|
+
else if (peek < exactly<';'> >(q)) rv.found = q;
|
|
2837
|
+
else if (peek < exactly<'}'> >(q)) rv.found = q;
|
|
2838
|
+
}
|
|
2839
|
+
|
|
2840
|
+
// return result
|
|
2841
|
+
return rv;
|
|
2842
|
+
}
|
|
2843
|
+
// EO lookahead_for_value
|
|
2844
|
+
|
|
2845
|
+
void Parser::read_bom()
|
|
2846
|
+
{
|
|
2847
|
+
size_t skip = 0;
|
|
2848
|
+
sass::string encoding;
|
|
2849
|
+
bool utf_8 = false;
|
|
2850
|
+
switch ((unsigned char)position[0]) {
|
|
2851
|
+
case 0xEF:
|
|
2852
|
+
skip = check_bom_chars(position, end, utf_8_bom, 3);
|
|
2853
|
+
encoding = "UTF-8";
|
|
2854
|
+
utf_8 = true;
|
|
2855
|
+
break;
|
|
2856
|
+
case 0xFE:
|
|
2857
|
+
skip = check_bom_chars(position, end, utf_16_bom_be, 2);
|
|
2858
|
+
encoding = "UTF-16 (big endian)";
|
|
2859
|
+
break;
|
|
2860
|
+
case 0xFF:
|
|
2861
|
+
skip = check_bom_chars(position, end, utf_16_bom_le, 2);
|
|
2862
|
+
skip += (skip ? check_bom_chars(position, end, utf_32_bom_le, 4) : 0);
|
|
2863
|
+
encoding = (skip == 2 ? "UTF-16 (little endian)" : "UTF-32 (little endian)");
|
|
2864
|
+
break;
|
|
2865
|
+
case 0x00:
|
|
2866
|
+
skip = check_bom_chars(position, end, utf_32_bom_be, 4);
|
|
2867
|
+
encoding = "UTF-32 (big endian)";
|
|
2868
|
+
break;
|
|
2869
|
+
case 0x2B:
|
|
2870
|
+
skip = check_bom_chars(position, end, utf_7_bom_1, 4)
|
|
2871
|
+
| check_bom_chars(position, end, utf_7_bom_2, 4)
|
|
2872
|
+
| check_bom_chars(position, end, utf_7_bom_3, 4)
|
|
2873
|
+
| check_bom_chars(position, end, utf_7_bom_4, 4)
|
|
2874
|
+
| check_bom_chars(position, end, utf_7_bom_5, 5);
|
|
2875
|
+
encoding = "UTF-7";
|
|
2876
|
+
break;
|
|
2877
|
+
case 0xF7:
|
|
2878
|
+
skip = check_bom_chars(position, end, utf_1_bom, 3);
|
|
2879
|
+
encoding = "UTF-1";
|
|
2880
|
+
break;
|
|
2881
|
+
case 0xDD:
|
|
2882
|
+
skip = check_bom_chars(position, end, utf_ebcdic_bom, 4);
|
|
2883
|
+
encoding = "UTF-EBCDIC";
|
|
2884
|
+
break;
|
|
2885
|
+
case 0x0E:
|
|
2886
|
+
skip = check_bom_chars(position, end, scsu_bom, 3);
|
|
2887
|
+
encoding = "SCSU";
|
|
2888
|
+
break;
|
|
2889
|
+
case 0xFB:
|
|
2890
|
+
skip = check_bom_chars(position, end, bocu_1_bom, 3);
|
|
2891
|
+
encoding = "BOCU-1";
|
|
2892
|
+
break;
|
|
2893
|
+
case 0x84:
|
|
2894
|
+
skip = check_bom_chars(position, end, gb_18030_bom, 4);
|
|
2895
|
+
encoding = "GB-18030";
|
|
2896
|
+
break;
|
|
2897
|
+
default: break;
|
|
2898
|
+
}
|
|
2899
|
+
if (skip > 0 && !utf_8) error("only UTF-8 documents are currently supported; your document appears to be " + encoding);
|
|
2900
|
+
position += skip;
|
|
2901
|
+
}
|
|
2902
|
+
|
|
2903
|
+
size_t check_bom_chars(const char* src, const char *end, const unsigned char* bom, size_t len)
|
|
2904
|
+
{
|
|
2905
|
+
size_t skip = 0;
|
|
2906
|
+
if (src + len > end) return 0;
|
|
2907
|
+
for (size_t i = 0; i < len; ++i, ++skip) {
|
|
2908
|
+
if ((unsigned char) src[i] != bom[i]) return 0;
|
|
2909
|
+
}
|
|
2910
|
+
return skip;
|
|
2911
|
+
}
|
|
2912
|
+
|
|
2913
|
+
|
|
2914
|
+
ExpressionObj Parser::fold_operands(ExpressionObj base, sass::vector<ExpressionObj>& operands, Operand op)
|
|
2915
|
+
{
|
|
2916
|
+
for (size_t i = 0, S = operands.size(); i < S; ++i) {
|
|
2917
|
+
base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), op, base, operands[i]);
|
|
2918
|
+
}
|
|
2919
|
+
return base;
|
|
2920
|
+
}
|
|
2921
|
+
|
|
2922
|
+
ExpressionObj Parser::fold_operands(ExpressionObj base, sass::vector<ExpressionObj>& operands, sass::vector<Operand>& ops, size_t i)
|
|
2923
|
+
{
|
|
2924
|
+
if (String_Schema* schema = Cast<String_Schema>(base)) {
|
|
2925
|
+
// return schema;
|
|
2926
|
+
if (schema->has_interpolants()) {
|
|
2927
|
+
if (i + 1 < operands.size() && (
|
|
2928
|
+
(ops[0].operand == Sass_OP::EQ)
|
|
2929
|
+
|| (ops[0].operand == Sass_OP::ADD)
|
|
2930
|
+
|| (ops[0].operand == Sass_OP::DIV)
|
|
2931
|
+
|| (ops[0].operand == Sass_OP::MUL)
|
|
2932
|
+
|| (ops[0].operand == Sass_OP::NEQ)
|
|
2933
|
+
|| (ops[0].operand == Sass_OP::LT)
|
|
2934
|
+
|| (ops[0].operand == Sass_OP::GT)
|
|
2935
|
+
|| (ops[0].operand == Sass_OP::LTE)
|
|
2936
|
+
|| (ops[0].operand == Sass_OP::GTE)
|
|
2937
|
+
)) {
|
|
2938
|
+
ExpressionObj rhs = fold_operands(operands[i], operands, ops, i + 1);
|
|
2939
|
+
rhs = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[0], schema, rhs);
|
|
2940
|
+
return rhs;
|
|
2941
|
+
}
|
|
2942
|
+
// return schema;
|
|
2943
|
+
}
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2946
|
+
if (operands.size() > Constants::MaxCallStack) {
|
|
2947
|
+
// XXX: this is never hit via spec tests
|
|
2948
|
+
sass::ostream stm;
|
|
2949
|
+
stm << "Stack depth exceeded max of " << Constants::MaxCallStack;
|
|
2950
|
+
error(stm.str());
|
|
2951
|
+
}
|
|
2952
|
+
|
|
2953
|
+
for (size_t S = operands.size(); i < S; ++i) {
|
|
2954
|
+
if (String_Schema* schema = Cast<String_Schema>(operands[i])) {
|
|
2955
|
+
if (schema->has_interpolants()) {
|
|
2956
|
+
if (i + 1 < S) {
|
|
2957
|
+
// this whole branch is never hit via spec tests
|
|
2958
|
+
ExpressionObj rhs = fold_operands(operands[i+1], operands, ops, i + 2);
|
|
2959
|
+
rhs = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], schema, rhs);
|
|
2960
|
+
base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, rhs);
|
|
2961
|
+
return base;
|
|
2962
|
+
}
|
|
2963
|
+
base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]);
|
|
2964
|
+
return base;
|
|
2965
|
+
} else {
|
|
2966
|
+
base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]);
|
|
2967
|
+
}
|
|
2968
|
+
} else {
|
|
2969
|
+
base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]);
|
|
2970
|
+
}
|
|
2971
|
+
Binary_Expression* b = Cast<Binary_Expression>(base.ptr());
|
|
2972
|
+
if (b && ops[i].operand == Sass_OP::DIV && b->left()->is_delayed() && b->right()->is_delayed()) {
|
|
2973
|
+
base->is_delayed(true);
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
// nested binary expression are never to be delayed
|
|
2977
|
+
if (Binary_Expression* b = Cast<Binary_Expression>(base)) {
|
|
2978
|
+
if (Cast<Binary_Expression>(b->left())) base->set_delayed(false);
|
|
2979
|
+
if (Cast<Binary_Expression>(b->right())) base->set_delayed(false);
|
|
2980
|
+
}
|
|
2981
|
+
return base;
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
void Parser::error(sass::string msg)
|
|
2985
|
+
{
|
|
2986
|
+
traces.push_back(Backtrace(pstate));
|
|
2987
|
+
throw Exception::InvalidSass(pstate, traces, msg);
|
|
2988
|
+
}
|
|
2989
|
+
|
|
2990
|
+
// print a css parsing error with actual context information from parsed source
|
|
2991
|
+
void Parser::css_error(const sass::string& msg, const sass::string& prefix, const sass::string& middle, const bool trim)
|
|
2992
|
+
{
|
|
2993
|
+
int max_len = 18;
|
|
2994
|
+
const char* end = this->end;
|
|
2995
|
+
while (*end != 0) ++ end;
|
|
2996
|
+
const char* pos = peek < optional_spaces >();
|
|
2997
|
+
if (!pos) pos = position;
|
|
2998
|
+
|
|
2999
|
+
const char* last_pos(pos);
|
|
3000
|
+
if (last_pos > begin) {
|
|
3001
|
+
utf8::prior(last_pos, begin);
|
|
3002
|
+
}
|
|
3003
|
+
// backup position to last significant char
|
|
3004
|
+
while (trim && last_pos > begin&& last_pos < end) {
|
|
3005
|
+
if (!Util::ascii_isspace(static_cast<unsigned char>(*last_pos))) break;
|
|
3006
|
+
utf8::prior(last_pos, begin);
|
|
3007
|
+
}
|
|
3008
|
+
|
|
3009
|
+
bool ellipsis_left = false;
|
|
3010
|
+
const char* pos_left(last_pos);
|
|
3011
|
+
const char* end_left(last_pos);
|
|
3012
|
+
|
|
3013
|
+
if (*pos_left) utf8::next(pos_left, end);
|
|
3014
|
+
if (*end_left) utf8::next(end_left, end);
|
|
3015
|
+
while (pos_left > begin) {
|
|
3016
|
+
if (utf8::distance(pos_left, end_left) >= max_len) {
|
|
3017
|
+
utf8::prior(pos_left, begin);
|
|
3018
|
+
ellipsis_left = *(pos_left) != '\n' &&
|
|
3019
|
+
*(pos_left) != '\r';
|
|
3020
|
+
utf8::next(pos_left, end);
|
|
3021
|
+
break;
|
|
3022
|
+
}
|
|
3023
|
+
|
|
3024
|
+
const char* prev = pos_left;
|
|
3025
|
+
utf8::prior(prev, begin);
|
|
3026
|
+
if (*prev == '\r') break;
|
|
3027
|
+
if (*prev == '\n') break;
|
|
3028
|
+
pos_left = prev;
|
|
3029
|
+
}
|
|
3030
|
+
if (pos_left < begin) {
|
|
3031
|
+
pos_left = begin;
|
|
3032
|
+
}
|
|
3033
|
+
|
|
3034
|
+
bool ellipsis_right = false;
|
|
3035
|
+
const char* end_right(pos);
|
|
3036
|
+
const char* pos_right(pos);
|
|
3037
|
+
while (end_right < end) {
|
|
3038
|
+
if (utf8::distance(pos_right, end_right) > max_len) {
|
|
3039
|
+
ellipsis_left = *(pos_right) != '\n' &&
|
|
3040
|
+
*(pos_right) != '\r';
|
|
3041
|
+
break;
|
|
3042
|
+
}
|
|
3043
|
+
if (*end_right == '\r') break;
|
|
3044
|
+
if (*end_right == '\n') break;
|
|
3045
|
+
utf8::next(end_right, end);
|
|
3046
|
+
}
|
|
3047
|
+
// if (*end_right == 0) end_right ++;
|
|
3048
|
+
|
|
3049
|
+
sass::string left(pos_left, end_left);
|
|
3050
|
+
sass::string right(pos_right, end_right);
|
|
3051
|
+
size_t left_subpos = left.size() > 15 ? left.size() - 15 : 0;
|
|
3052
|
+
size_t right_subpos = right.size() > 15 ? right.size() - 15 : 0;
|
|
3053
|
+
if (left_subpos && ellipsis_left) left = ellipsis + left.substr(left_subpos);
|
|
3054
|
+
if (right_subpos && ellipsis_right) right = right.substr(right_subpos) + ellipsis;
|
|
3055
|
+
// now pass new message to the more generic error function
|
|
3056
|
+
error(msg + prefix + quote(left) + middle + quote(right));
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
}
|