Edit

kc3-lang/libxkbcommon/src/xkbcomp/scanner.c

Branch :

  • Show log

    Commit

  • Author : Ran Benita
    Date : 2012-08-12 11:40:02
    Hash : a392d268
    Message : Replace flex scanner with a hand-written one The scanner is very similar in structure to the one in xkbcomp/rules.c. It avoids copying and has nicer error reporting. It uses gperf to generate a hashtable for the keywords, which gives a nice speed boost (compared to the naive strcasecmp method at least). But since there's hardly a reason to regenerate it every time and require people to install gperf, the output (keywords.c) is added here as well. Here are some stats from test/rulescomp: Before: compiled 1000 keymaps in 4.052939625s ==22063== total heap usage: 101,101 allocs, 101,101 frees, 11,840,834 bytes allocated After: compiled 1000 keymaps in 3.519665434s ==26505== total heap usage: 99,945 allocs, 99,945 frees, 7,033,608 bytes allocated Signed-off-by: Ran Benita <ran234@gmail.com>

  • src/xkbcomp/scanner.c
  • /*
     * Copyright © 2012 Ran Benita <ran234@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 (including the next
     * paragraph) 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.
     */
    
    #include "xkbcomp-priv.h"
    #include "parser-priv.h"
    #include "scanner-utils.h"
    
    int
    scanner_error(YYLTYPE *yylloc, struct scanner *s, const char *msg)
    {
        log_err(s->ctx, "%s:%d:%d: %s\n",
                s->file_name, yylloc->first_line, yylloc->first_column, msg);
        return ERROR_TOK;
    }
    
    static bool
    number(struct scanner *s, int64_t *out, enum yytokentype *out_tok)
    {
        bool is_float = false, is_hex = false;
        const char *start = s->s + s->pos;
        char *end;
    
        if (lit(s, "0x")) {
            while (isxdigit(peek(s))) next(s);
            is_hex = true;
        }
        else {
            while (isdigit(peek(s))) next(s);
            is_float = chr(s, '.');
            while (isdigit(peek(s))) next(s);
        }
        if (s->s + s->pos == start)
            return false;
    
        errno = 0;
        if (is_hex)
            *out = strtoul(start, &end, 16);
        else if (is_float)
            *out = strtod(start, &end);
        else
            *out = strtoul(start, &end, 10);
        if (errno != 0 || s->s + s->pos != end)
            *out_tok = ERROR_TOK;
        else
            *out_tok = (is_float ? FLOAT : INTEGER);
        return true;
    }
    
    int
    _xkbcommon_lex(YYSTYPE *yylval, YYLTYPE *yylloc, struct scanner *s)
    {
        enum yytokentype tok;
    
    skip_more_whitespace_and_comments:
        /* Skip spaces. */
        while (isspace(peek(s))) next(s);
    
        /* Skip comments. */
        if (lit(s, "//") || chr(s, '#')) {
            while (!eof(s) && !eol(s)) next(s);
            goto skip_more_whitespace_and_comments;
        }
    
        /* See if we're done. */
        if (eof(s)) return END_OF_FILE;
    
        /* New token. */
        yylloc->first_line = yylloc->last_line = s->line;
        yylloc->first_column = yylloc->last_column = s->column;
        s->buf_pos = 0;
    
        /* String literal. */
        if (chr(s, '\"')) {
            while (!eof(s) && !eol(s) && peek(s) != '\"') {
                if (chr(s, '\\')) {
                    uint8_t o;
                    if      (chr(s, '\\')) buf_append(s, '\\');
                    else if (chr(s, 'n'))  buf_append(s, '\n');
                    else if (chr(s, 't'))  buf_append(s, '\t');
                    else if (chr(s, 'r'))  buf_append(s, '\r');
                    else if (chr(s, 'b'))  buf_append(s, '\b');
                    else if (chr(s, 'f'))  buf_append(s, '\f');
                    else if (chr(s, 'v'))  buf_append(s, '\v');
                    else if (chr(s, 'e'))  buf_append(s, '\033');
                    else if (oct(s, &o))   buf_append(s, (char) o);
                    else return scanner_error(yylloc, s,
                                              "illegal escape sequence in string literal");
                } else {
                    buf_append(s, next(s));
                }
            }
            if (!buf_append(s, '\0') || !chr(s, '\"'))
                return scanner_error(yylloc, s, "unterminated string literal");
            yylval->str = strdup(s->buf);
            if (!yylval->str)
                return scanner_error(yylloc, s, "scanner out of memory");
            return STRING;
        }
    
        /* Key name literal. */
        if (chr(s, '<')) {
            while (isgraph(peek(s)) && peek(s) != '>')
                buf_append(s, next(s));
            if (s->buf_pos == 0)
                return scanner_error(yylloc, s, "empty key name literal");
            if (!buf_append(s, '\0') || !chr(s, '>'))
                return scanner_error(yylloc, s, "unterminated key name literal");
            yylval->sval = xkb_atom_intern(s->ctx, s->buf);
            return KEYNAME;
        }
    
        /* Operators and punctuation. */
        if (chr(s, ';')) return SEMI;
        if (chr(s, '{')) return OBRACE;
        if (chr(s, '}')) return CBRACE;
        if (chr(s, '=')) return EQUALS;
        if (chr(s, '[')) return OBRACKET;
        if (chr(s, ']')) return CBRACKET;
        if (chr(s, '(')) return OPAREN;
        if (chr(s, ')')) return CPAREN;
        if (chr(s, '.')) return DOT;
        if (chr(s, ',')) return COMMA;
        if (chr(s, '+')) return PLUS;
        if (chr(s, '-')) return MINUS;
        if (chr(s, '*')) return TIMES;
        if (chr(s, '!')) return EXCLAM;
        if (chr(s, '~')) return INVERT;
    
        /* Identifier. */
        if (isalpha(peek(s)) || peek(s) == '_') {
            s->buf_pos = 0;
            while (isalnum(peek(s)) || peek(s) == '_')
                buf_append(s, next(s));
            if (!buf_append(s, '\0'))
                return scanner_error(yylloc, s, "identifier too long");
    
            /* Keyword. */
            tok = keyword_to_token(s->buf);
            if (tok != -1) return tok;
    
            yylval->str = strdup(s->buf);
            if (!yylval->str)
                return scanner_error(yylloc, s, "scanner out of memory");
            return IDENT;
        }
    
        /* Number literal (hexadecimal / decimal / float). */
        if (number(s, &yylval->num, &tok)) {
            if (tok == ERROR_TOK)
                return scanner_error(yylloc, s, "malformed number literal");
            return tok;
        }
    
        return scanner_error(yylloc, s, "unrecognized token");
    }
    
    XkbFile *
    XkbParseString(struct xkb_context *ctx, const char *string, size_t len,
                   const char *file_name, const char *map)
    {
        struct scanner scanner;
        scanner_init(&scanner, ctx, string, len, file_name);
        return parse(ctx, &scanner, map);
    }
    
    XkbFile *
    XkbParseFile(struct xkb_context *ctx, FILE *file,
                 const char *file_name, const char *map)
    {
        bool ok;
        XkbFile *xkb_file;
        const char *string;
        size_t size;
    
        ok = map_file(file, &string, &size);
        if (!ok) {
            log_err(ctx, "Couldn't read XKB file %s: %s\n",
                    file_name, strerror(errno));
            return NULL;
        }
    
        xkb_file = XkbParseString(ctx, string, size, file_name, map);
        unmap_file(string, size);
        return xkb_file;
    }