-
Notifications
You must be signed in to change notification settings - Fork 86
Description
As part of the on-going effort to become spec-compliant and to make it easier to implement modern sass features in the future, I'm convinced we need to modernize the architecture of the code to make it easier to work on it. I'm also convinced that we should move towards an object-oriented codebase instead of all these array($type, $content, $extra) structures, as that will help a lot (especially given that the current array shapes are different for each type).
In order to make it easier to implement modern sass features, looking at the architecture of dart-sass makes sense IMO. The closer we are, the easier it would be to port new features. Here is the high-level parts of the dart-sass architecture:
Valuevalue objects (one sub-class per value type) to represent Sass values (equivalent to whatzvalrepresents in PHP for instance)Ast\Sassto represent the Sass ASTAst\Cssto represent a CSS ASTParserwhich takes an input (string) and produces a Sass AST (they actually have 3 parsers, one for Scss, one for Sass and one for CSS which handles forbidding most Sass syntaxes when importing CSS files)Evaluatorwhich walks the Sass AST to produce a CSS ASTSerializerwhich turns the CSS AST into a string (the output)Environmentwhich handles all the state of the evaluator (scopes, defined functions, modules, etc...)Compilerwhich is the public API, which does nothing except calling the Parser then the Evaluator and then the Serializer
Here is the current architecture of scssphp:
- the
array($type, $content, $extra)shapes are representing both the Sass AST and Sass values, without a clear distinction. TheNumberobject is also part of that, which is used to represent the number values, but still needs to support ArrayAccess reads to be part of the AST \ScssPhp\ScssPhp\Formatter\OutputBlockis what comes closer to the CSS AST, except it has no explicit knowledge about the structure of CSS\ScssPhp\ScssPhp\Parseris the scss and css parser (we don't support the indented sass syntax)\ScssPhp\ScssPhp\Formatter(and its subclasses) is what comes closer to the dart-sass Serializer, except that it is a lot harder to understand due to the weird representation it operates on\ScssPhp\ScssPhp\Compileris the public API, the evaluator, the implementation of builtin APIs, part of the parser (forbidding@returnoutside functions is implemented in the compiler in Scssphp rather than in the parser for instance) and parts of the serializer\ScssPhp\ScssPhp\Compiler\Environmenthandles the state (but as a single scope, which creates conflicts between names of mixins, functions and variables, which is not spec-compliant AFAIK)
Long term, I think having typed values, typed Sass AST and typed CSS AST (being the input of serializers/formatters) would make sense. But this is a huge rewrite. As such, I suggest an incremental process:
- Separate values and AST in our
array($type, $content, $extra)structure - Migrate to object-oriented values
- (later) Rework the Sass AST
- (even later) Rework the evaluation and serialization to use a CSS AST instead of
\ScssPhp\ScssPhp\Formatter\OutputBlock
Separation of values and AST
AST nodes actually belong to 2 families:
- structural nodes (at-rules, statements, rules, variable declarations, etc...)
- expressions (which appear in many places inside structural nodes, but can only contain expression nodes as children)
I suggest to segment our type values into 3 categories:
- structure nodes
- expression nodes
- values
This will require introducing new types for cases which are ambiguous today:
- a
ValueExpressionto wrap places where we have a basic value in the AST of an expression - distinguish map values from map expressions (which evaluate to a map value)
- distinguish list values from list expressions (which evaluate to a map value)
- distinguish sass string values from string expressions (which evaluate to a sass string after resolving interpolation)
We would rework our evaluation of expressions to clearly distinguish places dealing with an expression or a value (Compiler::reduce is basically evaluateExpression today). Methods would either take a node as argument or a value, but not both.
Object-oriented values
Once values and nodes are treated separately, we can migrate a proper object-oriented API for values instead of using arrays.
I suggest using the Value subnamespace for them (instead of Node used today for Number, as they are not nodes of the AST). All these classes would extend from a base Value type which will declare some base API common to all types, similar to what dart-sass does. All these value objects will be immutable.
Here is my proposal, which correspond to types available in dart-sass:
SassNumber(the existingNumberobject renamed)SassBooleanSassNullSassListSassMapSassColorSassFunction(returned byget-function)SassArgumentList extends SassList(as argument lists must also handle keyword arguments)SassStringrepresents both quoted and unquoted string, which includes identifiers, as dart-sass found out that it makes it a lot easier to implement string functions like that.
These classes will not implement ArrayAccess anymore.
Arguments to keep the Sass prefix in class names:
boolean,null,list,functionandstringare reserved keywords in PHP and so cannot be used without prefix- it makes it clear that they represent values from the Sass language, and so are subject to the Sass rules (for instance, an empty string is truthy in Sass). This makes it easier when we will deal with PHP types and Sass values in the code.
@Cerdic does this plan looks good to you ?