diff options
Diffstat (limited to 'src/parse/mq.c')
-rw-r--r-- | src/parse/mq.c | 1150 |
1 files changed, 1150 insertions, 0 deletions
diff --git a/src/parse/mq.c b/src/parse/mq.c new file mode 100644 index 0000000..cb9345d --- /dev/null +++ b/src/parse/mq.c @@ -0,0 +1,1150 @@ +/* + * This file is part of LibCSS. + * Licensed under the MIT License, + * http://www.opensource.org/licenses/mit-license.php + * Copyright 2016 John-Mark Bell <jmb@netsurf-browser.org> + */ + +/* https://drafts.csswg.org/mediaqueries/ */ + +#include <string.h> + +#include <libcss/fpmath.h> + +#include "stylesheet.h" +#include "bytecode/bytecode.h" +#include "parse/mq.h" +#include "parse/properties/utils.h" +#include "utils/utils.h" + +static void css__mq_value_destroy(css_mq_value *value) +{ + assert(value != NULL); + + if (value->type == CSS_MQ_VALUE_TYPE_IDENT) { + lwc_string_unref(value->data.ident); + } +} + +static void css__mq_feature_destroy(css_mq_feature *feature) +{ + if (feature != NULL) { + lwc_string_unref(feature->name); + css__mq_value_destroy(&feature->value); + css__mq_value_destroy(&feature->value2); + free(feature); + } +} + +static void css__mq_cond_or_feature_destroy( + css_mq_cond_or_feature *cond_or_feature); + +static void css__mq_cond_parts_destroy(css_mq_cond_parts *cond_parts) +{ + if (cond_parts != NULL) { + for (uint32_t i = 0; i < cond_parts->nparts; i++) { + css__mq_cond_or_feature_destroy(cond_parts->parts[i]); + } + free(cond_parts->parts); + free(cond_parts); + } +} + +static void css__mq_cond_destroy(css_mq_cond *cond) +{ + if (cond != NULL) { + css__mq_cond_parts_destroy(cond->parts); + free(cond); + } +} + +static void css__mq_cond_or_feature_destroy( + css_mq_cond_or_feature *cond_or_feature) +{ + if (cond_or_feature != NULL) { + switch (cond_or_feature->type) { + case CSS_MQ_FEATURE: + css__mq_feature_destroy(cond_or_feature->data.feat); + break; + case CSS_MQ_COND: + css__mq_cond_destroy(cond_or_feature->data.cond); + break; + } + free(cond_or_feature); + } +} + +void css__mq_query_destroy(css_mq_query *media) +{ + while (media != NULL) { + css_mq_query *next = media->next; + + css__mq_cond_destroy(media->cond); + free(media); + + media = next; + } +} + +static css_error mq_parse_condition(lwc_string **strings, + const parserutils_vector *vector, int *ctx, + bool permit_or, css_mq_cond **cond); + +static css_error mq_parse_ratio( + 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; + css_error error; + + 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 = 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(lwc_string **strings, + 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; + + /* <mf-range> = <mf-name> [ '<' | '>' ]? '='? <mf-value> + * | <mf-value> [ '<' | '>' ]? '='? <mf-name> + * | <mf-value> '<' '='? <mf-name> '<' '='? <mf-value> + * | <mf-value> '>' '='? <mf-name> '>' '='? <mf-value> + */ + + 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(vector, ctx, name_or_value, &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, + 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(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(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) { + css__mq_feature_destroy(result); + return error; + } + } + } + + *feature = result; + + return CSS_OK; +} + +static css_error mq_parse_media_feature(lwc_string **strings, + const parserutils_vector *vector, int *ctx, + css_mq_feature **feature) +{ + const css_token *name_or_value, *token; + css_mq_feature *result; + css_error error; + + /* <media-feature> = ( [ <mf-plain> | <mf-boolean> | <mf-range> ] ) + * <mf-plain> = <mf-name> : <mf-value> + * <mf-boolean> = <mf-name> + * <mf-name> = <ident> + * <mf-value> = <number> | <dimension> | <ident> | <ratio> + */ + + /* ( already consumed */ + + consumeWhitespace(vector, ctx); + + 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(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(strings, vector, ctx, + name_or_value, &result); + if (error != CSS_OK) { + return error; + } + + consumeWhitespace(vector, ctx); + } + } else { + /* mf-range */ + error = mq_parse_range(strings, 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) { + css__mq_feature_destroy(result); + return CSS_INVALID; + } + + *feature = result; + + return CSS_OK; +} + +/* + * Consume any value + * + * CSS Syntax Module Level 3: 8.2 + */ +static css_error mq_parse_consume_any_value(lwc_string **strings, + const parserutils_vector *vector, int *ctx, + bool until, const char until_char) +{ + const css_token *token; + css_error error; + + while (true) { + consumeWhitespace(vector, ctx); + + token = parserutils_vector_iterate(vector, ctx); + if (token == NULL) { + return CSS_INVALID; + } + + switch (token->type) { + case CSS_TOKEN_INVALID_STRING: + return CSS_INVALID; + + case CSS_TOKEN_CHAR: + if (until && tokenIsChar(token, until_char)) { + /* Found matching close bracket */ + return CSS_OK; + + } else if (tokenIsChar(token, ')') || + tokenIsChar(token, ']') || + tokenIsChar(token, '}')) { + /* Non-matching close bracket */ + return CSS_INVALID; + } + if (tokenIsChar(token, '(')) { + /* Need to consume until matching bracket. */ + error = mq_parse_consume_any_value(strings, + vector, ctx, true, ')'); + if (error != CSS_OK) { + return error; + } + } else if (tokenIsChar(token, '[')) { + /* Need to consume until matching bracket. */ + error = mq_parse_consume_any_value(strings, + vector, ctx, true, ']'); + if (error != CSS_OK) { + return error; + } + } else if (tokenIsChar(token, '{')) { + /* Need to consume until matching bracket. */ + error = mq_parse_consume_any_value(strings, + vector, ctx, true, '}'); + if (error != CSS_OK) { + return error; + } + } + break; + + default: + break; + } + } + + return CSS_OK; +} + +static css_error mq_parse_general_enclosed(lwc_string **strings, + const parserutils_vector *vector, int *ctx) +{ + const css_token *token; + css_error error; + + /* <general-enclosed> = [ <function-token> <any-value> ) ] + * | ( <ident> <any-value> ) + */ + + token = parserutils_vector_iterate(vector, ctx); + if (token == NULL) { + return CSS_INVALID; + } + + switch (token->type) { + case CSS_TOKEN_FUNCTION: + error = mq_parse_consume_any_value(strings, vector, ctx, + true, ')'); + if (error != CSS_OK) { + return error; + } + + token = parserutils_vector_peek(vector, *ctx); + if (!tokenIsChar(token, ')')) { + return CSS_INVALID; + } + break; + + case CSS_TOKEN_IDENT: + error = mq_parse_consume_any_value(strings, vector, ctx, + false, '\0'); + if (error != CSS_OK) { + return error; + } + break; + + default: + return CSS_INVALID; + } + + return CSS_OK; +} + +static css_error mq_parse_media_in_parens(lwc_string **strings, + const parserutils_vector *vector, int *ctx, + css_mq_cond_or_feature **cond_or_feature) +{ + const css_token *token; + bool match; + int old_ctx; + css_mq_cond_or_feature *result = NULL; + css_error error = CSS_OK; + + /* <media-in-parens> = ( <media-condition> ) | <media-feature> | <general-enclosed> + */ + + //LPAREN -> condition-or-feature + // "not" or LPAREN -> condition + // IDENT | NUMBER | DIMENSION | RATIO -> feature + + token = parserutils_vector_iterate(vector, ctx); + if (token == NULL || tokenIsChar(token, '(') == false) { + return CSS_INVALID; + } + + consumeWhitespace(vector, ctx); + + token = parserutils_vector_peek(vector, *ctx); + if (token == NULL) { + return CSS_INVALID; + } + + old_ctx = *ctx; + + if (tokenIsChar(token, '(') || (token->type == CSS_TOKEN_IDENT && + lwc_string_caseless_isequal(token->idata, + strings[NOT], &match) == lwc_error_ok && + match)) { + css_mq_cond *cond; + error = mq_parse_condition(strings, vector, ctx, true, &cond); + if (error == CSS_OK) { + token = parserutils_vector_iterate(vector, ctx); + if (tokenIsChar(token, ')') == false) { + return CSS_INVALID; + } + + result = malloc(sizeof(*result)); + if (result == NULL) { + css__mq_cond_destroy(cond); + return CSS_NOMEM; + } + memset(result, 0, sizeof(*result)); + result->type = CSS_MQ_COND; + result->data.cond = cond; + *cond_or_feature = result; + return CSS_OK; + } + } else if (token->type == CSS_TOKEN_IDENT || + token->type == CSS_TOKEN_NUMBER || + token->type == CSS_TOKEN_DIMENSION) { + css_mq_feature *feature; + error = mq_parse_media_feature(strings, vector, ctx, &feature); + if (error == CSS_OK) { + result = malloc(sizeof(*result)); + if (result == NULL) { + css__mq_feature_destroy(feature); + return CSS_NOMEM; + } + memset(result, 0, sizeof(*result)); + result->type = CSS_MQ_FEATURE; + result->data.feat = feature; + *cond_or_feature = result; + return CSS_OK; + } + } + + *ctx = old_ctx; + error = mq_parse_general_enclosed(strings, vector, ctx); + + return error; +} + +static css_error mq_parse_condition(lwc_string **strings, + const parserutils_vector *vector, int *ctx, + bool permit_or, css_mq_cond **cond) +{ + const css_token *token; + bool match = false; + int op = 0; /* Will be AND | OR once we've had one */ + css_mq_cond_or_feature *cond_or_feature, **parts; + css_mq_cond *result; + css_error error; + + /* <media-condition> = <media-not> | <media-in-parens> [ <media-and>* | <media-or>* ] + * <media-condition-without-or> = <media-not> | <media-in-parens> <media-and>* + * <media-not> = not <media-in-parens> + * <media-and> = and <media-in-parens> + * <media-or> = or <media-in-parens> + */ + + token = parserutils_vector_peek(vector, *ctx); + if (token == NULL || + (tokenIsChar(token, '(') == false && + token->type != CSS_TOKEN_IDENT && + lwc_string_caseless_isequal(token->idata, + strings[NOT], &match) != lwc_error_ok && + match == false)) { + return CSS_INVALID; + } + + result = malloc(sizeof(*result)); + if (result == NULL) { + return CSS_NOMEM; + } + memset(result, 0, sizeof(*result)); + result->parts = malloc(sizeof(*result->parts)); + if (result->parts == NULL) { + free(result); + return CSS_NOMEM; + } + memset(result->parts, 0, sizeof(*result->parts)); + + if (tokenIsChar(token, '(') == false) { + /* Must be "not" */ + parserutils_vector_iterate(vector, ctx); + consumeWhitespace(vector, ctx); + + error = mq_parse_media_in_parens(strings, + vector, ctx, &cond_or_feature); + if (error != CSS_OK) { + css__mq_cond_destroy(result); + return CSS_INVALID; + } + + result->negate = 1; + result->parts->nparts = 1; + result->parts->parts = malloc(sizeof(*result->parts->parts)); + if (result->parts->parts == NULL) { + css__mq_cond_or_feature_destroy(cond_or_feature); + css__mq_cond_destroy(result); + return CSS_NOMEM; + } + result->parts->parts[0] = cond_or_feature; + + *cond = result; + + return CSS_OK; + } + + /* FOLLOW(media-condition) := RPAREN | COMMA | EOF */ + while (token != NULL && tokenIsChar(token, ')') == false && + tokenIsChar(token, ',') == false) { + error = mq_parse_media_in_parens(strings, vector, ctx, + &cond_or_feature); + if (error != CSS_OK) { + css__mq_cond_destroy(result); + return CSS_INVALID; + } + + parts = realloc(result->parts->parts, + (result->parts->nparts+1)*sizeof(*result->parts->parts)); + if (parts == NULL) { + css__mq_cond_or_feature_destroy(cond_or_feature); + css__mq_cond_destroy(result); + return CSS_NOMEM; + } + parts[result->parts->nparts] = cond_or_feature; + result->parts->parts = parts; + result->parts->nparts++; + + consumeWhitespace(vector, ctx); + + token = parserutils_vector_peek(vector, *ctx); + if (token != NULL && tokenIsChar(token, ')') == false && + tokenIsChar(token, ',') == false) { + if (token->type != CSS_TOKEN_IDENT) { + css__mq_cond_destroy(result); + return CSS_INVALID; + } else if (lwc_string_caseless_isequal(token->idata, + strings[AND], &match) == lwc_error_ok && + match) { + if (op != 0 && op != AND) { + css__mq_cond_destroy(result); + return CSS_INVALID; + } + op = AND; + } else if (lwc_string_caseless_isequal(token->idata, + strings[OR], &match) == lwc_error_ok && + match) { + if (permit_or == false || (op != 0 && op != OR)) { + css__mq_cond_destroy(result); + return CSS_INVALID; + } + op = OR; + } else { + /* Neither AND nor OR */ + css__mq_cond_destroy(result); + return CSS_INVALID; + } + + parserutils_vector_iterate(vector, ctx); + consumeWhitespace(vector, ctx); + } + } + + if (op == OR) { + result->op = 1; + } + + *cond = result; + + return CSS_OK; +} + +/** + * Parse a media query type. + */ +static uint64_t mq_parse_type(lwc_string **strings, lwc_string *type) +{ + bool match; + + if (type == NULL) { + return CSS_MEDIA_ALL; + } else if (lwc_string_caseless_isequal( + type, strings[AURAL], + &match) == lwc_error_ok && match) { + return CSS_MEDIA_AURAL; + } else if (lwc_string_caseless_isequal( + type, strings[BRAILLE], + &match) == lwc_error_ok && match) { + return CSS_MEDIA_BRAILLE; + } else if (lwc_string_caseless_isequal( + type, strings[EMBOSSED], + &match) == lwc_error_ok && match) { + return CSS_MEDIA_EMBOSSED; + } else if (lwc_string_caseless_isequal( + type, strings[HANDHELD], + &match) == lwc_error_ok && match) { + return CSS_MEDIA_HANDHELD; + } else if (lwc_string_caseless_isequal( + type, strings[PRINT], + &match) == lwc_error_ok && match) { + return CSS_MEDIA_PRINT; + } else if (lwc_string_caseless_isequal( + type, strings[PROJECTION], + &match) == lwc_error_ok && match) { + return CSS_MEDIA_PROJECTION; + } else if (lwc_string_caseless_isequal( + type, strings[SCREEN], + &match) == lwc_error_ok && match) { + return CSS_MEDIA_SCREEN; + } else if (lwc_string_caseless_isequal( + type, strings[SPEECH], + &match) == lwc_error_ok && match) { + return CSS_MEDIA_SPEECH; + } else if (lwc_string_caseless_isequal( + type, strings[TTY], + &match) == lwc_error_ok && match) { + return CSS_MEDIA_TTY; + } else if (lwc_string_caseless_isequal( + type, strings[TV], + &match) == lwc_error_ok && match) { + return CSS_MEDIA_TV; + } else if (lwc_string_caseless_isequal( + type, strings[ALL], + &match) == lwc_error_ok && match) { + return CSS_MEDIA_ALL; + } + + return 0; +} + +static css_error mq_parse_media_query(lwc_string **strings, + const parserutils_vector *vector, int *ctx, + css_mq_query **query) +{ + const css_token *token; + bool match, is_condition = false; + css_mq_query *result; + css_error error; + + /* <media-query> = <media-condition> + * | [ not | only ]? <media-type> [ and <media-condition-without-or> ]? + * <media-type> = <ident> (except "not", "and", "or", "only") + */ + + // LPAREN -> media-condition + // not LPAREN -> media-condition + + consumeWhitespace(vector, ctx); + + token = parserutils_vector_peek(vector, *ctx); + if (tokenIsChar(token, '(')) { + is_condition = true; + } else if (token->type == CSS_TOKEN_IDENT && + lwc_string_caseless_isequal(token->idata, + strings[NOT], &match) == lwc_error_ok && + match) { + int old_ctx = *ctx; + + parserutils_vector_iterate(vector, ctx); + consumeWhitespace(vector, ctx); + + token = parserutils_vector_peek(vector, *ctx); + if (tokenIsChar(token, '(')) { + is_condition = true; + } + + *ctx = old_ctx; + } + + result = malloc(sizeof(*result)); + if (result == NULL) { + return CSS_NOMEM; + } + memset(result, 0, sizeof(*result)); + + if (is_condition) { + /* media-condition */ + error = mq_parse_condition(strings, vector, ctx, true, + &result->cond); + if (error != CSS_OK) { + free(result); + return error; + } + + *query = result; + return CSS_OK; + } + + token = parserutils_vector_iterate(vector, ctx); + if (token == NULL || token->type != CSS_TOKEN_IDENT) { + free(result); + return CSS_INVALID; + } + + if (lwc_string_caseless_isequal(token->idata, + strings[NOT], &match) == lwc_error_ok && match) { + result->negate_type = 1; + consumeWhitespace(vector, ctx); + token = parserutils_vector_iterate(vector, ctx); + } else if (lwc_string_caseless_isequal(token->idata, + strings[ONLY], &match) == lwc_error_ok && match) { + consumeWhitespace(vector, ctx); + token = parserutils_vector_iterate(vector, ctx); + } + + if (token == NULL || token->type != CSS_TOKEN_IDENT) { + free(result); + return CSS_INVALID; + } + + result->type = mq_parse_type(strings, token->idata); + + consumeWhitespace(vector, ctx); + + token = parserutils_vector_iterate(vector, ctx); + if (token != NULL) { + if (token->type != CSS_TOKEN_IDENT || + lwc_string_caseless_isequal(token->idata, + strings[AND], &match) != lwc_error_ok || + match == false) { + free(result); + return CSS_INVALID; + } + + consumeWhitespace(vector, ctx); + + error = mq_parse_condition(strings, vector, ctx, false, + &result->cond); + if (error != CSS_OK) { + free(result); + return error; + } + } + + *query = result; + return CSS_OK; +} + +css_error css__mq_parse_media_list(lwc_string **strings, + const parserutils_vector *vector, int *ctx, + css_mq_query **media) +{ + css_mq_query *result = NULL, *last = NULL; + const css_token *token; + css_error error; + + /* <media-query-list> = <media-query> [ COMMA <media-query> ]* */ + + /* 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 + */ + + token = parserutils_vector_peek(vector, *ctx); + while (token != NULL) { + css_mq_query *query; + + error = mq_parse_media_query(strings, vector, ctx, &query); + if (error != CSS_OK) { + /* TODO: error recovery (see above) */ + css__mq_query_destroy(result); + return error; + } else { + if (result == NULL) { + result = last = query; + } else { + assert(last != NULL); + last->next = query; + last = query; + } + } + + consumeWhitespace(vector, ctx); + + token = parserutils_vector_iterate(vector, ctx); + if (token != NULL && tokenIsChar(token, ',') == false) { + /* Give up */ + break; + } + } + + *media = result; + + return CSS_OK; +} + +typedef struct css_mq_parse_ctx { + lwc_string **strings; + css_mq_query *media; +} css_mq_parse_ctx; + +static css_error css_parse_media_query_handle_event( + css_parser_event type, + const parserutils_vector *tokens, + void *pw) +{ + int idx = 0; + css_error err; + css_mq_query *media; + const css_token *tok; + css_mq_parse_ctx *ctx = pw; + lwc_string **strings = ctx->strings; + + UNUSED(type); + + /* Skip @media */ + tok = parserutils_vector_iterate(tokens, &idx); + assert(tok->type == CSS_TOKEN_ATKEYWORD); + UNUSED(tok); + + /* Skip whitespace */ + tok = parserutils_vector_iterate(tokens, &idx); + assert(tok->type == CSS_TOKEN_S); + UNUSED(tok); + + err = css__mq_parse_media_list(strings, tokens, &idx, &media); + if (err != CSS_OK) { + return CSS_OK; + } + + ctx->media = media; + return CSS_OK; +} + +css_error css_parse_media_query(lwc_string **strings, + const uint8_t *mq, size_t len, + css_mq_query **media_out) +{ + css_error err; + css_parser *parser; + css_mq_parse_ctx ctx = { + .strings = strings, + }; + css_parser_optparams params_quirks = { + .quirks = false, + }; + css_parser_optparams params_handler = { + .event_handler = { + .handler = css_parse_media_query_handle_event, + .pw = &ctx, + }, + }; + + if (mq == NULL || len == 0) { + return CSS_BADPARM; + } + + err = css__parser_create_for_media_query(NULL, + CSS_CHARSET_DEFAULT, &parser); + if (err != CSS_OK) { + return err; + } + + err = css__parser_setopt(parser, CSS_PARSER_QUIRKS, + ¶ms_quirks); + if (err != CSS_OK) { + css__parser_destroy(parser); + return err; + } + + err = css__parser_setopt(parser, CSS_PARSER_EVENT_HANDLER, + ¶ms_handler); + if (err != CSS_OK) { + css__parser_destroy(parser); + return err; + } + + err = css__parser_parse_chunk(parser, + (const uint8_t *)"@media ", + strlen("@media ")); + if (err != CSS_OK && err != CSS_NEEDDATA) { + css__parser_destroy(parser); + return err; + } + + err = css__parser_parse_chunk(parser, mq, len); + if (err != CSS_OK && err != CSS_NEEDDATA) { + css__parser_destroy(parser); + return err; + } + + err = css__parser_completed(parser); + if (err != CSS_OK) { + css__parser_destroy(parser); + return err; + } + + css__parser_destroy(parser); + + *media_out = ctx.media; + return CSS_OK; +} + |