This commit is contained in:
yuanchuan 2017-02-24 17:50:06 +08:00
commit b0e0d58adf
12 changed files with 1249 additions and 0 deletions

22
LICENSE Normal file
View File

@ -0,0 +1,22 @@
(The MIT License)
Copyright (c) 2017 Yuan Chuan <yuanchuan23@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

3
Makefile Normal file
View File

@ -0,0 +1,3 @@
build:
@rollup --format=umd --output=css-doodle.js -- src/index.js

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# css-doodle
work in progress

586
css-doodle.js Normal file
View File

@ -0,0 +1,586 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory() :
typeof define === 'function' && define.amd ? define(factory) :
(factory());
}(this, (function () { 'use strict';
function nth(x, y, count) {
return n => n == count;
}
function at(x, y) {
return (x1, y1) => (x == x1 && y == y1);
}
function row(x) {
return n => n == x;
}
function col(x, y) {
return n => n == y;
}
function even(x, y, count) {
return _ => !(count % 2);
}
function odd(x, y, count) {
return _ => !!(count % 2);
}
var cond = Object.freeze({
nth: nth,
at: at,
row: row,
col: col,
even: even,
odd: odd
});
function index(x, y, count) {
return _ => count;
}
function row$1(x) {
return _ => x;
}
function col$1(x, y) {
return _ => y;
}
function any() {
return function(...args) {
return random.apply(null, args);
}
}
function rand() {
return function(...args) {
return random(memo(unitify(range)).apply(null, args));
}
}
function random(...items) {
let args = items.reduce((ret, n) => ret.concat(n), []);
return args[Math.floor(Math.random() * args.length)];
}
function memo(fn) {
return function(...args) {
const memo = {};
let key = args.join('-');
if (memo[key]) return memo[key];
return (memo[key] = fn.apply(null, args));
}
}
function range(start, stop, step) {
let length = arguments.length;
if (length == 1) [start, stop] = [0, start];
if (length < 3) step = (stop > 0 && stop < 1) ? .1 : 1;
let range = [];
while ((step > 0 && start < stop)
|| (step < 0 && start > stop)) {
range.push(start);
start += step;
}
return range;
}
function unitify(fn) {
return function(...args) {
let unit = get_unit(args[0]);
if (unit) {
args = args.map(remove_unit);
return add_unit(fn, unit).apply(null, args);
}
return fn.apply(null, args);
}
}
function add_unit(fn, unit) {
return function(...args) {
args = args.map(remove_unit);
let result = fn.apply(null, args);
if (unit) {
result = result.map(n => n + unit);
}
return result;
}
}
function get_unit(str) {
if (!str) return '';
let unit = /(%|cm|em|ex|in|mm|pc|pt|px|vh|vw|vmin|deg)$/;
let matched = ''.trim.call(str).match(unit);
return matched ? matched[0] : '';
}
function remove_unit(str) {
let unit = get_unit(str);
return unit ? +(str.replace(unit, '')) : str;
}
var func = Object.freeze({
index: index,
row: row$1,
col: col$1,
any: any,
rand: rand
});
function values(obj) {
return Array.isArray(obj)
? obj : Object.keys(obj).map(k => obj[k]);
}
function with_args(fn, ...args) {
return args.reduce((f, arg) => f.apply(null, values(arg)), fn);
}
function join_line(arr) {
return (arr || []).join('\n');
}
function rule_set() {
const set = {};
return {
add(selector, rule) {
let rules = set[selector];
if (!rules) rules = set[selector] = [];
rules.push.apply(
rules,
Array.isArray(rule) ? rule : [rule]
);
},
output() {
return join_line(Object.keys(set).map(selector => `
${ selector } {
${ join_line(set[selector]) }
}
`));
}
}
}
function compose_selector(count, psudo = '') {
return `.cell:nth-of-type(${ count }) .shape${ psudo }`;
}
function compose_value(value, coords) {
return value.map(val => {
switch (val.type) {
case 'text': {
return val.value;
}
case 'function': {
let fn = func[val.name.substr(1)];
if (fn) {
return with_args(fn, coords, val.arguments);
}
}
default: return '';
}
}).join('');
}
function compose_rule(token, coords) {
let property = token.property;
let value = compose_value(token.value, coords);
return `${ property }: ${ value };`;
}
function compose_tokens(tokens, set, coords) {
tokens.forEach(token => {
switch (token.type) {
case 'rule':
set.add(
compose_selector(coords.count),
compose_rule(token, coords)
);
break;
case 'psudo':
set.add(
compose_selector(coords.count, token.selector),
token.styles.map(s => compose_rule(s, coords))
);
break;
case 'cond':
let fn = cond[token.name.substr(1)];
if (fn) {
let result = with_args(fn, coords, token.arguments);
if (result) {
compose_tokens(token.styles, set, coords);
}
}
break;
}
});
}
function generator(tokens, size) {
let set = rule_set();
let count = 0;
for (let x = 1; x <= size.x; ++x) {
for (let y = 1; y <= size.y; ++y) {
count++;
compose_tokens(tokens, set, { x, y, count});
}
}
return set.output();
}
const struct = {
func: () =>
({ type: 'function', name: '', arguments: [] }),
text: () =>
({ type: 'text', value: '' }),
psudo: () =>
({ type: 'psudo', selector: '', styles: [] }),
cond: () =>
({ type: 'cond', name: '', styles: [], arguments: [] }),
rule: () =>
({ type: 'rule', property: '', value: [] })
};
const bracket_pair = {
'(': ')',
'[': ']',
'{': '}'
};
const is = {
white_space(c) {
return /[\s\n\t]/.test(c);
},
open_bracket(c) {
return bracket_pair.hasOwnProperty(c);
},
close_bracket_of(c) {
let pair = bracket_pair[c];
return p => p == pair;
},
number(n) {
return !isNaN(n);
}
};
function iterator(input) {
let index = 0;
return {
curr: (n = 0) => input[index + n],
next: () => input[index++],
end: () => input.length - 1 <= index,
index: () => index
};
}
function skip_block(it) {
let [skipped, c] = [it.curr(), it.curr()];
let is_close_bracket = is.close_bracket_of(c);
it.next();
while (!it.end()) {
if (is_close_bracket(c = it.curr())) {
skipped += c;
break;
}
else if (is.open_bracket(c)) {
skipped += skip_block(it);
} else {
skipped += c;
}
it.next();
}
return skipped;
}
function read_property(it) {
let prop = '', c;
while (!it.end()) {
if ((c = it.curr()) == ':') break;
else if (!/[a-zA-Z\-]/.test(c)) {
throw new Error('Syntax error: Bad property name.');
}
else if (!is.white_space(c)) prop += c;
it.next();
}
it.next();
return prop;
}
function read_quote_block(it, quote) {
let block = '', c;
it.next();
while (!it.end()) {
if ((c = it.curr()) == quote) {
if (it.curr(-1) !== '\\') break;
else block += c;
}
else block += c;
it.next();
}
return block;
}
function read_arguments(it) {
let args = [], arg = '', c;
while (!it.end()) {
if (is.open_bracket(c = it.curr())) {
arg += skip_block(it);
}
else if (/['"]/.test(c)) {
arg += read_quote_block(it, c);
}
else if (/[,)]/.test(c)) {
if (arg.length) {
args.push(is.number(+arg) ? +arg : arg);
arg = '';
}
if (c == ')') break;
}
else if (!is.white_space(c)) {
arg += c;
}
it.next();
}
return args;
}
function read_func(it) {
let func = struct.func(), name = '', c;
while (!it.end()) {
if ((c = it.curr()) == ')') break;
if (c == '(') {
it.next();
func.name = name;
func.arguments = read_arguments(it);
break;
}
else name += c;
it.next();
}
return func;
}
function read_value(it) {
let text = struct.text(), c;
const value = [];
while (!it.end()) {
if ((c = it.curr()) == '\n') {
it.next();
continue;
}
else if (/[;}]/.test(c)) {
if (text.value.length) value.push(text);
break;
}
else if (c == '@') {
if (text.value.length) value.push(text);
text = struct.text();
value.push(read_func(it));
}
else if (!is.white_space(c) || !is.white_space(it.curr(-1))) {
text.value += c;
}
it.next();
}
return value;
}
function read_selector(it) {
let selector = '', c;
while (!it.end()) {
if ((c = it.curr())== '{') break;
else if (!is.white_space(c)) selector += c;
it.next();
}
return selector;
}
function read_cond_selector(it) {
let selector = { name: '', arguments: [] }, c;
while (!it.end()) {
if ((c = it.curr()) == '(') {
it.next();
selector.arguments = read_arguments(it);
}
else if (/[){]/.test(c)) break;
else if (!is.white_space(c)) selector.name += c;
it.next();
}
return selector;
}
function read_psudo(it) {
let psudo = struct.psudo(), c;
while (!it.end()) {
if ((c = it.curr())== '}') break;
if (is.white_space(c)) {
it.next();
continue;
}
else if (!psudo.selector) psudo.selector = read_selector(it);
else psudo.styles.push(read_rule(it));
it.next();
}
return psudo;
}
function read_rule(it) {
let rule = struct.rule(), c;
while (!it.end()) {
if ((c = it.curr()) == ';') break;
else if (!rule.property.length) {
rule.property = read_property(it);
}
else {
rule.value = read_value(it);
break;
}
it.next();
}
return rule;
}
function read_cond(it) {
let cond = struct.cond(), c;
while (!it.end()) {
if ((c = it.curr()) == '}') break;
else if (!cond.name.length) {
Object.assign(cond, read_cond_selector(it));
}
else if (c == ':') {
let psudo = read_psudo(it);
if (psudo.selector) cond.styles.push(psudo);
}
else if (c == '@') {
cond.styles.push(read_cond(it));
}
else if (!is.white_space(c)) {
let rule = read_rule(it);
if (rule.property) cond.styles.push(rule);
}
it.next();
}
return cond;
}
function tokenizer(input) {
const it = iterator(input);
const tokens = [];
while (!it.end()) {
let c = it.curr();
if (is.white_space(c)) {
it.next();
continue;
}
else if (c == ':') {
let psudo = read_psudo(it);
if (psudo.selector) tokens.push(psudo);
}
else if (c == '@') {
let cond = read_cond(it);
if (cond.name.length) tokens.push(cond);
}
else if (!is.white_space(c)) {
let rule = read_rule(it);
if (rule.property) tokens.push(rule);
}
it.next();
}
return tokens;
}
function compile(input, size) {
return generator(tokenizer(input), size);
}
function clamp(num, min, max) {
return (num <= min) ? min : ((num >= max) ? max : num);
}
function parse_size(size) {
const split = size
.replace(/\s+/g, '')
.replace(/[,xX]+/, 'x')
.split('x');
const ret = {
x: clamp(parseInt(split[0], 10), 1, 16),
y: clamp(parseInt(split[1] || split[0]), 1, 16)
};
return {
x: ret.x,
y: ret.y,
count: ret.x * ret.y
};
}
const basic_styles = (size) => `
:host {
display: inline-block;
width: 1em;
height: 1em;
}
.container {
position: relative;
width: 100%;
height: 100%;
}
.container:after {
content: '';
display: block;
clear: both;
visibility: hidden;
}
.cell {
position: relative;
width: ${ 100 / size.x + '%' };
height: ${ 100 / size.y + '%' };
float: left;
line-height: 0;
}
.shape {
line-height: 0;
position: absolute;
width: 100%;
height: 100%;
transform-origin: center center;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
}
`;
class Doodle extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
this.size = parse_size(this.getAttribute('grid') || '1x1');
const cell_styles = compile(this.innerHTML, this.size);
this.attachShadow({ mode: 'open' }).innerHTML = `
<style> ${ basic_styles(this.size)} </style>
<style> ${ cell_styles } </style>
<div class="container">
${
`<div class="cell">
<div class="shape"></div>
</div>
`.repeat(this.size.count)
}
</div>
`;
}
}
customElements.define('css-doodle', Doodle);
})));

