From b0e0d58adf5afad79aa6f2adbe5adb3ff40698f2 Mon Sep 17 00:00:00 2001 From: yuanchuan Date: Fri, 24 Feb 2017 17:50:06 +0800 Subject: [PATCH] ship --- LICENSE | 22 ++ Makefile | 3 + README.md | 3 + css-doodle.js | 586 +++++++++++++++++++++++++++++++++++++++++++ samples/circles.html | 30 +++ samples/race.html | 28 +++ src/compiler.js | 9 + src/cond.js | 25 ++ src/function.js | 86 +++++++ src/generator.js | 109 ++++++++ src/index.js | 84 +++++++ src/tokenizer.js | 264 +++++++++++++++++++ 12 files changed, 1249 insertions(+) create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 css-doodle.js create mode 100644 samples/circles.html create mode 100644 samples/race.html create mode 100644 src/compiler.js create mode 100644 src/cond.js create mode 100644 src/function.js create mode 100644 src/generator.js create mode 100644 src/index.js create mode 100644 src/tokenizer.js diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c774fb1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +(The MIT License) + +Copyright (c) 2017 Yuan Chuan + +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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8403cf6 --- /dev/null +++ b/Makefile @@ -0,0 +1,3 @@ + +build: + @rollup --format=umd --output=css-doodle.js -- src/index.js diff --git a/README.md b/README.md new file mode 100644 index 0000000..d83359f --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# css-doodle + +work in progress diff --git a/css-doodle.js b/css-doodle.js new file mode 100644 index 0000000..bf96aa0 --- /dev/null +++ b/css-doodle.js @@ -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 = ` + + +
+ ${ + `
+
+
+ `.repeat(this.size.count) + } +
+ `; + } +} + +customElements.define('css-doodle', Doodle); + +}))); diff --git a/samples/circles.html b/samples/circles.html new file mode 100644 index 0000000..3985c97 --- /dev/null +++ b/samples/circles.html @@ -0,0 +1,30 @@ + + + + + demo + + + + +
+ + --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%) + ); + + +
+ + diff --git a/samples/race.html b/samples/race.html new file mode 100644 index 0000000..248215c --- /dev/null +++ b/samples/race.html @@ -0,0 +1,28 @@ + + + + + demo + + + + + + --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% + ); + + + diff --git a/src/compiler.js b/src/compiler.js new file mode 100644 index 0000000..83553d6 --- /dev/null +++ b/src/compiler.js @@ -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; diff --git a/src/cond.js b/src/cond.js new file mode 100644 index 0000000..04d5c3f --- /dev/null +++ b/src/cond.js @@ -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); +} + diff --git a/src/function.js b/src/function.js new file mode 100644 index 0000000..238cb22 --- /dev/null +++ b/src/function.js @@ -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; +} + diff --git a/src/generator.js b/src/generator.js new file mode 100644 index 0000000..f128bcb --- /dev/null +++ b/src/generator.js @@ -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; + diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..cd58b15 --- /dev/null +++ b/src/index.js @@ -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 = ` + + +
+ ${ + `
+
+
+ `.repeat(this.size.count) + } +
+ `; + } +} + +customElements.define('css-doodle', Doodle); + diff --git a/src/tokenizer.js b/src/tokenizer.js new file mode 100644 index 0000000..3ca1f84 --- /dev/null +++ b/src/tokenizer.js @@ -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; +