luacs.lua

main 137 lines · 3.9 KB Raw
Alex Alex Commit Client Upload Part 2 19/10/2026 00:06
1local parser = {}
2parser.__index = parser
3
4parser.new = function()
5 parser.imports = {}
6 parser.objects = {
7 type = "cssroot",
8 children = {}
9 }
10 parser.strings = {}
11 return setmetatable({}, parser)
12end
13
14-- what to get the correct result
15-- call parser:parse on all input names
16-- check for imported files using parser:get_imports
17-- recursively load all imported files until you get no more imports
18-- load imported files using parser:import
19function parser:parse(text, filename)
20 -- parse the CSS text
21 self.text = text
22 -- list of imported CSS files
23 self:parse_blocks()
24 return self.cssom
25end
26
27function parser:get_imports()
28 -- get list of imported CSS files. each of them should be parsed by a separate parser and imported using parser:import()
29 local imports = {}
30 for filename, _ in pairs(self.imports) do
31 imports[#imports + 1] = filename
32 end
33 return imports
34end
35
36--- Import CSS declarations from another file
37-- The CSS file should be another instacte of the CSS parser
38function parser:import(filename, CSS)
39 self.imports[filename] = CSS:get_objects()
40end
41
42function parser:get_objects()
43 return self.objects
44end
45
46function parser:parse_blocks()
47 -- for efficiency
48 local objects = self.objects
49 -- first parse "" and '' strings
50 self.text = self:parse_strings(self.text)
51 -- then delete comments
52 self.text = self:delete_comments(self.text)
53 local newobjects = self:parse_statements(self.text)
54 for _, obj in ipairs(newobjects) do
55 table.insert(objects, obj)
56 end
57end
58
59function parser:parse_strings(text)
60 -- delete strings from the CSS in order to safely remove the comments
61 -- strings will be saved to a table, where they will be retrieved lately
62 local function replace_string(s)
63 local count = #self.strings + 1
64 -- save the string and remove the quotations
65 self.strings[count] = s:sub(2, -2)
66 return "%%" .. count
67 end
68 local text = text:gsub("(%b'')", replace_string)
69 text = text:gsub('(%b"")', replace_string)
70 return text
71end
72
73function parser:delete_comments(text)
74 return text:gsub("/%*.-%*/", " ")
75end
76
77function parser:parse_statements(text)
78 local pos = 0
79 local objects = {}
80 local startpos, obj, at
81 while pos do
82 -- find start of the next statement and detect whether it is an at-rule
83 startpos, pos, at = string.find(text, "(@?)%w+", pos + 1)
84 if startpos then
85 -- these parse commands should parse next statement and return object representing it and
86 -- the end position of the statement in the parsed text
87 if at == "@" then
88 obj, pos = self:parse_at_rule(text, startpos - 1)
89 else
90 obj, pos = self:parse_rule(text, startpos - 1)
91 end
92 table.insert(objects, obj)
93 end
94 end
95 return objects
96end
97
98function parser:parse_at_rule(text, startpos)
99 -- detect if the statement contains block or is ended just by semicolon
100 local _, _, brace_or_semicolon = string.find(text, "([;{])", startpos)
101 if brace_or_semicolon == ";" then
102 local startpos, pos, statement = string.find(text, "(.-);", startpos)
103 return { type = "at_statement", statement = statement }, pos
104 else
105 local startpos, pos, statement, block = string.find(text, "(.-)(%b{})", startpos)
106 return { type = "at_statement", statement = statement, block = block }, pos
107 end
108end
109
110function parser:parse_rule(text, startpos)
111 local startpos, pos, selector, block = string.find(text, "(.-)(%b{})", startpos)
112 local obj = {
113 type = "rule",
114 selector = self:parse_selector(selector),
115 declarations = self:parse_declarations(block:sub(2, -2)) -- remove the brackets using string.sub
116 }
117 return obj, pos
118end
119
120function parser:parse_selector(selector)
121 return selector
122end
123
124function parser:parse_declarations(declarations)
125 local rules = string.explode(declarations, ";")
126 local properties = {}
127 for _, rule in ipairs(rules) do
128 local property, value = rule:match("%s*(.+)%s*:%s*(.+)%s*")
129 if property then
130 properties[property] = value
131 end
132 end
133 return properties
134end
135
136CssParse = parser
137
Top