Если вы находитесь в веб -разработке, возможно, вы слышали о Sass , Меньше , Мопс, Стилус и т.п. Все это предварительные обработки. В этом уроке мы собираемся построить не что иное, как функциональный препроцессор CSS с нуля с переменными и функциями. Этот тип нового языка называется Source-To-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 }
который собирается для этого:
.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%; }
Посмотрим, какие функции мы включили
Переменные
x = 1 y = 5 x-y-z = 6 col-z = berry
Мы видим, что
- Нам не нужно указывать спецификатор, такой как $ или var в JS.
- Мы можем добавить – в имени переменной
Мы также можем вызвать переменные в значениях функции
rgb(x, 4, 5);
И в значениях атрибутов
color: ..col-z;
Где .. обозначает переменный вызов в атрибуте напрямую.
Функции
@f{ border: 10px solid black; border-radius: 50%; }
Функции обозначены знаком @. Они называются как в следующем случае, когда он расширяется в свойства, которые он содержит.
.a div a{ color: ..col-z; @f }
Этого достаточно, чтобы справиться. Давайте начнем!
import copy # we have just one import
и мы поместили источник в качестве переменной
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 } '''
Мы объединили наши константы в классе.
class SYM: LEFT_BRACE = '{' RIGHT_BRACE = '}' LEFT_ROUND = '(' RIGHT_ROUND = ')' NEW_LINE = '\n' TAB = ' ' COLON = ':' SEMI_COLON = ';' SPACE = ' ' COMMA = ',' EQUAL = '=' UNION = '-' DOT = '.' AT = '@'
Затем мы определяем наши ключевые слова
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)
Строительство Лексера
Код для Lexer от этот учебник Lexer . Пожалуйста, пройдите через это, если вам нужно. Здесь мы преобразовали код в класс с помощью метода get_lexeme. Метод дает нам список. Приведенный выше фрагмент Dotdot преобразуется в список, хранящийся в переменной Lexemes ниже.
Вот наш класс Lexer:
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
Затем мы объявляем наши основные переменные:
v = Lexer(source, KEYWORDS) lexemes = v.get_lexemes() lexemes.append('') # appending an unused last element to properly dump all values
Lexemes теперь равен
['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', '}', '']
С такими разделенными данными гораздо проще продолжить!
Затем мы определяем словарь, чтобы удерживать все наши переменные.
memory = {}
куда
x = 1
станет этим позже:
{'x':'1'}
Чтобы получить это, мы просто сделаем
memory['x']
Концепция дерева
У нас будет словарь под названием Tree
tree = {}
который будет удерживать конвертированный код как
{ '@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': '' } }
Наш следующий шаг будет именно таким: преобразование списка Lexemes в этот словарь
Чтобы отслеживать, где мы находимся, у нас будет ряд переменных, принимающих истинные/ложные значения (включено/выключение)
Настройка держателей
id_string = '' last_id_string = '' last_attribute = '' current_attribute = '' current_value = '' len_lexemes = len(lexemes)
Идентификационная строка будет держать как #Add, .x Div и т. Д.
Переменные последнего_ просто хранят переменные, которые не опустошены после выхода из подразделения
Настройка флагов
У нас будет 3 флаги.
Один, когда мы начинаем блок, который станет истинным при столкновении с {и false, когда встречается a}
Атрибут – цвет в цвете: черный;
Постоянный атрибут станет верным при прохождении {и; и станет ложным при прохождении:
value_ongoing станет истинной при переходе: и ложь при переходе;
block_ongoing = False attribute_ongoing = False value_ongoing = False
Мы начнем зацикливаться на списке и реализовать то, что мы описали в вышеупомянутых флагах
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
Продолжение необходимо, так как, как только мы активировали флаг, у нас нет работы, мы идем дальше.
Чтобы назначить переменные, мы просто ждем, затем продолжимся.
Затем, переходя через имя переменной или значение, мы просто предотвращаем продолжение цикла, используя продолжение
if lex == SYM.EQUAL: memory[prev_lexeme] = next_lexeme continue elif next_lexeme == SYM.EQUAL or prev_lexeme == SYM.EQUAL: continue
Теперь мы воспользуемся флагами
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 = ''
Здесь мы имеем дело с идентификатором блока, пример #Add в #ADD {}. Мы сделали
tree[id_string.strip()] = {}
Вот пример дерева на этом этапе
{ '.add': {} }
Используя тот же принцип, мы сделаем это для атрибута
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 = ''
Вот пример дерева на этом этапе
{ '.add': { 'color':'' } }
и значение
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 = ''
Вот пример дерева на этом этапе
{ '.add': { 'color':'white' } }
Этот фрагмент заканчивает наш блок построения деревьев
Теперь давайте посмотрим, как заменить имена переменных в таких функциях, как rgb () и rgba ()
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))
Здесь мы заменяем RGB (x, 4, 5) на RGB (1, 4, 5), заменив x на 1 в словаре памяти. Чтобы построить препроцессор CSS с нуля, мы должны взять все дела, что мы сделаем после.
Теперь нам нужно еще раз передать дерево и расширить функции, заменить значения, включая использование нашей определенной функции выше.
Технически мы не можем изменить словарь, итерапируя его. Мы должны скопировать его, чтобы нарушить ссылки. Мы должны глубоко их косо.
ntree = copy.deepcopy(tree)
Тогда мы перечислим:
for block_name in ntree: properties = ntree[block_name] if block_name[0] == SYM.AT: continue
Теперь, если мы получим первый символ идентификатора как @, мы просто пропустим его, поскольку нам не нужно расширять определение.
for element in properties: value = properties[element] if SYM.LEFT_ROUND in value: tree[block_name][element] = parseFunc(value)
Затем мы говорим, есть ли (в значении, это обозначает функцию, такую как rgb (). В этом случае мы используем нашу функцию parsefunc.
if SYM.DOT in value: tree[block_name][element] = memory[value.strip(SYM.DOT)]
Далее мы видим. В значении мы посмотрим в словаре памяти и заменим его
if SYM.AT in element: del tree[block_name][element] tree[block_name].update(tree[element])
Затем мы видим символ @ как ключ в блоке, мы просто добавляем в него словаря в словаря в этом блоке (выполненном Update () в Python).
Это также показывает преимущество использования структуры данных по простым строкам.
Теперь мы можем скомпилировать его в обычный CSS, мы просто распечатаем его на данный момент (вы можете распечатать кстати)
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')
который производит
.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%; }
Это было сделано без сетки безопасности и отсутствия много крутых функций, но это сделано, чтобы продемонстрировать, что с некоторой нормальной логикой программирования вы можете настроить что -то крутое. Это был мой обычный проект LinkedIn Python недели.
Если вы хотите построить предварительную процедуру CSS с нуля на основе отступления (например, Stylus), используйте этот учебник Анкет
Что следует добавить следующее? Прокомментируйте это ниже!
Ссылка GitHub: https://github.com/Abdur-rahmaanJ/dotdot
Зеркально на Pythonmembersclub
Изображения из Unsplash
-Абдур-Рахмаан Джанхангер
Оригинал: “https://dev.to/abdurrahmaanj/how-to-build-a-css-pre-processor-like-sass-from-scratch-33mc”