From e47d8dcc95f8fbe830a2b7852ba0af0d60f1a29b Mon Sep 17 00:00:00 2001 From: John-Mark Bell Date: Sat, 4 Feb 2017 19:43:34 +0000 Subject: Media Queries: parse features --- src/parse/mq.c | 450 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- src/parse/mq.h | 6 +- 2 files changed, 452 insertions(+), 4 deletions(-) (limited to 'src/parse') diff --git a/src/parse/mq.c b/src/parse/mq.c index 5c9c7fa..96f70df 100644 --- a/src/parse/mq.c +++ b/src/parse/mq.c @@ -7,7 +7,443 @@ /* https://drafts.csswg.org/mediaqueries/ */ +#include + +#include "bytecode/bytecode.h" #include "parse/mq.h" +#include "parse/properties/utils.h" +#include "utils/utils.h" + +static css_error mq_parse_ratio(css_language *c, + const parserutils_vector *vector, int *ctx, + const css_token *numerator, css_fixed *ratio) +{ + const css_token *token; + css_fixed num, den; + size_t num_len, den_len; + + /* NUMBER ws* '/' ws* NUMBER */ + + /* numerator, ws* already consumed */ + + token = parserutils_vector_iterate(vector, ctx); + if (token == NULL || tokenIsChar(token, '/') == false) { + return CSS_INVALID; + } + + consumeWhitespace(vector, ctx); + + token = parserutils_vector_iterate(vector, ctx); + if (token == NULL || token->type != CSS_TOKEN_NUMBER) { + return CSS_INVALID; + } + + num = css__number_from_lwc_string(numerator->idata, true, &num_len); + den = css__number_from_lwc_string(token->idata, true, &den_len); + + *ratio = css_divide_fixed(num, den); + + return CSS_OK; +} + +static css_error mq_create_feature( + lwc_string *name, + css_mq_feature **feature) +{ + css_mq_feature *f; + + f = malloc(sizeof(*f)); + if (f == NULL) { + return CSS_NOMEM; + } + + memset(f, 0, sizeof(*f)); + + f->name = lwc_string_ref(name); + + *feature = f; + + return CSS_OK; +} + +static css_error mq_populate_value(css_mq_value *value, + const css_token *token) +{ + if (token->type == CSS_TOKEN_NUMBER) { + size_t num_len; + value->type = CSS_MQ_VALUE_TYPE_NUM; + value->data.num_or_ratio = css__number_from_lwc_string( + token->idata, false, &num_len); + } else if (token->type == CSS_TOKEN_DIMENSION) { + size_t len = lwc_string_length(token->idata); + const char *data = lwc_string_data(token->idata); + uint32_t unit = UNIT_PX; + size_t consumed; + + value->type == CSS_MQ_VALUE_TYPE_DIM; + value->data.dim.len = css__number_from_lwc_string( + token->idata, false, &consumed); + error = css__parse_unit_keyword(data + consumed, len - consumed, + &unit); + if (error != CSS_OK) { + return error; + } + value->data.dim.unit = temp_unit; + } else if (token->type == CSS_TOKEN_IDENT) { + value->type = CSS_MQ_VALUE_TYPE_IDENT; + value->data.ident = lwc_string_ref(token->idata); + } + + return CSS_OK; +} + +static css_error mq_parse_op(const css_token *token, + css_mq_feature_op *op) +{ + size_t len; + const char *data; + + if (token == NULL || token->type != CSS_TOKEN_CHAR) + return CSS_INVALID; + + len = lwc_string_length(token->idata); + data = lwc_string_data(token->idata); + + if (len == 2) { + if (strncasecmp(data, "<=", 2) == 0) + *op = CSS_MQ_FEATURE_OP_LTE; + else if (strncasecmp(data, ">=", 2) == 0) + *op = CSS_MQ_FEATURE_OP_GTE; + else + return CSS_INVALID; + } else if (len == 1) { + if (*data == '<') + *op = CSS_MQ_FEATURE_OP_LT; + else if (*data == '=') + *op = CSS_MQ_FEATURE_OP_EQ; + else if (*data == '>') + *op = CSS_MQ_FEATURE_OP_GT; + else + return CSS_INVALID; + } else { + return CSS_INVALID; + } + + return CSS_OK; +} + +static css_error mq_parse_range(css_language *c, + const parserutils_vector *vector, int *ctx, + const css_token *name_or_value, + css_mq_feature **feature) +{ + const css_token *token, *value_or_name, *name = NULL, *value2 = NULL; + css_mq_feature *result; + css_mq_feature_op op, op2; + css_fixed ratio, ratio2; + bool name_first = false, value_is_ratio = false, value2_is_ratio = false, match; + css_error error; + + /* = [ '<' | '>' ]? '='? + * | [ '<' | '>' ]? '='? + * | '<' '='? '<' '='? + * | '>' '='? '>' '='? + */ + + if (name_or_value == NULL || (name_or_value->type != CSS_TOKEN_NUMBER && + name_or_value->type != CSS_TOKEN_DIMENSION && + name_or_value->type != CSS_TOKEN_IDENT)) { + return CSS_INVALID; + } + + consumeWhitespace(vector, ctx); + + /* Name-or-value */ + if (name_or_value->type == CSS_TOKEN_NUMBER && + tokenIsChar(parserutils_vector_peek(vector, *ctx), '/')) { + /* ratio */ + error = mq_parse_ratio(c, vector, ctx, token, &ratio); + if (error != CSS_OK) { + return error; + } + + consumeWhitespace(vector, ctx); + + value_is_ratio = true; + } else if (name_or_value->type == CSS_TOKEN_IDENT && + lwc_string_caseless_isequal(name_or_value->idata, + c->strings[INFINITE], &match) == lwc_error_ok && + match == false) { + /* The only ident permitted for mf-value is 'infinite', thus must have name */ + name = name_or_value; + name_first = true; + } + + /* Op */ + token = parserutils_vector_iterate(vector, ctx); + error = mq_parse_op(token, &op); + if (error != CSS_OK) { + return error; + } + + consumeWhitespace(vector, ctx); + + /* Value-or-name */ + value_or_name = parserutils_vector_iterate(vector, ctx); + if (value_or_name == NULL || (value_or_name->type != CSS_TOKEN_NUMBER && + value_or_name->type != CSS_TOKEN_DIMENSION && + value_or_name->type != CSS_TOKEN_IDENT)) { + return CSS_INVALID; + } + + if (name == NULL) { + if (value_or_name->type != CSS_TOKEN_IDENT) { + return CSS_INVALID; + } else { + name = value_or_name; + } + } + + consumeWhitespace(vector, ctx); + + if (value_or_name->type == CSS_TOKEN_NUMBER && + tokenIsChar(parserutils_vector_peek(vector, *ctx), '/')) { + /* ratio */ + error = mq_parse_ratio(c, vector, ctx, token, &ratio); + if (error != CSS_OK) { + return error; + } + + consumeWhitespace(vector, ctx); + + value_is_ratio = true; + } + + token = parserutils_vector_peek(vector, *ctx); + if (name_first == false && token != NULL && tokenIsChar(token, ')') == false) { + /* Op2 */ + token = parserutils_vector_iterate(vector, ctx); + error = mq_parse_op(token, &op2); + if (error != CSS_OK) { + return error; + } + + consumeWhitespace(vector, ctx); + + /* Validate operators: must both be LT(E) or GT(E) */ + if (op == CSS_MQ_FEATURE_OP_LT || op == CSS_MQ_FEATURE_OP_LTE) { + if (op2 != CSS_MQ_FEATURE_OP_LT && op2 != CSS_MQ_FEATURE_OP_LTE) { + return CSS_INVALID; + } + } else if (op == CSS_MQ_FEATURE_OP_GT || op == CSS_MQ_FEATURE_OP_GTE) { + if (op2 != CSS_MQ_FEATURE_OP_GT && op2 != CSS_MQ_FEATURE_OP_GTE) { + return CSS_INVALID; + } + } else { + return CSS_INVALID; + } + + /* Value2 */ + value2 = parserutils_vector_iterate(vector, ctx); + if (value2 == NULL || (value2->type != CSS_TOKEN_NUMBER && + value2->type != CSS_TOKEN_DIMENSION && + value2->type != CSS_TOKEN_IDENT)) { + return CSS_INVALID; + } + + consumeWhitespace(vector, ctx); + + if (value_or_name->type == CSS_TOKEN_NUMBER && + tokenIsChar(parserutils_vector_peek(vector, *ctx), '/')) { + /* ratio */ + error = mq_parse_ratio(c, vector, ctx, token, &ratio2); + if (error != CSS_OK) { + return error; + } + + consumeWhitespace(vector, ctx); + + value2_is_ratio = true; + } + } + + error = mq_create_feature(name->idata, &result); + if (error != CSS_OK) { + return error; + } + if (name_first) { + /* Invert operator */ + if (op == CSS_MQ_FEATURE_OP_LT) { + op = CSS_MQ_FEATURE_OP_GTE; + } else if (op == CSS_MQ_FEATURE_OP_LTE) { + op = CSS_MQ_FEATURE_OP_GT; + } else if (op == CSS_MQ_FEATURE_OP_GT) { + op = CSS_MQ_FEATURE_OP_LTE; + } else if (op == CSS_MQ_FEATURE_OP_GTE) { + op = CSS_MQ_FEATURE_OP_LT; + } + } + result->op = op; + if (value_is_ratio) { + result->value.type = CSS_MQ_VALUE_TYPE_RATIO; + result->value.data.num_or_ratio = ratio; + } else { + /* num/dim/ident */ + error = mq_populate_value(&result->value, token); + if (error != CSS_OK) { + free(result); + return error; + } + } + if (value2 != NULL) { + result->op2 = op2; + if (value2_is_ratio) { + result->value2.type = CSS_MQ_VALUE_TYPE_RATIO; + result->value2.data.num_or_ratio = ratio; + } else { + /* num/dim/ident */ + error = mq_populate_value(&result->value2, token); + if (error != CSS_OK) { + /* TODO: clean up result properly? */ + free(result); + return error; + } + } + } + + *feature = result; + + return CSS_OK; +} + +static css_error mq_parse_media_feature(css_language *c, + const parserutils_vector *vector, int *ctx, + css_mq_feature **feature) +{ + const css_token *name_or_value, *token; + css_mq_feature *result; + css_error error; + + /* = ( [ | | ] ) + * = : + * = + * = + * = | | | + */ + + /* ( already consumed */ + + name_or_value = parserutils_vector_iterate(vector, ctx); + if (name_or_value == NULL) + return CSS_INVALID; + + if (name_or_value->type == CSS_TOKEN_IDENT) { + consumeWhitespace(vector, ctx); + + token = parserutils_vector_peek(vector, *ctx); + if (tokenIsChar(token, ')')) { + /* mf-boolean */ + error = mq_create_feature(name_or_value->idata, &result); + if (error != CSS_OK) { + return error; + } + + result->op = CSS_MQ_FEATURE_OP_BOOL; + } else if (tokenIsChar(token, ':')) { + /* mf-plain */ + parserutils_vector_iterate(vector, ctx); + + consumeWhitespace(vector, ctx); + + token = parserutils_vector_iterate(vector, ctx); + if (token == NULL || (token->type != CSS_TOKEN_NUMBER && + token->type != CSS_TOKEN_DIMENSION && + token->type != CSS_TOKEN_IDENT)) { + return CSS_INVALID; + } + + consumeWhitespace(vector, ctx); + + error = mq_create_feature(name_or_value->idata, &result); + if (error != CSS_OK) { + return error; + } + result->op = CSS_MQ_FEATURE_OP_EQ; + + if (token->type == CSS_TOKEN_NUMBER && + tokenIsChar(parserutils_vector_peek(vector, *ctx), '/')) { + /* ratio */ + css_fixed ratio; + + error = mq_parse_ratio(c, vector, ctx, token, &ratio); + if (error != CSS_OK) { + free(result); + return error; + } + + result->value.type = CSS_MQ_VALUE_TYPE_RATIO; + result->value.data.num_or_ratio = ratio; + } else { + /* num/dim/ident */ + error = mq_populate_value(&result->value, token); + if (error != CSS_OK) { + free(result); + return error; + } + } + + consumeWhitespace(vector, ctx); + } else { + /* mf-range */ + error = mq_parse_range(c, vector, ctx, name_or_value, &result); + if (error != CSS_OK) { + return error; + } + + consumeWhitespace(vector, ctx); + } + } else { + /* mf-range */ + error = mq_parse_range(c, vector, ctx, name_or_value, &result); + if (error != CSS_OK) { + return error; + } + + consumeWhitespace(vector, ctx); + } + + token = parserutils_vector_iterate(vector, ctx); + if (tokenIsChar(token, ')') == false) { + /* TODO: clean up result */ + return CSS_INVALID; + } + + *feature = result; + + return CSS_OK; +} + +static css_error mq_parse_media_in_parens() +{ + /* = ( ) | | + * = [ ) ] | ( ) + */ + + //LPAREN -> condition-or-feature + // "not" or LPAREN -> condition + // IDENT | NUMBER | DIMENSION | RATIO -> feature + +} + +static css_error mq_parse_condition() +{ + /* = | [ * | * ] + * = | * + * = not + * = and + * = or + */ +} css_error css__mq_parse_media_list(css_language *c, const parserutils_vector *vector, int *ctx, @@ -16,7 +452,19 @@ css_error css__mq_parse_media_list(css_language *c, css_mq_query *ret = NULL; const css_token *token; - /* (IDENT ws (',' ws IDENT ws)* )? */ + /* = [ COMMA ]* + * = + * | [ not | only ]? [ and ]? + * = (except "not", "and", "or", "only") + * + */ + + /* if {[(, push }]) to stack + * if func, push ) to stack + * on error, scan forward until stack is empty (or EOF), popping matching tokens off stack + * if stack is empty, the next input token must be comma or EOF + * if comma, consume, and start again from the next input token + */ UNUSED(c); diff --git a/src/parse/mq.h b/src/parse/mq.h index 7d62e8c..c5268c7 100644 --- a/src/parse/mq.h +++ b/src/parse/mq.h @@ -22,7 +22,7 @@ typedef struct { css_fixed num_or_ratio; /* Where ratio is the result of a/b */ struct { css_fixed len; - css_unit unit; + uint32_t unit; } dim; lwc_string *ident; } data; @@ -31,8 +31,8 @@ typedef struct { /* * "name : value" is encoded as "name = value" * "name" is encoded by setting the operator to "bool" - * "name op value" is encoded verbatim (with op2 set to "unused") - * "value op name" inverts the operator to encode (i.e < becomes >=) (and sets op2 to "unused") + * "value op name" is encoded verbatim (with op2 set to "unused") + * "name op value" inverts the operator to encode (i.e < becomes >=) (and sets op2 to "unused") * "value op name op value" is encoded using op2 and value2 */ typedef enum { -- cgit v1.2.3