Skip to content

Commit 1449674

Browse files
committed
More refactoring
1 parent f2e9250 commit 1449674

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+2033
-1704
lines changed

Makefile.conf

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,16 @@ HPPFILES = \
5151
exceptions.hpp \
5252
settings.hpp \
5353
memory.hpp \
54-
memory/config.hpp \
55-
memory/allocator.hpp \
56-
memory/shared_ptr.hpp \
57-
memory/memory_pool.hpp \
54+
memory_config.hpp \
55+
memory_allocator.hpp \
56+
memory_pool.hpp \
57+
shared_ptr.hpp \
5858
MurmurHash2.hpp \
5959
eval.hpp \
6060
extender.hpp \
6161
extension.hpp \
6262
file.hpp \
63+
import.hpp \
6364
compiler.hpp \
6465
fn_meta.hpp \
6566
fn_maps.hpp \
@@ -142,6 +143,7 @@ SOURCES = \
142143
ast_sel_super.cpp \
143144
ast_sel_weave.cpp \
144145
ast_selectors.cpp \
146+
memory_allocator.cpp \
145147
modules.cpp \
146148
constants.cpp \
147149
compiler.cpp \
@@ -156,6 +158,7 @@ SOURCES = \
156158
environment.cpp \
157159
ast_fwd_decl.cpp \
158160
file.cpp \
161+
import.cpp \
159162
string_utils.cpp \
160163
logger.cpp \
161164
strings.cpp \
@@ -205,9 +208,7 @@ SOURCES = \
205208
source_state.cpp \
206209
source_span.cpp \
207210
exceptions.cpp \
208-
memory/allocator.cpp \
209-
memory/shared_ptr.cpp \
210-
LUrlParser/LUrlParser.cpp \
211+
shared_ptr.cpp \
211212
unicode.cpp \
212213
cencode.cpp
213214

docs/dev-env-stacks.md

Lines changed: 85 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,38 +6,113 @@ for implementers. It documents how variable stacks are implemented.
66
## Foreword
77

88
LibSass uses an optimized stack approach similar to how C compilers have always
9-
done it, by using a growable stack where we can push and pop items of. Unfortunately
9+
done it, by using a growable stack where we can push and pop items. Unfortunately
1010
Sass has proven to be a bit more dynamic than static optimizers like, therefore we
1111
had to adopt the principle a little to accommodate the edge-cases due to this.
1212

1313
There are three different kind of entities on the stack during runtime, namely
14-
variables, functions and mixins. Each has it's own dedicated stack to optimize
14+
variables, functions and mixins. Each has its own dedicated stack to optimize
1515
the lookups. In this doc we will often only cover one case, but it should be
16-
applicable to any other stack object (with some small differences).
16+
applicable to any other stack object (with some small differences). Variables
17+
are the most complicated ones, as functions and mixins can only be declared
18+
on the root scope, so loops or functions don't need to be considered for them.
1719

1820
Also for regular sass code and style-rules we wouldn't need this setup, but
1921
it becomes essential to correctly support mixins and functions, since those
20-
can be called recursively. It is also vital for loops, like @for or @each.
22+
can be called recursively. It is also vital for loops, like `@for` or `@each`.
2123

2224
## Overview
2325

2426
The whole process is split into two main phases. In order to correctly support
25-
@import we had to introduce the preloader phase, where all @use, @forward and
26-
@import rules are loaded first, before any evaluation happens. This ensures that
27+
`@import` we had to introduce the preloader phase, where all `@use`, `@forward` and
28+
`@import` rules are loaded first, before any evaluation happens. This ensures that
2729
we know all entities before the evaluation phase in order to correctly setup
28-
all stack frames.
30+
all stack frames before populating them.
31+
32+
### Parser/EnvFrame phase
33+
34+
During parsing every potential scope block creates an `EnvFrame`, which is
35+
stored at the corresponding ast-node (e.g. on a `StyleRule`). The `EnvFrame`
36+
is designed as a RAII stack object. It adds itself to the frame-stack on
37+
creation and removes itself once it goes out of scope. Additionally it
38+
creates an `EnvRefs` instance on the heap, which is later used by the
39+
compile/evaluation phase.
40+
41+
### Compiler/EnvScope phase
42+
43+
The `EnvScope` is similar to the `EnvFrame`, as it is also designed to be a RAII
44+
stack object. On creation it will increase the stack for each entity and update
45+
the corresponding offset pointers and reverts it when it goes out of scope.
46+
47+
### EnvRefs heap object
48+
49+
The `EnvRefs` object is the main object holding all the information about a
50+
block scope. It mainly holds three (flat) maps, one for every entity type.
51+
These maps are used to resolve a name to an integer offset. Whenever a new
52+
entity (e.g. variable assignment), the item is added to the map if it does
53+
not already exist there and the offset is simply increased (size of the map).
54+
55+
### EnvRoot object
56+
57+
The `EnvRoot` is the main object where entities are stored. It mainly holds
58+
two stack vectors for every entity type. The actual stack vector and an
59+
additional vector holding the previous stack size (or offset position).
60+
Further it holds on to all created `EnvRefs` and all built-in entities.
61+
62+
### Frame offset pointers
63+
64+
Every `EnvRefs` object get a unique number, increasing from 0 (which is
65+
reserved for the root scope block). The `EnvRoot` objects has a vector
66+
containing all `EnvRefs` which should correspond to that index offset.
2967

