If you are in web development, maybe you’ve heard of Sass, Less, Pug, Stylus etc. All these are pre-processors. In this tutorial we’re going to build nothing less than a functional css pre-processor from scratch with variables and functions. This type of new language is called source-to-source compiled. If you are thrilled, i hope not to disappoint you.
Charting The Routes
First let’s see what we’ll be doing. We’ll call the language DotDot with extention .dot
When designing a new language, it’s good to spend sometimes on design. If you have something that really flows, you can start coding.
Here is a DotDot snippet:
x = 1y = 5x-y-z = 6col-z = berry@f{border: 10px solid black;border-radius: 50%;}.add{color:white;background-color: rgb(3, 4, 5);}#ad{color: rgb(x, 4, 5);}.a div a{color: ..col-z;@f}x = 1 y = 5 x-y-z = 6 col-z = berry @f{ border: 10px solid black; border-radius: 50%; } .add{ color:white; background-color: rgb(3, 4, 5); } #ad{ color: rgb(x, 4, 5); } .a div a{ color: ..col-z; @f }x = 1 y = 5 x-y-z = 6 col-z = berry @f{ border: 10px solid black; border-radius: 50%; } .add{ color:white; background-color: rgb(3, 4, 5); } #ad{ color: rgb(x, 4, 5); } .a div a{ color: ..col-z; @f }
Enter fullscreen mode Exit fullscreen mode
which compiles to this:
.add{color: white;background-color: rgb(3,4,5);}#ad{color: rgb(1,4,5);}.a div a{color: berry;border: 10px solid black;border-radius: 50%;}.add{ color: white; background-color: rgb(3,4,5); } #ad{ color: rgb(1,4,5); } .a div a{ color: berry; border: 10px solid black; border-radius: 50%; }.add{ color: white; background-color: rgb(3,4,5); } #ad{ color: rgb(1,4,5); } .a div a{ color: berry; border: 10px solid black; border-radius: 50%; }
Enter fullscreen mode Exit fullscreen mode
Dissecting Design
Let’s see what features we included
Variables
x = 1y = 5x-y-z = 6col-z = berryx = 1 y = 5 x-y-z = 6 col-z = berryx = 1 y = 5 x-y-z = 6 col-z = berry
Enter fullscreen mode Exit fullscreen mode
We see that
- we don’t need to specify a specifier like $ or var in Js.
- we can add – in the variable name
We can also call variables in function values
rgb(x, 4, 5);rgb(x, 4, 5);rgb(x, 4, 5);
Enter fullscreen mode Exit fullscreen mode
And in attribute values
color: ..col-z;color: ..col-z;color: ..col-z;
Enter fullscreen mode Exit fullscreen mode
Where .. denotes a variable call in attribute directly.
Functions
@f{border: 10px solid black;border-radius: 50%;}@f{ border: 10px solid black; border-radius: 50%; }@f{ border: 10px solid black; border-radius: 50%; }
Enter fullscreen mode Exit fullscreen mode
Functions are denoted by an @ sign. These are called as in the following case where it expands into the properties it contains.
.a div a{color: ..col-z;@f}.a div a{ color: ..col-z; @f }.a div a{ color: ..col-z; @f }
Enter fullscreen mode Exit fullscreen mode
That is enough complexity to deal with. Let’s start!
<span>import</span> <span>copy</span> <span># we have just one import </span><span>import</span> <span>copy</span> <span># we have just one import </span>import copy # we have just one import
Enter fullscreen mode Exit fullscreen mode
and we put the source as a variable
<span>source</span> <span>=</span> <span>'''</span><span> x = 1 y = 5 x-y-z = 6 col-z = berry @f{ border: 10px solid black; border-radius: 50%; } .add{ color:white; background-color: rgb(3, 4, 5); } #ad{ color: rgb(x, 4, 5); } .a div a{ color: ..col-z; @f } </span><span>'''</span><span>source</span> <span>=</span> <span>'''</span><span> x = 1 y = 5 x-y-z = 6 col-z = berry @f{ border: 10px solid black; border-radius: 50%; } .add{ color:white; background-color: rgb(3, 4, 5); } #ad{ color: rgb(x, 4, 5); } .a div a{ color: ..col-z; @f } </span><span>'''</span>source = ''' x = 1 y = 5 x-y-z = 6 col-z = berry @f{ border: 10px solid black; border-radius: 50%; } .add{ color:white; background-color: rgb(3, 4, 5); } #ad{ color: rgb(x, 4, 5); } .a div a{ color: ..col-z; @f } '''
Enter fullscreen mode Exit fullscreen mode
Defining Constants
We’ve bundled our constants in a class.
<span>class</span> <span>SYM</span><span>:</span><span>LEFT_BRACE</span> <span>=</span> <span>'</span><span>{</span><span>'</span><span>RIGHT_BRACE</span> <span>=</span> <span>'</span><span>}</span><span>'</span><span>LEFT_ROUND</span> <span>=</span> <span>'</span><span>(</span><span>'</span><span>RIGHT_ROUND</span> <span>=</span> <span>'</span><span>)</span><span>'</span><span>NEW_LINE</span> <span>=</span> <span>'</span><span>\n</span><span>'</span><span>TAB</span> <span>=</span> <span>'</span><span> </span><span>'</span><span>COLON</span> <span>=</span> <span>'</span><span>:</span><span>'</span><span>SEMI_COLON</span> <span>=</span> <span>'</span><span>;</span><span>'</span><span>SPACE</span> <span>=</span> <span>'</span><span> </span><span>'</span><span>COMMA</span> <span>=</span> <span>'</span><span>,</span><span>'</span><span>EQUAL</span> <span>=</span> <span>'</span><span>=</span><span>'</span><span>UNION</span> <span>=</span> <span>'</span><span>-</span><span>'</span><span>DOT</span> <span>=</span> <span>'</span><span>.</span><span>'</span><span>AT</span> <span>=</span> <span>'</span><span>@</span><span>'</span><span>class</span> <span>SYM</span><span>:</span> <span>LEFT_BRACE</span> <span>=</span> <span>'</span><span>{</span><span>'</span> <span>RIGHT_BRACE</span> <span>=</span> <span>'</span><span>}</span><span>'</span> <span>LEFT_ROUND</span> <span>=</span> <span>'</span><span>(</span><span>'</span> <span>RIGHT_ROUND</span> <span>=</span> <span>'</span><span>)</span><span>'</span> <span>NEW_LINE</span> <span>=</span> <span>'</span><span>\n</span><span>'</span> <span>TAB</span> <span>=</span> <span>'</span><span> </span><span>'</span> <span>COLON</span> <span>=</span> <span>'</span><span>:</span><span>'</span> <span>SEMI_COLON</span> <span>=</span> <span>'</span><span>;</span><span>'</span> <span>SPACE</span> <span>=</span> <span>'</span><span> </span><span>'</span> <span>COMMA</span> <span>=</span> <span>'</span><span>,</span><span>'</span> <span>EQUAL</span> <span>=</span> <span>'</span><span>=</span><span>'</span> <span>UNION</span> <span>=</span> <span>'</span><span>-</span><span>'</span> <span>DOT</span> <span>=</span> <span>'</span><span>.</span><span>'</span> <span>AT</span> <span>=</span> <span>'</span><span>@</span><span>'</span>class SYM: LEFT_BRACE = '{' RIGHT_BRACE = '}' LEFT_ROUND = '(' RIGHT_ROUND = ')' NEW_LINE = '\n' TAB = ' ' COLON = ':' SEMI_COLON = ';' SPACE = ' ' COMMA = ',' EQUAL = '=' UNION = '-' DOT = '.' AT = '@'
Enter fullscreen mode Exit fullscreen mode
We then define our keywords
<span>KEYWORDS</span> <span>=</span> <span>(</span><span>SYM</span><span>.</span><span>LEFT_BRACE</span><span>,</span> <span>SYM</span><span>.</span><span>RIGHT_BRACE</span><span>,</span> <span>SYM</span><span>.</span><span>NEW_LINE</span><span>,</span> <span>SYM</span><span>.</span><span>TAB</span><span>,</span> <span>SYM</span><span>.</span><span>COLON</span><span>,</span><span>SYM</span><span>.</span><span>SEMI_COLON</span><span>,</span> <span>SYM</span><span>.</span><span>SPACE</span><span>,</span> <span>SYM</span><span>.</span><span>RIGHT_ROUND</span><span>,</span> <span>SYM</span><span>.</span><span>LEFT_ROUND</span><span>,</span> <span>SYM</span><span>.</span><span>COMMA</span><span>,</span> <span>SYM</span><span>.</span><span>EQUAL</span><span>)</span><span>KEYWORDS</span> <span>=</span> <span>(</span><span>SYM</span><span>.</span><span>LEFT_BRACE</span><span>,</span> <span>SYM</span><span>.</span><span>RIGHT_BRACE</span><span>,</span> <span>SYM</span><span>.</span><span>NEW_LINE</span><span>,</span> <span>SYM</span><span>.</span><span>TAB</span><span>,</span> <span>SYM</span><span>.</span><span>COLON</span><span>,</span> <span>SYM</span><span>.</span><span>SEMI_COLON</span><span>,</span> <span>SYM</span><span>.</span><span>SPACE</span><span>,</span> <span>SYM</span><span>.</span><span>RIGHT_ROUND</span><span>,</span> <span>SYM</span><span>.</span><span>LEFT_ROUND</span><span>,</span> <span>SYM</span><span>.</span><span>COMMA</span><span>,</span> <span>SYM</span><span>.</span><span>EQUAL</span><span>)</span>KEYWORDS = (SYM.LEFT_BRACE, SYM.RIGHT_BRACE, SYM.NEW_LINE, SYM.TAB, SYM.COLON, SYM.SEMI_COLON, SYM.SPACE, SYM.RIGHT_ROUND, SYM.LEFT_ROUND, SYM.COMMA, SYM.EQUAL)
Enter fullscreen mode Exit fullscreen mode
Building the Lexer
The code for the lexer is from this lexer tutorial. Please go over it if you feel the need to. Here we converted the code into a class with a get_lexeme method. The method gives us a list. The dotdot snippet above gets converted to the list held in lexemes variable below.
Here is our lexer class:
<span>class</span> <span>Lexer</span><span>:</span><span>def</span> <span>__init__</span><span>(</span><span>self</span><span>,</span> <span>source</span><span>:</span> <span>str</span><span>,</span> <span>KEYWORDS</span><span>:</span> <span>list</span><span>):</span><span>self</span><span>.</span><span>white_space</span> <span>=</span> <span>SYM</span><span>.</span><span>SPACE</span><span>self</span><span>.</span><span>KEYWORDS</span> <span>=</span> <span>KEYWORDS</span><span>self</span><span>.</span><span>lexeme</span> <span>=</span> <span>''</span><span>self</span><span>.</span><span>lexemes</span> <span>=</span> <span>[]</span><span>self</span><span>.</span><span>string</span> <span>=</span> <span>source</span><span>def</span> <span>get_lexemes</span><span>(</span><span>self</span><span>)</span> <span>-></span> <span>list</span><span>:</span><span>for</span> <span>i</span><span>,</span> <span>char</span> <span>in</span> <span>enumerate</span><span>(</span><span>self</span><span>.</span><span>string</span><span>):</span><span>if</span> <span>char</span> <span>!=</span> <span>self</span><span>.</span><span>white_space</span> <span>and</span> <span>char</span> <span>!=</span> <span>SYM</span><span>.</span><span>NEW_LINE</span><span>:</span><span>self</span><span>.</span><span>lexeme</span> <span>+=</span> <span>char</span> <span># adding a char each time </span> <span>if </span><span>(</span><span>i</span><span>+</span><span>1</span> <span>&</span><span>lt</span><span>;</span> <span>len</span><span>(</span><span>self</span><span>.</span><span>string</span><span>)):</span> <span># prevents error </span> <span>if</span> <span>self</span><span>.</span><span>string</span><span>[</span><span>i</span><span>+</span><span>1</span><span>]</span> <span>==</span> <span>self</span><span>.</span><span>white_space</span> <span>or</span> <span>self</span><span>.</span><span>string</span><span>[</span><span>i</span><span>+</span><span>1</span><span>]</span> <span>in</span> <span>KEYWORDS</span> <span>or</span> <span>self</span><span>.</span><span>lexeme</span> <span>in</span> <span>KEYWORDS</span> <span>or</span> <span>self</span><span>.</span><span>string</span><span>[</span><span>i</span><span>+</span><span>1</span><span>]</span> <span>==</span> <span>SYM</span><span>.</span><span>NEW_LINE</span><span>:</span> <span># if next char == ' ' </span> <span>if</span> <span>self</span><span>.</span><span>lexeme</span> <span>!=</span> <span>''</span><span>:</span><span>self</span><span>.</span><span>lexemes</span><span>.</span><span>append</span><span>(</span><span>self</span><span>.</span><span>lexeme</span><span>)</span><span>self</span><span>.</span><span>lexeme</span> <span>=</span> <span>''</span><span>return</span> <span>self</span><span>.</span><span>lexemes</span><span>class</span> <span>Lexer</span><span>:</span> <span>def</span> <span>__init__</span><span>(</span><span>self</span><span>,</span> <span>source</span><span>:</span> <span>str</span><span>,</span> <span>KEYWORDS</span><span>:</span> <span>list</span><span>):</span> <span>self</span><span>.</span><span>white_space</span> <span>=</span> <span>SYM</span><span>.</span><span>SPACE</span> <span>self</span><span>.</span><span>KEYWORDS</span> <span>=</span> <span>KEYWORDS</span> <span>self</span><span>.</span><span>lexeme</span> <span>=</span> <span>''</span> <span>self</span><span>.</span><span>lexemes</span> <span>=</span> <span>[]</span> <span>self</span><span>.</span><span>string</span> <span>=</span> <span>source</span> <span>def</span> <span>get_lexemes</span><span>(</span><span>self</span><span>)</span> <span>-></span> <span>list</span><span>:</span> <span>for</span> <span>i</span><span>,</span> <span>char</span> <span>in</span> <span>enumerate</span><span>(</span><span>self</span><span>.</span><span>string</span><span>):</span> <span>if</span> <span>char</span> <span>!=</span> <span>self</span><span>.</span><span>white_space</span> <span>and</span> <span>char</span> <span>!=</span> <span>SYM</span><span>.</span><span>NEW_LINE</span><span>:</span> <span>self</span><span>.</span><span>lexeme</span> <span>+=</span> <span>char</span> <span># adding a char each time </span> <span>if </span><span>(</span><span>i</span><span>+</span><span>1</span> <span>&</span><span>lt</span><span>;</span> <span>len</span><span>(</span><span>self</span><span>.</span><span>string</span><span>)):</span> <span># prevents error </span> <span>if</span> <span>self</span><span>.</span><span>string</span><span>[</span><span>i</span><span>+</span><span>1</span><span>]</span> <span>==</span> <span>self</span><span>.</span><span>white_space</span> <span>or</span> <span>self</span><span>.</span><span>string</span><span>[</span><span>i</span><span>+</span><span>1</span><span>]</span> <span>in</span> <span>KEYWORDS</span> <span>or</span> <span>self</span><span>.</span><span>lexeme</span> <span>in</span> <span>KEYWORDS</span> <span>or</span> <span>self</span><span>.</span><span>string</span><span>[</span><span>i</span><span>+</span><span>1</span><span>]</span> <span>==</span> <span>SYM</span><span>.</span><span>NEW_LINE</span><span>:</span> <span># if next char == ' ' </span> <span>if</span> <span>self</span><span>.</span><span>lexeme</span> <span>!=</span> <span>''</span><span>:</span> <span>self</span><span>.</span><span>lexemes</span><span>.</span><span>append</span><span>(</span><span>self</span><span>.</span><span>lexeme</span><span>)</span> <span>self</span><span>.</span><span>lexeme</span> <span>=</span> <span>''</span> <span>return</span> <span>self</span><span>.</span><span>lexemes</span>class Lexer: def __init__(self, source: str, KEYWORDS: list): self.white_space = SYM.SPACE self.KEYWORDS = KEYWORDS self.lexeme = '' self.lexemes = [] self.string = source def get_lexemes(self) -> list: for i, char in enumerate(self.string): if char != self.white_space and char != SYM.NEW_LINE: self.lexeme += char # adding a char each time if (i+1 < len(self.string)): # prevents error if self.string[i+1] == self.white_space or self.string[i+1] in KEYWORDS or self.lexeme in KEYWORDS or self.string[i+1] == SYM.NEW_LINE: # if next char == ' ' if self.lexeme != '': self.lexemes.append(self.lexeme) self.lexeme = '' return self.lexemes
Enter fullscreen mode Exit fullscreen mode
Then we declare our basic variables:
<span>v</span> <span>=</span> <span>Lexer</span><span>(</span><span>source</span><span>,</span> <span>KEYWORDS</span><span>)</span><span>lexemes</span> <span>=</span> <span>v</span><span>.</span><span>get_lexemes</span><span>()</span><span>lexemes</span><span>.</span><span>append</span><span>(</span><span>''</span><span>)</span> <span># appending an unused last element to properly dump all values </span><span>v</span> <span>=</span> <span>Lexer</span><span>(</span><span>source</span><span>,</span> <span>KEYWORDS</span><span>)</span> <span>lexemes</span> <span>=</span> <span>v</span><span>.</span><span>get_lexemes</span><span>()</span> <span>lexemes</span><span>.</span><span>append</span><span>(</span><span>''</span><span>)</span> <span># appending an unused last element to properly dump all values </span>v = Lexer(source, KEYWORDS) lexemes = v.get_lexemes() lexemes.append('') # appending an unused last element to properly dump all values
Enter fullscreen mode Exit fullscreen mode
lexemes is now equal to
['x', '=', '1', 'y', '=', '5', 'x-y-z', '=', '6', 'col-z','=', 'berry', '@f', '{', 'border', ':', '10px', 'solid','black', ';', 'border-radius', ':', '50%', ';', '}', '.add','{', 'color', ':', 'white', ';', 'background-color', ':','rgb', '(', '3', ',', '4', ',', '5', ')', ';', '}', '#ad','{', 'color', ':', 'rgb','(', 'x', ',', '4', ',', '5', ')',';', '}', '.a', 'div', 'a', '{', 'color', ':', '..col-z',';', '@f', '}', '']['x', '=', '1', 'y', '=', '5', 'x-y-z', '=', '6', 'col-z', '=', 'berry', '@f', '{', 'border', ':', '10px', 'solid', 'black', ';', 'border-radius', ':', '50%', ';', '}', '.add', '{', 'color', ':', 'white', ';', 'background-color', ':', 'rgb', '(', '3', ',', '4', ',', '5', ')', ';', '}', '#ad', '{', 'color', ':', 'rgb','(', 'x', ',', '4', ',', '5', ')', ';', '}', '.a', 'div', 'a', '{', 'color', ':', '..col-z', ';', '@f', '}', '']['x', '=', '1', 'y', '=', '5', 'x-y-z', '=', '6', 'col-z', '=', 'berry', '@f', '{', 'border', ':', '10px', 'solid', 'black', ';', 'border-radius', ':', '50%', ';', '}', '.add', '{', 'color', ':', 'white', ';', 'background-color', ':', 'rgb', '(', '3', ',', '4', ',', '5', ')', ';', '}', '#ad', '{', 'color', ':', 'rgb','(', 'x', ',', '4', ',', '5', ')', ';', '}', '.a', 'div', 'a', '{', 'color', ':', '..col-z', ';', '@f', '}', '']
Enter fullscreen mode Exit fullscreen mode
With such separated data, it’s much easier to proceed!
The Concept of Memory
Next we define a dictionary to hold all our variables.
memory = {}memory = {}memory = {}
Enter fullscreen mode Exit fullscreen mode
where
x = 1x = 1x = 1
Enter fullscreen mode Exit fullscreen mode
will become this later on:
<span>{</span><span>'</span><span>x</span><span>'</span><span>:</span><span>'</span><span>1</span><span>'</span><span>}</span><span>{</span><span>'</span><span>x</span><span>'</span><span>:</span><span>'</span><span>1</span><span>'</span><span>}</span>{'x':'1'}
Enter fullscreen mode Exit fullscreen mode
to retrieve it we’ll just do
<span>memory</span><span>[</span><span>'</span><span>x</span><span>'</span><span>]</span><span>memory</span><span>[</span><span>'</span><span>x</span><span>'</span><span>]</span>memory['x']
Enter fullscreen mode Exit fullscreen mode
The Concept of Tree
We’ll have a dictionary called tree
<span>tree</span> <span>=</span> <span>{}</span><span>tree</span> <span>=</span> <span>{}</span>tree = {}
Enter fullscreen mode Exit fullscreen mode
which will hold the converted code as
{'@f': {'border': '10px solid black','border-radius': '50%'},'.add': {'color':'white','background-color': 'rgb ( 3 , 4 , 5 )'}, '#ad': {'color': 'rgb ( x ,4 , 5 )'},'.a div a': {'color': '..col-z','@f': ''}}{ '@f': { 'border': '10px solid black', 'border-radius': '50%' }, '.add': { 'color':'white', 'background-color': 'rgb ( 3 , 4 , 5 )' }, '#ad': { 'color': 'rgb ( x ,4 , 5 )' }, '.a div a': { 'color': '..col-z', '@f': '' } }{ '@f': { 'border': '10px solid black', 'border-radius': '50%' }, '.add': { 'color':'white', 'background-color': 'rgb ( 3 , 4 , 5 )' }, '#ad': { 'color': 'rgb ( x ,4 , 5 )' }, '.a div a': { 'color': '..col-z', '@f': '' } }
Enter fullscreen mode Exit fullscreen mode
Our next step will exactly be this: converting the lexemes list into this dictionary
Generating The Tree
To keep track of where we are, we’ll have a series of variables taking True/False values (on/off)
Setting Up Holders
<span>id_string</span> <span>=</span> <span>''</span><span>last_id_string</span> <span>=</span> <span>''</span><span>last_attribute</span> <span>=</span> <span>''</span><span>current_attribute</span> <span>=</span> <span>''</span><span>current_value</span> <span>=</span> <span>''</span><span>len_lexemes</span> <span>=</span> <span>len</span><span>(</span><span>lexemes</span><span>)</span><span>id_string</span> <span>=</span> <span>''</span> <span>last_id_string</span> <span>=</span> <span>''</span> <span>last_attribute</span> <span>=</span> <span>''</span> <span>current_attribute</span> <span>=</span> <span>''</span> <span>current_value</span> <span>=</span> <span>''</span> <span>len_lexemes</span> <span>=</span> <span>len</span><span>(</span><span>lexemes</span><span>)</span>id_string = '' last_id_string = '' last_attribute = '' current_attribute = '' current_value = '' len_lexemes = len(lexemes)
Enter fullscreen mode Exit fullscreen mode
id string will hold like #add, .x a div etc
the last_ variables just hold variables that are not emptied upon leaving the sub section
Setting Up Flags
We’ll have 3 flags.
One when we are starting a block, which will become true when encountering a { and false when encountering a }
An attribute is color in color:black;
The attribute ongoing will become true when passing over { and ; and will become false when passing over :
value_ongoing will become true when going over : and false when going over ;
<span>block_ongoing</span> <span>=</span> <span>False</span><span>attribute_ongoing</span> <span>=</span> <span>False</span><span>value_ongoing</span> <span>=</span> <span>False</span><span>block_ongoing</span> <span>=</span> <span>False</span> <span>attribute_ongoing</span> <span>=</span> <span>False</span> <span>value_ongoing</span> <span>=</span> <span>False</span>block_ongoing = False attribute_ongoing = False value_ongoing = False
Enter fullscreen mode Exit fullscreen mode
We’ll start looping over the list and implement what we described of the flags above
<span>for</span> <span>i</span><span>,</span> <span>lex</span> <span>in</span> <span>enumerate</span><span>(</span><span>lexemes</span><span>):</span><span>if</span> <span>i</span><span>+</span><span>1</span> <span>&</span><span>lt</span><span>;</span> <span>len_lexemes</span><span>:</span><span>next_lexeme</span> <span>=</span> <span>lexemes</span><span>[</span><span>i</span><span>+</span><span>1</span><span>]</span><span>prev_lexeme</span> <span>=</span> <span>lexemes</span><span>[</span><span>i</span><span>-</span><span>1</span><span>]</span><span>if</span> <span>lex</span> <span>==</span> <span>SYM</span><span>.</span><span>LEFT_BRACE</span><span>:</span><span>block_ongoing</span> <span>=</span> <span>True</span><span>attribute_ongoing</span> <span>=</span> <span>True</span><span>continue</span><span>elif</span> <span>lex</span> <span>==</span> <span>SYM</span><span>.</span><span>RIGHT_BRACE</span><span>:</span><span>block_ongoing</span> <span>=</span> <span>False</span><span>statement_ongoing</span> <span>=</span> <span>False</span><span>attribute_ongoing</span> <span>=</span> <span>False</span><span>value_ongoing</span> <span>=</span> <span>False</span><span>continue</span><span>if</span> <span>lex</span> <span>==</span> <span>SYM</span><span>.</span><span>COLON</span><span>:</span><span>value_ongoing</span> <span>=</span> <span>True</span><span>attribute_ongoing</span> <span>=</span> <span>False</span><span>continue</span><span>elif</span> <span>lex</span> <span>==</span> <span>SYM</span><span>.</span><span>SEMI_COLON</span><span>:</span><span>value_ongoing</span> <span>=</span> <span>False</span><span>statement_ongoing</span> <span>=</span> <span>False</span><span>attribute_ongoing</span> <span>=</span> <span>True</span><span>continue</span><span>for</span> <span>i</span><span>,</span> <span>lex</span> <span>in</span> <span>enumerate</span><span>(</span><span>lexemes</span><span>):</span> <span>if</span> <span>i</span><span>+</span><span>1</span> <span>&</span><span>lt</span><span>;</span> <span>len_lexemes</span><span>:</span> <span>next_lexeme</span> <span>=</span> <span>lexemes</span><span>[</span><span>i</span><span>+</span><span>1</span><span>]</span> <span>prev_lexeme</span> <span>=</span> <span>lexemes</span><span>[</span><span>i</span><span>-</span><span>1</span><span>]</span> <span>if</span> <span>lex</span> <span>==</span> <span>SYM</span><span>.</span><span>LEFT_BRACE</span><span>:</span> <span>block_ongoing</span> <span>=</span> <span>True</span> <span>attribute_ongoing</span> <span>=</span> <span>True</span> <span>continue</span> <span>elif</span> <span>lex</span> <span>==</span> <span>SYM</span><span>.</span><span>RIGHT_BRACE</span><span>:</span> <span>block_ongoing</span> <span>=</span> <span>False</span> <span>statement_ongoing</span> <span>=</span> <span>False</span> <span>attribute_ongoing</span> <span>=</span> <span>False</span> <span>value_ongoing</span> <span>=</span> <span>False</span> <span>continue</span> <span>if</span> <span>lex</span> <span>==</span> <span>SYM</span><span>.</span><span>COLON</span><span>:</span> <span>value_ongoing</span> <span>=</span> <span>True</span> <span>attribute_ongoing</span> <span>=</span> <span>False</span> <span>continue</span> <span>elif</span> <span>lex</span> <span>==</span> <span>SYM</span><span>.</span><span>SEMI_COLON</span><span>:</span> <span>value_ongoing</span> <span>=</span> <span>False</span> <span>statement_ongoing</span> <span>=</span> <span>False</span> <span>attribute_ongoing</span> <span>=</span> <span>True</span> <span>continue</span>for i, lex in enumerate(lexemes): if i+1 < len_lexemes: next_lexeme = lexemes[i+1] prev_lexeme = lexemes[i-1] if lex == SYM.LEFT_BRACE: block_ongoing = True attribute_ongoing = True continue elif lex == SYM.RIGHT_BRACE: block_ongoing = False statement_ongoing = False attribute_ongoing = False value_ongoing = False continue if lex == SYM.COLON: value_ongoing = True attribute_ongoing = False continue elif lex == SYM.SEMI_COLON: value_ongoing = False statement_ongoing = False attribute_ongoing = True continue
Enter fullscreen mode Exit fullscreen mode
Continue is needed as once we have activated a flag, we have no work to do, we move on.
Dealing with variables
To assign variables, we just wait for the = sign then continue.
Then when going over a variable name or a value we just prevent the loop from continuing by using continue
<span>if</span> <span>lex</span> <span>==</span> <span>SYM</span><span>.</span><span>EQUAL</span><span>:</span><span>memory</span><span>[</span><span>prev_lexeme</span><span>]</span> <span>=</span> <span>next_lexeme</span><span>continue</span><span>elif</span> <span>next_lexeme</span> <span>==</span> <span>SYM</span><span>.</span><span>EQUAL</span> <span>or</span> <span>prev_lexeme</span> <span>==</span> <span>SYM</span><span>.</span><span>EQUAL</span><span>:</span><span>continue</span><span>if</span> <span>lex</span> <span>==</span> <span>SYM</span><span>.</span><span>EQUAL</span><span>:</span> <span>memory</span><span>[</span><span>prev_lexeme</span><span>]</span> <span>=</span> <span>next_lexeme</span> <span>continue</span> <span>elif</span> <span>next_lexeme</span> <span>==</span> <span>SYM</span><span>.</span><span>EQUAL</span> <span>or</span> <span>prev_lexeme</span> <span>==</span> <span>SYM</span><span>.</span><span>EQUAL</span><span>:</span> <span>continue</span>if lex == SYM.EQUAL: memory[prev_lexeme] = next_lexeme continue elif next_lexeme == SYM.EQUAL or prev_lexeme == SYM.EQUAL: continue
Enter fullscreen mode Exit fullscreen mode
Where The Tree Builds
Now we’ll make use of the flags
<span>if</span> <span>not</span> <span>block_ongoing</span><span>:</span><span>id_string</span> <span>+=</span> <span>lex</span> <span>+</span> <span>'</span><span> </span><span>'</span><span>elif</span> <span>block_ongoing</span><span>:</span><span>if</span> <span>id_string</span><span>:</span><span>tree</span><span>[</span><span>id_string</span><span>.</span><span>strip</span><span>()]</span> <span>=</span> <span>{}</span><span>last_id_string</span> <span>=</span> <span>id_string</span><span>.</span><span>strip</span><span>()</span><span>id_string</span> <span>=</span> <span>''</span><span>if</span> <span>not</span> <span>block_ongoing</span><span>:</span> <span>id_string</span> <span>+=</span> <span>lex</span> <span>+</span> <span>'</span><span> </span><span>'</span> <span>elif</span> <span>block_ongoing</span><span>:</span> <span>if</span> <span>id_string</span><span>:</span> <span>tree</span><span>[</span><span>id_string</span><span>.</span><span>strip</span><span>()]</span> <span>=</span> <span>{}</span> <span>last_id_string</span> <span>=</span> <span>id_string</span><span>.</span><span>strip</span><span>()</span> <span>id_string</span> <span>=</span> <span>''</span>if not block_ongoing: id_string += lex + ' ' elif block_ongoing: if id_string: tree[id_string.strip()] = {} last_id_string = id_string.strip() id_string = ''
Enter fullscreen mode Exit fullscreen mode
Here we are dealing with the block id, example #add in #add {}. We did
<span>tree</span><span>[</span><span>id_string</span><span>.</span><span>strip</span><span>()]</span> <span>=</span> <span>{}</span><span>tree</span><span>[</span><span>id_string</span><span>.</span><span>strip</span><span>()]</span> <span>=</span> <span>{}</span>tree[id_string.strip()] = {}
Enter fullscreen mode Exit fullscreen mode
here is an example tree at this point
{'.add': {}}{ '.add': {} }{ '.add': {} }
Enter fullscreen mode Exit fullscreen mode
Using the same principle, we’ll do so for the attribute
<span>if</span> <span>attribute_ongoing</span><span>:</span><span>current_attribute</span> <span>+=</span> <span>lex</span><span>elif</span> <span>not</span> <span>attribute_ongoing</span><span>:</span><span>if</span> <span>current_attribute</span><span>:</span><span>tree</span><span>[</span><span>last_id_string</span><span>][</span><span>current_attribute</span><span>]</span> <span>=</span> <span>''</span><span>last_attribute</span> <span>=</span> <span>current_attribute</span><span>current_attribute</span> <span>=</span> <span>''</span><span>if</span> <span>attribute_ongoing</span><span>:</span> <span>current_attribute</span> <span>+=</span> <span>lex</span> <span>elif</span> <span>not</span> <span>attribute_ongoing</span><span>:</span> <span>if</span> <span>current_attribute</span><span>:</span> <span>tree</span><span>[</span><span>last_id_string</span><span>][</span><span>current_attribute</span><span>]</span> <span>=</span> <span>''</span> <span>last_attribute</span> <span>=</span> <span>current_attribute</span> <span>current_attribute</span> <span>=</span> <span>''</span>if attribute_ongoing: current_attribute += lex elif not attribute_ongoing: if current_attribute: tree[last_id_string][current_attribute] = '' last_attribute = current_attribute current_attribute = ''
Enter fullscreen mode Exit fullscreen mode
Here is an example tree at this point
{'.add': {'color':''}}{ '.add': { 'color':'' } }{ '.add': { 'color':'' } }
Enter fullscreen mode Exit fullscreen mode
and value
<span>if</span> <span>value_ongoing</span><span>:</span><span>current_value</span> <span>+=</span> <span>lex</span> <span>+</span> <span>'</span><span> </span><span>'</span><span>elif</span> <span>not</span> <span>value_ongoing</span><span>:</span><span>if</span> <span>current_value</span><span>:</span><span>tree</span><span>[</span><span>last_id_string</span><span>][</span><span>last_attribute</span><span>]</span> <span>=</span> <span>current_value</span><span>.</span><span>strip</span><span>()</span><span>last_value</span> <span>=</span> <span>current_value</span><span>.</span><span>strip</span><span>()</span><span>current_value</span> <span>=</span> <span>''</span><span>if</span> <span>value_ongoing</span><span>:</span> <span>current_value</span> <span>+=</span> <span>lex</span> <span>+</span> <span>'</span><span> </span><span>'</span> <span>elif</span> <span>not</span> <span>value_ongoing</span><span>:</span> <span>if</span> <span>current_value</span><span>:</span> <span>tree</span><span>[</span><span>last_id_string</span><span>][</span><span>last_attribute</span><span>]</span> <span>=</span> <span>current_value</span><span>.</span><span>strip</span><span>()</span> <span>last_value</span> <span>=</span> <span>current_value</span><span>.</span><span>strip</span><span>()</span> <span>current_value</span> <span>=</span> <span>''</span>if value_ongoing: current_value += lex + ' ' elif not value_ongoing: if current_value: tree[last_id_string][last_attribute] = current_value.strip() last_value = current_value.strip() current_value = ''
Enter fullscreen mode Exit fullscreen mode
Here is an example tree at this point
{'.add': {'color':'white'}}{ '.add': { 'color':'white' } }{ '.add': { 'color':'white' } }
Enter fullscreen mode Exit fullscreen mode
That snippet ends our tree-building block
Replacing Values
Now let us see how to replace variable names in functions like rgb() and rgba()
<span>def</span> <span>parseFunc</span><span>(</span><span>f</span><span>):</span><span>v</span> <span>=</span> <span>f</span><span>.</span><span>split</span><span>(</span><span>SYM</span><span>.</span><span>LEFT_ROUND</span><span>)</span><span>name</span> <span>=</span> <span>v</span><span>[</span><span>0</span><span>]</span><span>vals</span> <span>=</span> <span>v</span><span>[</span><span>1</span><span>][:</span><span>-</span><span>1</span><span>].</span><span>replace</span><span>(</span><span>SYM</span><span>.</span><span>SPACE</span><span>,</span> <span>''</span><span>)</span><span>values</span> <span>=</span> <span>vals</span><span>.</span><span>split</span><span>(</span><span>SYM</span><span>.</span><span>COMMA</span><span>)</span><span>for</span> <span>i</span><span>,</span> <span>v</span> <span>in</span> <span>enumerate</span><span>(</span><span>values</span><span>):</span><span>if</span> <span>not</span> <span>v</span><span>.</span><span>isnumeric</span><span>():</span><span>values</span><span>[</span><span>i</span><span>]</span> <span>=</span> <span>memory</span><span>[</span><span>v</span><span>]</span><span>return</span> <span>'</span><span>{}({})</span><span>'</span><span>.</span><span>format</span><span>(</span><span>name</span><span>.</span><span>strip</span><span>(),</span> <span>'</span><span>,</span><span>'</span><span>.</span><span>join</span><span>(</span><span>values</span><span>))</span><span>def</span> <span>parseFunc</span><span>(</span><span>f</span><span>):</span> <span>v</span> <span>=</span> <span>f</span><span>.</span><span>split</span><span>(</span><span>SYM</span><span>.</span><span>LEFT_ROUND</span><span>)</span> <span>name</span> <span>=</span> <span>v</span><span>[</span><span>0</span><span>]</span> <span>vals</span> <span>=</span> <span>v</span><span>[</span><span>1</span><span>][:</span><span>-</span><span>1</span><span>].</span><span>replace</span><span>(</span><span>SYM</span><span>.</span><span>SPACE</span><span>,</span> <span>''</span><span>)</span> <span>values</span> <span>=</span> <span>vals</span><span>.</span><span>split</span><span>(</span><span>SYM</span><span>.</span><span>COMMA</span><span>)</span> <span>for</span> <span>i</span><span>,</span> <span>v</span> <span>in</span> <span>enumerate</span><span>(</span><span>values</span><span>):</span> <span>if</span> <span>not</span> <span>v</span><span>.</span><span>isnumeric</span><span>():</span> <span>values</span><span>[</span><span>i</span><span>]</span> <span>=</span> <span>memory</span><span>[</span><span>v</span><span>]</span> <span>return</span> <span>'</span><span>{}({})</span><span>'</span><span>.</span><span>format</span><span>(</span><span>name</span><span>.</span><span>strip</span><span>(),</span> <span>'</span><span>,</span><span>'</span><span>.</span><span>join</span><span>(</span><span>values</span><span>))</span>def parseFunc(f): v = f.split(SYM.LEFT_ROUND) name = v[0] vals = v[1][:-1].replace(SYM.SPACE, '') values = vals.split(SYM.COMMA) for i, v in enumerate(values): if not v.isnumeric(): values[i] = memory[v] return '{}({})'.format(name.strip(), ','.join(values))
Enter fullscreen mode Exit fullscreen mode
Here we replace rgb(x, 4, 5) to rgb(1, 4, 5) by replacing the x by 1 in the memory dictionary. To build a CSS pre-processor from scratch, we have to take all cases, which we’ll do after.
Transforming The Tree Into It’s Final Form
Now we need to pass over the tree once again and expand functions, replace values, including the use of our defined function above.
Technically we can’t change a dictionary while iterating over it. We have to copy it to break references. We have to deepcopy it.
<span>ntree</span> <span>=</span> <span>copy</span><span>.</span><span>deepcopy</span><span>(</span><span>tree</span><span>)</span><span>ntree</span> <span>=</span> <span>copy</span><span>.</span><span>deepcopy</span><span>(</span><span>tree</span><span>)</span>ntree = copy.deepcopy(tree)
Enter fullscreen mode Exit fullscreen mode
then we iterate over:
<span>for</span> <span>block_name</span> <span>in</span> <span>ntree</span><span>:</span><span>properties</span> <span>=</span> <span>ntree</span><span>[</span><span>block_name</span><span>]</span><span>if</span> <span>block_name</span><span>[</span><span>0</span><span>]</span> <span>==</span> <span>SYM</span><span>.</span><span>AT</span><span>:</span><span>continue</span><span>for</span> <span>block_name</span> <span>in</span> <span>ntree</span><span>:</span> <span>properties</span> <span>=</span> <span>ntree</span><span>[</span><span>block_name</span><span>]</span> <span>if</span> <span>block_name</span><span>[</span><span>0</span><span>]</span> <span>==</span> <span>SYM</span><span>.</span><span>AT</span><span>:</span> <span>continue</span>for block_name in ntree: properties = ntree[block_name] if block_name[0] == SYM.AT: continue
Enter fullscreen mode Exit fullscreen mode
Now, if we get the first symbol of the id as a @, we just skip it as we don’t need to expand the definition.
<span>for</span> <span>element</span> <span>in</span> <span>properties</span><span>:</span><span>value</span> <span>=</span> <span>properties</span><span>[</span><span>element</span><span>]</span><span>if</span> <span>SYM</span><span>.</span><span>LEFT_ROUND</span> <span>in</span> <span>value</span><span>:</span><span>tree</span><span>[</span><span>block_name</span><span>][</span><span>element</span><span>]</span> <span>=</span> <span>parseFunc</span><span>(</span><span>value</span><span>)</span><span>for</span> <span>element</span> <span>in</span> <span>properties</span><span>:</span> <span>value</span> <span>=</span> <span>properties</span><span>[</span><span>element</span><span>]</span> <span>if</span> <span>SYM</span><span>.</span><span>LEFT_ROUND</span> <span>in</span> <span>value</span><span>:</span> <span>tree</span><span>[</span><span>block_name</span><span>][</span><span>element</span><span>]</span> <span>=</span> <span>parseFunc</span><span>(</span><span>value</span><span>)</span>for element in properties: value = properties[element] if SYM.LEFT_ROUND in value: tree[block_name][element] = parseFunc(value)
Enter fullscreen mode Exit fullscreen mode
Next we say if there is a ( in the value, it denotes a function like rgb(). In that case we use our function parseFunc.
<span>if</span> <span>SYM</span><span>.</span><span>DOT</span> <span>in</span> <span>value</span><span>:</span><span>tree</span><span>[</span><span>block_name</span><span>][</span><span>element</span><span>]</span> <span>=</span> <span>memory</span><span>[</span><span>value</span><span>.</span><span>strip</span><span>(</span><span>SYM</span><span>.</span><span>DOT</span><span>)]</span><span>if</span> <span>SYM</span><span>.</span><span>DOT</span> <span>in</span> <span>value</span><span>:</span> <span>tree</span><span>[</span><span>block_name</span><span>][</span><span>element</span><span>]</span> <span>=</span> <span>memory</span><span>[</span><span>value</span><span>.</span><span>strip</span><span>(</span><span>SYM</span><span>.</span><span>DOT</span><span>)]</span>if SYM.DOT in value: tree[block_name][element] = memory[value.strip(SYM.DOT)]
Enter fullscreen mode Exit fullscreen mode
Next we see a . in the value, we go see in the memory dictionary and replace it
<span>if</span> <span>SYM</span><span>.</span><span>AT</span> <span>in</span> <span>element</span><span>:</span><span>del</span> <span>tree</span><span>[</span><span>block_name</span><span>][</span><span>element</span><span>]</span><span>tree</span><span>[</span><span>block_name</span><span>].</span><span>update</span><span>(</span><span>tree</span><span>[</span><span>element</span><span>])</span><span>if</span> <span>SYM</span><span>.</span><span>AT</span> <span>in</span> <span>element</span><span>:</span> <span>del</span> <span>tree</span><span>[</span><span>block_name</span><span>][</span><span>element</span><span>]</span> <span>tree</span><span>[</span><span>block_name</span><span>].</span><span>update</span><span>(</span><span>tree</span><span>[</span><span>element</span><span>])</span>if SYM.AT in element: del tree[block_name][element] tree[block_name].update(tree[element])
Enter fullscreen mode Exit fullscreen mode
Next we see the @ symbol as key in a block, we just add the dictionaries in it to the dictionaries in that block (Done by update() in Python) .
That also shows the advantage of using a data structure over plain strings.
Compiling To Normal Css
Now we can compile it to normal CSS, we’ll just print it for now (you can file= in print btw)
<span>for</span> <span>key</span> <span>in</span> <span>tree</span><span>:</span><span>if</span> <span>key</span><span>[</span><span>0</span><span>]</span> <span>==</span> <span>SYM</span><span>.</span><span>AT</span><span>:</span><span>continue</span><span>print</span><span>(</span><span>key</span><span>,</span> <span>'</span><span>{</span><span>'</span><span>,</span> <span>sep</span><span>=</span><span>''</span><span>)</span><span>for</span> <span>elem</span> <span>in</span> <span>tree</span><span>[</span><span>key</span><span>]:</span><span>print</span><span>(</span><span>'</span><span>{}{}: {};</span><span>'</span><span>.</span><span>format</span><span>(</span><span>SYM</span><span>.</span><span>TAB</span><span>,</span> <span>elem</span><span>,</span> <span>tree</span><span>[</span><span>key</span><span>][</span><span>elem</span><span>]))</span><span>print</span><span>(</span><span>'</span><span>}</span><span>\n</span><span>'</span><span>)</span><span>for</span> <span>key</span> <span>in</span> <span>tree</span><span>:</span> <span>if</span> <span>key</span><span>[</span><span>0</span><span>]</span> <span>==</span> <span>SYM</span><span>.</span><span>AT</span><span>:</span> <span>continue</span> <span>print</span><span>(</span><span>key</span><span>,</span> <span>'</span><span>{</span><span>'</span><span>,</span> <span>sep</span><span>=</span><span>''</span><span>)</span> <span>for</span> <span>elem</span> <span>in</span> <span>tree</span><span>[</span><span>key</span><span>]:</span> <span>print</span><span>(</span><span>'</span><span>{}{}: {};</span><span>'</span><span>.</span><span>format</span><span>(</span><span>SYM</span><span>.</span><span>TAB</span><span>,</span> <span>elem</span><span>,</span> <span>tree</span><span>[</span><span>key</span><span>][</span><span>elem</span><span>]))</span> <span>print</span><span>(</span><span>'</span><span>}</span><span>\n</span><span>'</span><span>)</span>for key in tree: if key[0] == SYM.AT: continue print(key, '{', sep='') for elem in tree[key]: print('{}{}: {};'.format(SYM.TAB, elem, tree[key][elem])) print('}\n')
Enter fullscreen mode Exit fullscreen mode
which produces
.add{color: white;background-color: rgb(3,4,5);}#ad{color: rgb(1,4,5);}.a div a{color: berry;border: 10px solid black;border-radius: 50%;}.add{ color: white; background-color: rgb(3,4,5); } #ad{ color: rgb(1,4,5); } .a div a{ color: berry; border: 10px solid black; border-radius: 50%; }.add{ color: white; background-color: rgb(3,4,5); } #ad{ color: rgb(1,4,5); } .a div a{ color: berry; border: 10px solid black; border-radius: 50%; }
Enter fullscreen mode Exit fullscreen mode
The Future of It
This was done without a safety net and lacking lot of cool features but it is done to demonstrate that with some normal programming logic, you can set up something cool. It was my usual linkedIn Python Project of The Week.
If you want to build a CSS pre-processor from scratch based on indentation (like stylus), use this tutorial.
What’s the next thing/s to add? Comment it below!
Github Link: https://github.com/Abdur-rahmaanJ/dotdot
Mirrored on PythonMembersClub
Images from unsplash
— Abdur-Rahmaan Janhangeer
原文链接:How to build a CSS pre-processor (like SASS) from scratch
暂无评论内容