30
samples/circles.html Normal file
View File

@ -0,0 +1,30 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>demo</title>
<style>
html, body { height: 100%; margin: 0; padding: 0;}
body { background: #666; overflow: hidden; }
body { display: flex; align-items: center; justify-content: center }
css-doodle { width: 100vw; height: 100vw; background: #fff; overflow: hidden; }
</style>
<script src="../css-doodle.js"></script>
</head>
<body>
<div>
<css-doodle grid="8">
--size: @rand(200%);
width: var(--size);
height: var(--size);
background: hsla(@rand(100), 80%, 50%, @rand(.8));
border-radius: @rand(50%, 100%);
transform: translate(
@any(-50%, 50%),
@any(-50%, 50%)
);
</css-doodle>
</div>
</body>
</html>

28
samples/race.html Normal file
View File

@ -0,0 +1,28 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>demo</title>
<style>
html, body { height: 100%; margin: 0; padding: 0;}
body { overflow: hidden; }
body { display: flex; align-items: center; justify-content: center }
css-doodle { width: 80vh; height: 50vh; overflow: hidden; }
</style>
<script src="../css-doodle.js"></script>
</head>
<body>
<css-doodle grid="1, 16">
--size: @rand(10%, 80%);
--color: hsla(@rand(100), 80%, 40%, @rand(.4, .9));
width: var(--size);
height: 50%;
left: @rand(32%);
border-radius: 5px 0 0 5px;
background-image: linear-gradient(
to right, var(--color) 20%, transparent 80%
);
</css-doodle>
</body>
</html>

9
src/compiler.js Normal file
View File

@ -0,0 +1,9 @@
import generator from './generator';
import tokenizer from './tokenizer';
function compile(input, size) {
return generator(tokenizer(input), size);
}
export { generator, tokenizer };
export default compile;

25
src/cond.js Normal file
View File

@ -0,0 +1,25 @@
export function nth(x, y, count) {
return n => n == count;
}
export function at(x, y) {
return (x1, y1) => (x == x1 && y == y1);
}
export function row(x) {
return n => n == x;
}
export function col(x, y) {
return n => n == y;
}
export function even(x, y, count) {
return _ => !(count % 2);
}
export function odd(x, y, count) {
return _ => !!(count % 2);
}

86
src/function.js Normal file
View File

@ -0,0 +1,86 @@
export function index(x, y, count) {
return _ => count;
}
export function row(x) {
return _ => x;
}
export function col(x, y) {
return _ => y;
}
export function any() {
return function(...args) {
return random.apply(null, args);
}
}
export function rand() {
return function(...args) {
return random(memo(unitify(range)).apply(null, args));
}
}
function random(...items) {
let args = items.reduce((ret, n) => ret.concat(n), []);
return args[Math.floor(Math.random() * args.length)];
}
function memo(fn) {
return function(...args) {
const memo = {};
let key = args.join('-');
if (memo[key]) return memo[key];
return (memo[key] = fn.apply(null, args));
}
}
function range(start, stop, step) {
let length = arguments.length;
if (length == 1) [start, stop] = [0, start];
if (length < 3) step = (stop > 0 && stop < 1) ? .1 : 1;
let range = [];
while ((step > 0 && start < stop)
|| (step < 0 && start > stop)) {
range.push(start);
start += step;
}
return range;
}
function unitify(fn) {
return function(...args) {
let unit = get_unit(args[0]);
if (unit) {
args = args.map(remove_unit);
return add_unit(fn, unit).apply(null, args);
}
return fn.apply(null, args);
}
}
function add_unit(fn, unit) {
return function(...args) {
args = args.map(remove_unit);
let result = fn.apply(null, args);
if (unit) {
result = result.map(n => n + unit);
}
return result;
}
}
function get_unit(str) {
if (!str) return '';
let unit = /(%|cm|em|ex|in|mm|pc|pt|px|vh|vw|vmin|deg)$/;
let matched = ''.trim.call(str).match(unit);
return matched ? matched[0] : '';
}
function remove_unit(str) {
let unit = get_unit(str);
return unit ? +(str.replace(unit, '')) : str;
}

109
src/generator.js Normal file
View File

@ -0,0 +1,109 @@
import * as cond from './cond';
import * as func from './function';
function values(obj) {
return Array.isArray(obj)
? obj : Object.keys(obj).map(k => obj[k]);
}
function with_args(fn, ...args) {
return args.reduce((f, arg) => f.apply(null, values(arg)), fn);
}
function join_line(arr) {
return (arr || []).join('\n');
}
function rule_set() {
const set = {};
return {
add(selector, rule) {
let rules = set[selector];
if (!rules) rules = set[selector] = [];
rules.push.apply(
rules,
Array.isArray(rule) ? rule : [rule]
);
},
output() {
return join_line(Object.keys(set).map(selector => `
${ selector } {
${ join_line(set[selector]) }
}
`));
}
}
}
function compose_selector(count, psudo = '') {
return `.cell:nth-of-type(${ count }) .shape${ psudo }`;
}
function compose_value(value, coords) {
return value.map(val => {
switch (val.type) {
case 'text': {
return val.value;
}
case 'function': {
let fn = func[val.name.substr(1)];
if (fn) {
return with_args(fn, coords, val.arguments);
}
}
default: return '';
}
}).join('');
}
function compose_rule(token, coords) {
let property = token.property;
let value = compose_value(token.value, coords);
return `${ property }: ${ value };`;
}
function compose_tokens(tokens, set, coords) {
tokens.forEach(token => {
switch (token.type) {
case 'rule':
set.add(
compose_selector(coords.count),
compose_rule(token, coords)
);
break;
case 'psudo':
set.add(
compose_selector(coords.count, token.selector),
token.styles.map(s => compose_rule(s, coords))
);
break;
case 'cond':
let fn = cond[token.name.substr(1)];
if (fn) {
let result = with_args(fn, coords, token.arguments);
if (result) {
compose_tokens(token.styles, set, coords);
}
}
break;
}
});
}
function generator(tokens, size) {
let set = rule_set();
let count = 0;
for (let x = 1; x <= size.x; ++x) {
for (let y = 1; y <= size.y; ++y) {
count++;
compose_tokens(tokens, set, { x, y, count});
}
}
return set.output();
}
export default generator;

84
src/index.js Normal file
View File

@ -0,0 +1,84 @@
import compile from './compiler';
function clamp(num, min, max) {
return (num <= min) ? min : ((num >= max) ? max : num);
};
function parse_size(size) {
const split = size
.replace(/\s+/g, '')
.replace(/[,xX]+/, 'x')
.split('x');
const ret = {
x: clamp(parseInt(split[0], 10), 1, 16),
y: clamp(parseInt(split[1] || split[0]), 1, 16)
};
return {
x: ret.x,
y: ret.y,
count: ret.x * ret.y
};
}
const basic_styles = (size) => `
:host {
display: inline-block;
width: 1em;
height: 1em;
}
.container {
position: relative;
width: 100%;
height: 100%;
}
.container:after {
content: '';
display: block;
clear: both;
visibility: hidden;
}
.cell {
position: relative;
width: ${ 100 / size.x + '%' };
height: ${ 100 / size.y + '%' };
float: left;
line-height: 0;
}
.shape {
line-height: 0;
position: absolute;
width: 100%;
height: 100%;
transform-origin: center center;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
}
`;
class Doodle extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
this.size = parse_size(this.getAttribute('grid') || '1x1');
const cell_styles = compile(this.innerHTML, this.size);
this.attachShadow({ mode: 'open' }).innerHTML = `
<style> ${ basic_styles(this.size)} </style>
<style> ${ cell_styles } </style>
<div class="container">
${
`<div class="cell">
<div class="shape"></div>
</div>
`.repeat(this.size.count)
}
</div>
`;
}
}
customElements.define('css-doodle', Doodle);