3068
## Basic example
3169

3270
Let's assume we have the following scss code:
3371

3472
```scss
35-
$a: 1;
73+
$a: a;
3674
b {
37-
$a: 2;
75+
$a: b;
3876
}
3977
```
4078

79+
### Parsing phase
80+
81+
The parser will first initialize the `EnvRoot` with the root `EnvRefs`.
82+
First it will parse the top variable assignment, which will create a new
83+
entry in the variable map of the `EnvRefs` heap object.
84+
85+
It will then parse the `StyleRule` and create a `EnvFrame` (which will also
86+
create and register a new `EnvRefs` heap object). It will then parse the inner
87+
assignment and create a new entry in the new `EnvRefs` of the `StyleRule`.
88+
89+
### Evaluation phase
90+
91+
First the compiler will create an `EnvScope` stack object, which will increase
92+
the stack size of `EnvRoot` accordingly. In this case it will increase the size
93+
of `varStack` by one to accommodate the single local variable. Note that the
94+
variable object itself is still undefined at this point, but the slot on the
95+
stack exists now. The `EnvScope` will also remember that it has to decrease
96+
that stack vector by one, once it goes out of scope.
97+
98+
On the assignment the compiler knows that variable `$a` can be found at the
99+
local offset `0` (as stored within the `EnvRefs` map). Now it only needs to
100+
add the current `EnvRefs` position on the `varStack` to get the absolute
101+
address of the requested variable.
102+
103+
104+
105+
when it sees `b`, it will
106+
create and assign a new `EnvFrame` with the `StyleRule`. Each `EnvRefs`
107+
will have one Variable `$a` with the offset `0`. On runtime the compiler will
108+
first evaluate the top assignment rule, thus assigning the string `a` to the
109+
variable at offset `0` at the current active scope (root). Then it evaluates
110+
the ruleset and creates a new `EnvScope`, which will push new instances onto
111+
the env-stack for all previously parsed entities (one variable in this case).
112+
113+
We now have two variables on the actual env-scope with the inner still undefined.
114+
115+
41116
This will allocate two independent variables on the stack. For easier reference
42117
we can think of them as variable 0 and variable 1. So let's see what happens if
43118
we introduce some VariableExpressions:
@@ -59,7 +134,7 @@ As you may have guesses, the `a0` expression will reference variable 0 and the
59134
variable 0 again. Given this easy example this might seem overengineered, but
60135
let's see what happens if we introduce a loop:
61136

62-
```
137+
```scss
63138
$a: 1;
64139
b {
65140
@for $x from 1 through 2 {

docs/dev-var-scoping.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Sass variable scoping
2+
3+
Variable scoping in Sass has some quirks which I'll try to
4+
explain in this document. This partially also applies to
5+
functions and mixins, as they are scope objects like variables
6+
too. But they can only be defined on the root scope, so their
7+
logic is not that complicated as variables.
8+
9+
## Variable declarations in Sass
10+
11+
Sass doesn't have explicit variable declarations. Variables
12+
are declared whenever an assignment to a variable happens. It
13+
is then local to the scope block the assignment was in. E.g.
14+
every ruleset (opened via `{` in scss) is a new scope block.
15+
16+
On the surface this makes variable scoping quite simple, as
17+
declarations always happen with the definition of the variable.
18+
Under the hood it has some quirks, mainly in combination with loops.
19+
20+
## Block scopes and (semi) transparent loops
21+
22+
Consider the following example:
23+
24+
```scss
25+
$a: 0;
26+
@for $i from 1 through 3 {
27+
@debug $a;
28+
$a: $i;
29+
}
30+
@debug $a
31+
```
32+
33+
The first `@debug` calls should be easy to guess (0,1,2). But the
34+
last `@debug` is a bit trickier. In this case the `@for` loop is
35+
transparent and the assignment inside it is made to the outer
36+
variable on the root scope and will be reported as `3`.
37+
38+
Now lets wrap the loop into a ruleset scope:
39+
40+
```scss
41+
$b: 0;
42+
a {
43+
@for $i from 1 through 3 {
44+
@debug $b;
45+
$b: $i;
46+
}
47+
@debug $b
48+
}
49+
```
50+
51+
Again, the inner `@debug` calls are simply 0,1 and 2 and the
52+
tricky question is again what the last `@debug` call will yield?
53+
You might have guess it will report `3` again, but that is not
54+
correct, as Sass will report `0` here.
55+
56+
Conclusion: Loops like `@for`, `@each` and `@while` are transparent
57+
only on the root-scope, but not on any inner scopes.

0 commit comments

Comments
 (0)