264
src/tokenizer.js Normal file
View File

@ -0,0 +1,264 @@
const struct = {
func: () =>
({ type: 'function', name: '', arguments: [] }),
text: () =>
({ type: 'text', value: '' }),
psudo: () =>
({ type: 'psudo', selector: '', styles: [] }),
cond: () =>
({ type: 'cond', name: '', styles: [], arguments: [] }),
rule: () =>
({ type: 'rule', property: '', value: [] })
};
const bracket_pair = {
'(': ')',
'[': ']',
'{': '}'
};
const is = {
white_space(c) {
return /[\s\n\t]/.test(c);
},
open_bracket(c) {
return bracket_pair.hasOwnProperty(c);
},
close_bracket_of(c) {
let pair = bracket_pair[c];
return p => p == pair;
},
number(n) {
return !isNaN(n);
}
};
function iterator(input) {
let index = 0;
return {
curr: (n = 0) => input[index + n],
next: () => input[index++],
end: () => input.length - 1 <= index,
index: () => index
};
}
function skip_block(it) {
let [skipped, c] = [it.curr(), it.curr()];
let is_close_bracket = is.close_bracket_of(c);
it.next();
while (!it.end()) {
if (is_close_bracket(c = it.curr())) {
skipped += c;
break;
}
else if (is.open_bracket(c)) {
skipped += skip_block(it);
} else {
skipped += c;
}
it.next();
}
return skipped;
}
function read_property(it) {
let prop = '', c;
while (!it.end()) {
if ((c = it.curr()) == ':') break;
else if (!/[a-zA-Z\-]/.test(c)) {
throw new Error('Syntax error: Bad property name.');
}
else if (!is.white_space(c)) prop += c;
it.next();
}
it.next();
return prop;
}
function read_quote_block(it, quote) {
let block = '', c;
it.next();
while (!it.end()) {
if ((c = it.curr()) == quote) {
if (it.curr(-1) !== '\\') break;
else block += c;
}
else block += c;
it.next();
}
return block;
}
function read_arguments(it) {
let args = [], arg = '', c;
while (!it.end()) {
if (is.open_bracket(c = it.curr())) {
arg += skip_block(it);
}
else if (/['"]/.test(c)) {
arg += read_quote_block(it, c);
}
else if (/[,)]/.test(c)) {
if (arg.length) {
args.push(is.number(+arg) ? +arg : arg);
arg = '';
}
if (c == ')') break;
}
else if (!is.white_space(c)) {
arg += c;
}
it.next();
}
return args;
}
function read_func(it) {
let func = struct.func(), name = '', c;
while (!it.end()) {
if ((c = it.curr()) == ')') break;
if (c == '(') {
it.next();
func.name = name;
func.arguments = read_arguments(it);
break;
}
else name += c;
it.next();
}
return func;
}
function read_value(it) {
let text = struct.text(), c;
const value = [];
while (!it.end()) {
if ((c = it.curr()) == '\n') {
it.next();
continue;
}
else if (/[;}]/.test(c)) {
if (text.value.length) value.push(text);
break;
}
else if (c == '@') {
if (text.value.length) value.push(text);
text = struct.text();
value.push(read_func(it));
}
else if (!is.white_space(c) || !is.white_space(it.curr(-1))) {
text.value += c;
}
it.next();
}
return value;
}
function read_selector(it) {
let selector = '', c;
while (!it.end()) {
if ((c = it.curr())== '{') break;
else if (!is.white_space(c)) selector += c;
it.next();
}
return selector;
}
function read_cond_selector(it) {
let selector = { name: '', arguments: [] }, c;
while (!it.end()) {
if ((c = it.curr()) == '(') {
it.next();
selector.arguments = read_arguments(it);
}
else if (/[){]/.test(c)) break;
else if (!is.white_space(c)) selector.name += c;
it.next();
}
return selector;
}
function read_psudo(it) {
let psudo = struct.psudo(), c;
while (!it.end()) {
if ((c = it.curr())== '}') break;
if (is.white_space(c)) {
it.next();
continue;
}
else if (!psudo.selector) psudo.selector = read_selector(it);
else psudo.styles.push(read_rule(it));
it.next();
}
return psudo;
}
function read_rule(it) {
let rule = struct.rule(), c;
while (!it.end()) {
if ((c = it.curr()) == ';') break;
else if (!rule.property.length) {
rule.property = read_property(it);
}
else {
rule.value = read_value(it);
break;
}
it.next();
}
return rule;
}
function read_cond(it) {
let cond = struct.cond(), c;
while (!it.end()) {
if ((c = it.curr()) == '}') break;
else if (!cond.name.length) {
Object.assign(cond, read_cond_selector(it));
}
else if (c == ':') {
let psudo = read_psudo(it);
if (psudo.selector) cond.styles.push(psudo);
}
else if (c == '@') {
cond.styles.push(read_cond(it));
}
else if (!is.white_space(c)) {
let rule = read_rule(it);
if (rule.property) cond.styles.push(rule);
}
it.next();
}
return cond;
}
function tokenizer(input) {
const it = iterator(input);
const tokens = [];
while (!it.end()) {
let c = it.curr();
if (is.white_space(c)) {
it.next();
continue;
}
else if (c == ':') {
let psudo = read_psudo(it);
if (psudo.selector) tokens.push(psudo);
}
else if (c == '@') {
let cond = read_cond(it);
if (cond.name.length) tokens.push(cond);
}
else if (!is.white_space(c)) {
let rule = read_rule(it);
if (rule.property) tokens.push(rule);
}
it.next();
}
return tokens;
}
export default tokenizer;