forked from len0rd/rockbox
		
	git-svn-id: svn://svn.rockbox.org/rockbox/trunk@30907 a1c6a512-1295-4272-9138-f99709370657
		
			
				
	
	
		
			849 lines
		
	
	
	
		
			26 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			849 lines
		
	
	
	
		
			26 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /***************************************************************************
 | |
|  *             __________               __   ___.
 | |
|  *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 | |
|  *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 | |
|  *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 | |
|  *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 | |
|  *                     \/            \/     \/    \/            \/
 | |
|  * $Id$
 | |
|  *
 | |
|  * Copyright (C) 2011 Amaury Pouly
 | |
|  *
 | |
|  * This program is free software; you can redistribute it and/or
 | |
|  * modify it under the terms of the GNU General Public License
 | |
|  * as published by the Free Software Foundation; either version 2
 | |
|  * of the License, or (at your option) any later version.
 | |
|  *
 | |
|  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 | |
|  * KIND, either express or implied.
 | |
|  *
 | |
|  ****************************************************************************/
 | |
| 
 | |
| #define _POSIX_C_SOURCE 200809L /* for strdup */
 | |
| #include <stdio.h>
 | |
| #include <ctype.h>
 | |
| #include <stdint.h>
 | |
| #include <string.h>
 | |
| #include "dbparser.h"
 | |
| #include "misc.h"
 | |
| 
 | |
| enum lexem_type_t
 | |
| {
 | |
|     LEX_IDENTIFIER,
 | |
|     LEX_LPAREN,
 | |
|     LEX_RPAREN,
 | |
|     LEX_NUMBER,
 | |
|     LEX_STRING, /* double-quoted string */
 | |
|     LEX_EQUAL,
 | |
|     LEX_SEMICOLON,
 | |
|     LEX_LBRACE,
 | |
|     LEX_RBRACE,
 | |
|     LEX_RANGLE,
 | |
|     LEX_OR,
 | |
|     LEX_LSHIFT,
 | |
|     LEX_COLON,
 | |
|     LEX_LE,
 | |
|     LEX_EOF
 | |
| };
 | |
| 
 | |
| struct lexem_t
 | |
| {
 | |
|     enum lexem_type_t type;
 | |
|     /* if str is not NULL, it must be a malloc'd pointer */
 | |
|     char *str;
 | |
|     uint32_t num;
 | |
|     int line;
 | |
|     const char *file;
 | |
| };
 | |
| 
 | |
| struct context_t
 | |
| {
 | |
|     const char *file;
 | |
|     char *begin;
 | |
|     char *end;
 | |
|     char *ptr;
 | |
|     int line;
 | |
| };
 | |
| 
 | |
| #define parse_error(ctx, ...) \
 | |
|     do { fprintf(stderr, "%s:%d: ", ctx->file, ctx->line); \
 | |
|         fprintf(stderr, __VA_ARGS__); exit(2); } while(0)
 | |
| 
 | |
| static void advance(struct context_t *ctx, int nr_chars)
 | |
| {
 | |
|     while(nr_chars--)
 | |
|     {
 | |
|         if(*(ctx->ptr++) == '\n')
 | |
|             ctx->line++;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static inline bool eof(struct context_t *ctx)
 | |
| {
 | |
|     return ctx->ptr == ctx->end;
 | |
| }
 | |
| 
 | |
| static inline bool next_valid(struct context_t *ctx, int nr)
 | |
| {
 | |
|     return ctx->ptr + nr < ctx->end;
 | |
| }
 | |
| 
 | |
| static inline char cur_char(struct context_t *ctx)
 | |
| {
 | |
|     return *ctx->ptr;
 | |
| }
 | |
| 
 | |
| static inline char next_char(struct context_t *ctx, int nr)
 | |
| {
 | |
|     return ctx->ptr[nr];
 | |
| }
 | |
| 
 | |
| static inline void locate_lexem(struct lexem_t *lex, struct context_t *ctx)
 | |
| {
 | |
|     lex->file = ctx->file;
 | |
|     lex->line = ctx->line;
 | |
| }
 | |
| 
 | |
| static void __parse_string(struct context_t *ctx, void *user, void (*emit_fn)(void *user, char c))
 | |
| {
 | |
|     while(!eof(ctx))
 | |
|     {
 | |
|         if(cur_char(ctx) == '"')
 | |
|             break;
 | |
|         else if(cur_char(ctx) == '\\')
 | |
|         {
 | |
|             advance(ctx, 1);
 | |
|             if(eof(ctx))
 | |
|                 parse_error(ctx, "Unfinished string\n");
 | |
|             if(cur_char(ctx) == '\\') emit_fn(user, '\\');
 | |
|             else if(cur_char(ctx) == '\'') emit_fn(user, '\'');
 | |
|             else if(cur_char(ctx) == '\"') emit_fn(user, '\"');
 | |
|             else parse_error(ctx, "Unknown escape sequence \\%c\n", cur_char(ctx));
 | |
|             advance(ctx, 1);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             emit_fn(user, cur_char(ctx));
 | |
|             advance(ctx, 1);
 | |
|         }
 | |
|     }
 | |
|     if(eof(ctx) || cur_char(ctx) != '"')
 | |
|         parse_error(ctx, "Unfinished string\n");
 | |
|     advance(ctx, 1);
 | |
| }
 | |
| 
 | |
| static void __parse_string_emit(void *user, char c)
 | |
| {
 | |
|     char **pstr = (char **)user;
 | |
|     *(*pstr)++ = c;
 | |
| }
 | |
| 
 | |
| static void __parse_string_count(void *user, char c)
 | |
| {
 | |
|     (void) c;
 | |
|     (*(int *)user)++;
 | |
| }
 | |
| 
 | |
| static void parse_string(struct context_t *ctx, struct lexem_t *lexem)
 | |
| {
 | |
|     locate_lexem(lexem, ctx);
 | |
|     /* skip " */
 | |
|     advance(ctx, 1);
 | |
|     /* compute length */
 | |
|     struct context_t cpy_ctx = *ctx;
 | |
|     int length = 0;
 | |
|     __parse_string(&cpy_ctx, (void *)&length, __parse_string_count);
 | |
|     /* parse again */
 | |
|     lexem->type = LEX_STRING;
 | |
|     lexem->str = xmalloc(length + 1);
 | |
|     lexem->str[length] = 0;
 | |
|     char *pstr = lexem->str;
 | |
|     __parse_string(ctx, (void *)&pstr, __parse_string_emit);
 | |
| }
 | |
| 
 | |
| static void parse_ascii_number(struct context_t *ctx, struct lexem_t *lexem)
 | |
| {
 | |
|     locate_lexem(lexem, ctx);
 | |
|     /* skip ' */
 | |
|     advance(ctx, 1);
 | |
|     /* we expect n<=4 character and then '  */
 | |
|     int len = 0;
 | |
|     uint32_t value = 0;
 | |
|     while(!eof(ctx))
 | |
|     {
 | |
|         if(cur_char(ctx) != '\'')
 | |
|         {
 | |
|             value = value << 8 | cur_char(ctx);
 | |
|             len++;
 | |
|             advance(ctx, 1);
 | |
|         }
 | |
|         else
 | |
|             break;
 | |
|     }
 | |
|     if(eof(ctx) || cur_char(ctx) != '\'')
 | |
|         parse_error(ctx, "Unterminated ascii number literal\n");
 | |
|     if(len == 0 || len > 4)
 | |
|         parse_error(ctx, "Invalid ascii number literal length: only 1 to 4 characters allowed\n");
 | |
|     /* skip ' */
 | |
|     advance(ctx, 1);
 | |
|     lexem->type = LEX_NUMBER;
 | |
|     lexem->num = value;
 | |
| }
 | |
| 
 | |
| static void parse_number(struct context_t *ctx, struct lexem_t *lexem)
 | |
| {
 | |
|     locate_lexem(lexem, ctx);
 | |
|     /* check base */
 | |
|     int base = 10;
 | |
|     if(cur_char(ctx) == '0' && next_valid(ctx, 1) && next_char(ctx, 1) == 'x')
 | |
|     {
 | |
|         advance(ctx, 2);
 | |
|         base = 16;
 | |
|     }
 | |
| 
 | |
|     lexem->type = LEX_NUMBER;
 | |
|     lexem->num = 0;
 | |
|     while(!eof(ctx) && isxdigit(cur_char(ctx)))
 | |
|     {
 | |
|         if(base == 10 && !isdigit(cur_char(ctx)))
 | |
|             break;
 | |
|         byte v;
 | |
|         if(convxdigit(cur_char(ctx), &v))
 | |
|             break;
 | |
|         lexem->num = base * lexem->num + v;
 | |
|         advance(ctx, 1);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void parse_identifier(struct context_t *ctx, struct lexem_t *lexem)
 | |
| {
 | |
|     locate_lexem(lexem, ctx);
 | |
|     /* remember position */
 | |
|     char *old = ctx->ptr;
 | |
|     while(!eof(ctx) && (isalnum(cur_char(ctx)) || cur_char(ctx) == '_'))
 | |
|         advance(ctx, 1);
 | |
|     lexem->type = LEX_IDENTIFIER;
 | |
|     int len = ctx->ptr - old;
 | |
|     lexem->str = xmalloc(len + 1);
 | |
|     lexem->str[len] = 0;
 | |
|     memcpy(lexem->str, old, len);
 | |
| }
 | |
| 
 | |
| static void next_lexem(struct context_t *ctx, struct lexem_t *lexem)
 | |
| {
 | |
|     #define ret_simple(t, adv) \
 | |
|         do {locate_lexem(lexem, ctx); \
 | |
|             lexem->type = t; \
 | |
|             advance(ctx, adv); \
 | |
|             return;} while(0)
 | |
|     while(!eof(ctx))
 | |
|     {
 | |
|         char c = cur_char(ctx);
 | |
|         /* skip whitespace */
 | |
|         if(c == ' ' || c == '\t' || c == '\n' || c == '\r')
 | |
|         {
 | |
|             advance(ctx, 1);
 | |
|             continue;
 | |
|         }
 | |
|         /* skip C++ style comments */
 | |
|         if(c == '/' && next_valid(ctx, 1) && next_char(ctx, 1) == '/')
 | |
|         {
 | |
|             while(!eof(ctx) && cur_char(ctx) != '\n')
 | |
|                 advance(ctx, 1);
 | |
|             continue;
 | |
|         }
 | |
|         /* skip C-style comments */
 | |
|         if(c == '/' && next_valid(ctx, 1) && next_char(ctx, 1) == '*')
 | |
|         {
 | |
|             advance(ctx, 2);
 | |
|             while(true)
 | |
|             {
 | |
|                 if(!next_valid(ctx, 1))
 | |
|                     parse_error(ctx, "Unterminated comment");
 | |
|                 if(cur_char(ctx) == '*' && next_char(ctx, 1) == '/')
 | |
|                 {
 | |
|                     advance(ctx, 2);
 | |
|                     break;
 | |
|                 }
 | |
|                 advance(ctx, 1);
 | |
|             }
 | |
|             continue;
 | |
|         }
 | |
|         break;
 | |
|     }
 | |
|     if(eof(ctx)) ret_simple(LEX_EOF, 0);
 | |
|     char c = cur_char(ctx);
 | |
|     bool nv = next_valid(ctx, 1);
 | |
|     char nc = nv  ? next_char(ctx, 1) : 0;
 | |
|     if(c == '(') ret_simple(LEX_LPAREN, 1);
 | |
|     if(c == ')') ret_simple(LEX_RPAREN, 1);
 | |
|     if(c == '{') ret_simple(LEX_LBRACE, 1);
 | |
|     if(c == '}') ret_simple(LEX_RBRACE, 1);
 | |
|     if(c == '>') ret_simple(LEX_RANGLE, 1);
 | |
|     if(c == '=') ret_simple(LEX_EQUAL, 1);
 | |
|     if(c == ';') ret_simple(LEX_SEMICOLON, 1);
 | |
|     if(c == ',') ret_simple(LEX_COLON, 1);
 | |
|     if(c == '|') ret_simple(LEX_OR, 1);
 | |
|     if(c == '<' && nv && nc == '<') ret_simple(LEX_LSHIFT, 2);
 | |
|     if(c == '<' && nv && nc == '=') ret_simple(LEX_LE, 2);
 | |
|     if(c == '"') return parse_string(ctx, lexem);
 | |
|     if(c == '\'') return parse_ascii_number(ctx, lexem);
 | |
|     if(isdigit(c)) return parse_number(ctx, lexem);
 | |
|     if(isalpha(c) || c == '_') return parse_identifier(ctx, lexem);
 | |
|     parse_error(ctx, "Unexpected character '%c'\n", c);
 | |
|     #undef ret_simple
 | |
| }
 | |
| 
 | |
| #if 0
 | |
| static void log_lexem(struct lexem_t *lexem)
 | |
| {
 | |
|     switch(lexem->type)
 | |
|     {
 | |
|         case LEX_EOF: printf("<eof>"); break;
 | |
|         case LEX_EQUAL: printf("="); break;
 | |
|         case LEX_IDENTIFIER: printf("id(%s)", lexem->str); break;
 | |
|         case LEX_LPAREN: printf("("); break;
 | |
|         case LEX_RPAREN: printf(")"); break;
 | |
|         case LEX_LBRACE: printf("{"); break;
 | |
|         case LEX_RBRACE: printf("}"); break;
 | |
|         case LEX_SEMICOLON: printf(";"); break;
 | |
|         case LEX_NUMBER: printf("num(%d)", lexem->num); break;
 | |
|         case LEX_STRING: printf("str(%s)", lexem->str); break;
 | |
|         case LEX_OR: printf("|"); break;
 | |
|         case LEX_LSHIFT: printf("<<"); break;
 | |
|         default: printf("<unk>");
 | |
|     }
 | |
| }
 | |
| #endif
 | |
| 
 | |
| struct cmd_source_t *db_find_source_by_id(struct cmd_file_t *cmd_file, const char *id)
 | |
| {
 | |
|     struct cmd_source_t *src = cmd_file->source_list;
 | |
|     while(src)
 | |
|     {
 | |
|         if(strcmp(src->identifier, id) == 0)
 | |
|             return src;
 | |
|         src = src->next;
 | |
|     }
 | |
|     return NULL;
 | |
| }
 | |
| 
 | |
| struct cmd_option_t *db_find_option_by_id(struct cmd_option_t *opt, const char *name)
 | |
| {
 | |
|     while(opt)
 | |
|     {
 | |
|         if(strcmp(opt->name, name) == 0)
 | |
|             return opt;
 | |
|         opt = opt->next;
 | |
|     }
 | |
|     return NULL;
 | |
| }
 | |
| 
 | |
| #define INVALID_SB_SUBVERSION   0xffff
 | |
| 
 | |
| static uint16_t parse_sb_subversion(char *str)
 | |
| {
 | |
|     int len = strlen(str);
 | |
|     uint16_t n = 0;
 | |
|     if(len == 0 || len > 4)
 | |
|         return INVALID_SB_SUBVERSION;
 | |
|     for(int i = 0; i < len; i++)
 | |
|     {
 | |
|         if(!isdigit(str[i]))
 | |
|             return INVALID_SB_SUBVERSION;
 | |
|         n = n << 4 | (str[i] - '0');
 | |
|     }
 | |
|     return n;
 | |
| }
 | |
| 
 | |
| bool db_parse_sb_version(struct sb_version_t *ver, char *str)
 | |
| {
 | |
|     int len = strlen(str);
 | |
|     int cnt = 0;
 | |
|     int pos[2];
 | |
| 
 | |
|     for(int i = 0; i < len; i++)
 | |
|     {
 | |
|         if(str[i] != '.')
 | |
|             continue;
 | |
|         if(cnt == 2)
 | |
|             return false;
 | |
|         pos[cnt++] = i + 1;
 | |
|         str[i] = 0;
 | |
|     }
 | |
|     if(cnt != 2)
 | |
|         return false;
 | |
|     ver->major = parse_sb_subversion(str);
 | |
|     ver->minor = parse_sb_subversion(str + pos[0]);
 | |
|     ver->revision = parse_sb_subversion(str + pos[1]);
 | |
|     return ver->major != INVALID_SB_SUBVERSION &&
 | |
|         ver->minor != INVALID_SB_SUBVERSION &&
 | |
|         ver->revision != INVALID_SB_SUBVERSION;
 | |
| }
 | |
| 
 | |
| #undef parse_error
 | |
| #define parse_error(lexem, ...) \
 | |
|     do { fprintf(stderr, "%s:%d: ", lexem.file, lexem.line); \
 | |
|         fprintf(stderr, __VA_ARGS__); exit(2); } while(0)
 | |
| 
 | |
| struct lex_ctx_t
 | |
| {
 | |
|     struct context_t ctx;
 | |
|     struct lexem_t lexem;
 | |
| };
 | |
| 
 | |
| /* When lexems hold strings (like identifier), it might be useful to steal
 | |
|  * the pointer and don't clean the lexem but in other case, one don't want
 | |
|  * to keep the pointer to the string and just want to release the memory.
 | |
|  * Thus clean_lexem should be true except when one keeps a pointer */
 | |
| static inline void next(struct lex_ctx_t *ctx, bool clean_lexem)
 | |
| {
 | |
|     if(clean_lexem)
 | |
|         free(ctx->lexem.str);
 | |
|     memset(&ctx->lexem, 0, sizeof(struct lexem_t));
 | |
|     next_lexem(&ctx->ctx, &ctx->lexem);
 | |
| }
 | |
| 
 | |
| static uint32_t parse_term_expr(struct lex_ctx_t *ctx, struct cmd_option_t *const_list)
 | |
| {
 | |
|     uint32_t ret = 0;
 | |
|     if(ctx->lexem.type == LEX_NUMBER)
 | |
|         ret = ctx->lexem.num;
 | |
|     else if(ctx->lexem.type == LEX_IDENTIFIER)
 | |
|     {
 | |
|         struct cmd_option_t *c = db_find_option_by_id(const_list, ctx->lexem.str);
 | |
|         if(c == NULL)
 | |
|             parse_error(ctx->lexem, "Undefined reference to constant '%s'\n", ctx->lexem.str);
 | |
|         if(c->is_string)
 | |
|             parse_error(ctx->lexem, "Internal error: constant '%s' is not an integer\n", ctx->lexem.str);
 | |
|         ret = c->val;
 | |
|     }
 | |
|     else
 | |
|         parse_error(ctx->lexem, "Number or constant identifier expected\n");
 | |
|     next(ctx, true);
 | |
|     return ret;
 | |
| }
 | |
| 
 | |
| static uint32_t parse_shift_expr(struct lex_ctx_t *ctx, struct cmd_option_t *const_list)
 | |
| {
 | |
|     uint32_t v = parse_term_expr(ctx, const_list);
 | |
|     while(ctx->lexem.type == LEX_LSHIFT)
 | |
|     {
 | |
|         next(ctx, true);
 | |
|         v <<= parse_term_expr(ctx, const_list);
 | |
|     }
 | |
|     return v;
 | |
| }
 | |
| 
 | |
| static uint32_t parse_or_expr(struct lex_ctx_t *ctx, struct cmd_option_t *const_list)
 | |
| {
 | |
|     uint32_t v = parse_shift_expr(ctx, const_list);
 | |
|     while(ctx->lexem.type == LEX_OR)
 | |
|     {
 | |
|         next(ctx, true);
 | |
|         v |= parse_shift_expr(ctx, const_list);
 | |
|     }
 | |
|     return v;
 | |
| }
 | |
| 
 | |
| static uint32_t parse_intexpr(struct lex_ctx_t *ctx, struct cmd_option_t *const_list)
 | |
| {
 | |
|     return parse_or_expr(ctx, const_list);
 | |
| }
 | |
| 
 | |
| #define NR_INITIAL_CONSTANTS    4
 | |
| static char *init_const_name[NR_INITIAL_CONSTANTS] = {"true", "false", "yes", "no"};
 | |
| static uint32_t init_const_value[NR_INITIAL_CONSTANTS] = {1, 0, 1, 0};
 | |
| 
 | |
| struct cmd_file_t *db_parse_file(const char *file)
 | |
| {
 | |
|     size_t size;
 | |
|     FILE *f = fopen(file, "r");
 | |
|     if(f == NULL)
 | |
|         bugp("Cannot open file '%s'", file);
 | |
|     fseek(f, 0, SEEK_END);
 | |
|     size = ftell(f);
 | |
|     fseek(f, 0, SEEK_SET);
 | |
|     char *buf = xmalloc(size);
 | |
|     if(fread(buf, size, 1, f) != 1)
 | |
|         bugp("Cannot read file '%s'", file);
 | |
|     fclose(f);
 | |
| 
 | |
|     if(g_debug)
 | |
|         printf("Parsing db file '%s'\n", file);
 | |
|     struct cmd_file_t *cmd_file = xmalloc(sizeof(struct cmd_file_t));
 | |
|     memset(cmd_file, 0, sizeof(struct cmd_file_t));
 | |
| 
 | |
|     /* add initial constants */
 | |
|     for(int i = 0; i < NR_INITIAL_CONSTANTS; i++)
 | |
|     {
 | |
|         struct cmd_option_t *opt = xmalloc(sizeof(struct cmd_option_t));
 | |
|         memset(opt, 0, sizeof(struct cmd_option_t));
 | |
|         opt->name = strdup(init_const_name[i]);
 | |
|         opt->is_string = false;
 | |
|         opt->val = init_const_value[i];
 | |
|         opt->next = cmd_file->constant_list;
 | |
|         cmd_file->constant_list = opt;
 | |
|     }
 | |
| 
 | |
|     struct lex_ctx_t lctx;
 | |
|     lctx.ctx.file = file;
 | |
|     lctx.ctx.line = 1;
 | |
|     lctx.ctx.begin = buf;
 | |
|     lctx.ctx.ptr = buf;
 | |
|     lctx.ctx.end = buf + size;
 | |
|     #define next(clean_lexem) next(&lctx, clean_lexem)
 | |
|     #define lexem lctx.lexem
 | |
|     /* init lexer */
 | |
|     next(false); /* don't clean init lexem because it doesn't exist */
 | |
|     /* constants ? */
 | |
|     if(lexem.type == LEX_IDENTIFIER && !strcmp(lexem.str, "constants"))
 | |
|     {
 | |
|         next(true);
 | |
|         if(lexem.type != LEX_LBRACE)
 | |
|             parse_error(lexem, "'{' expected after 'constants'\n");
 | |
| 
 | |
|         while(true)
 | |
|         {
 | |
|             struct cmd_option_t *opt = xmalloc(sizeof(struct cmd_option_t));
 | |
|             memset(opt, 0, sizeof(struct cmd_option_t));
 | |
|             next(true);
 | |
|             if(lexem.type == LEX_RBRACE)
 | |
|                 break;
 | |
|             if(lexem.type != LEX_IDENTIFIER)
 | |
|                 parse_error(lexem, "Identifier expected in constants\n");
 | |
|             opt->name = lexem.str;
 | |
|             next(false); /* lexem string is kept as option name */
 | |
|             if(lexem.type != LEX_EQUAL)
 | |
|                 parse_error(lexem, "'=' expected after identifier\n");
 | |
|             next(true);
 | |
|             opt->is_string = false;
 | |
|             opt->val = parse_intexpr(&lctx, cmd_file->constant_list);
 | |
|             opt->next = cmd_file->constant_list;
 | |
|             cmd_file->constant_list = opt;
 | |
|             if(lexem.type != LEX_SEMICOLON)
 | |
|                parse_error(lexem, "';' expected after string\n");
 | |
|         }
 | |
|         next(true);
 | |
|     }
 | |
|     /* options ? */
 | |
|     if(lexem.type == LEX_IDENTIFIER && !strcmp(lexem.str, "options"))
 | |
|     {
 | |
|         next(true);
 | |
|         if(lexem.type != LEX_LBRACE)
 | |
|             parse_error(lexem, "'{' expected after 'options'\n");
 | |
| 
 | |
|         while(true)
 | |
|         {
 | |
|             next(true);
 | |
|             if(lexem.type == LEX_RBRACE)
 | |
|                 break;
 | |
|             struct cmd_option_t *opt = xmalloc(sizeof(struct cmd_option_t));
 | |
|             memset(opt, 0, sizeof(struct cmd_option_t));
 | |
|             if(lexem.type != LEX_IDENTIFIER)
 | |
|                 parse_error(lexem, "Identifier expected in options\n");
 | |
|             opt->name = lexem.str;
 | |
|             next(false); /* lexem string is kept as option name */
 | |
|             if(lexem.type != LEX_EQUAL)
 | |
|                 parse_error(lexem, "'=' expected after identifier\n");
 | |
|             next(true);
 | |
|             if(lexem.type == LEX_STRING)
 | |
|             {
 | |
|                 opt->is_string = true;
 | |
|                 opt->str = lexem.str;
 | |
|                 next(false); /* lexem string is kept as option name */
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 opt->is_string = false;
 | |
|                 opt->val = parse_intexpr(&lctx, cmd_file->constant_list);
 | |
|             }
 | |
|             opt->next = cmd_file->opt_list;
 | |
|             cmd_file->opt_list = opt;
 | |
|             if(lexem.type != LEX_SEMICOLON)
 | |
|                parse_error(lexem, "';' expected after string\n");
 | |
|         }
 | |
|         next(true);
 | |
|     }
 | |
|     /* sources */
 | |
|     if(lexem.type != LEX_IDENTIFIER || strcmp(lexem.str, "sources"))
 | |
|         parse_error(lexem, "'sources' expected\n");
 | |
|     next(true);
 | |
|     if(lexem.type != LEX_LBRACE)
 | |
|         parse_error(lexem, "'{' expected after 'sources'\n");
 | |
| 
 | |
|     while(true)
 | |
|     {
 | |
|         next(true);
 | |
|         if(lexem.type == LEX_RBRACE)
 | |
|             break;
 | |
|         struct cmd_source_t *src = xmalloc(sizeof(struct cmd_source_t));
 | |
|         memset(src, 0, sizeof(struct cmd_source_t));
 | |
|         if(lexem.type != LEX_IDENTIFIER)
 | |
|             parse_error(lexem, "identifier expected in sources\n");
 | |
|         src->identifier = lexem.str;
 | |
|         next(false); /* lexem string is kept as source name */
 | |
|         if(lexem.type != LEX_EQUAL)
 | |
|             parse_error(lexem, "'=' expected after identifier\n");
 | |
|         next(true);
 | |
|         if(lexem.type == LEX_STRING)
 | |
|         {
 | |
|             src->is_extern = false;
 | |
|             src->filename = lexem.str;
 | |
|             next(false); /* lexem string is kept as file name */
 | |
|         }
 | |
|         else if(lexem.type == LEX_IDENTIFIER && !strcmp(lexem.str, "extern"))
 | |
|         {
 | |
|             src->is_extern = true;
 | |
|             src->filename = strdup("<extern>"); /* duplicate because it will be free'd */
 | |
|             next(true);
 | |
|             if(lexem.type != LEX_LPAREN)
 | |
|                 parse_error(lexem, "'(' expected after 'extern'\n");
 | |
|             next(true);
 | |
|             src->extern_nr = parse_intexpr(&lctx, cmd_file->constant_list);
 | |
|             if(lexem.type != LEX_RPAREN)
 | |
|                 parse_error(lexem, "')' expected\n");
 | |
|             next(true);
 | |
|         }
 | |
|         else
 | |
|             parse_error(lexem, "String or 'extern' expected after '='\n");
 | |
|         if(lexem.type != LEX_SEMICOLON)
 | |
|             parse_error(lexem, "';' expected\n");
 | |
|         if(db_find_source_by_id(cmd_file, src->identifier) != NULL)
 | |
|             parse_error(lexem, "Duplicate source identifier\n");
 | |
|         /* type filled later */
 | |
|         src->type = CMD_SRC_UNK;
 | |
|         src->next = cmd_file->source_list;
 | |
|         cmd_file->source_list = src;
 | |
|     }
 | |
| 
 | |
|     /* sections */
 | |
|     struct cmd_section_t *end_sec = NULL;
 | |
|     while(true)
 | |
|     {
 | |
|         next(true);
 | |
|         if(lexem.type == LEX_EOF)
 | |
|             break;
 | |
|         struct cmd_section_t *sec = xmalloc(sizeof(struct cmd_section_t));
 | |
|         struct cmd_inst_t *end_list = NULL;
 | |
|         memset(sec, 0, sizeof(struct cmd_section_t));
 | |
|         if(lexem.type != LEX_IDENTIFIER || strcmp(lexem.str, "section") != 0)
 | |
|             parse_error(lexem, "'section' expected\n");
 | |
|         next(true);
 | |
|         if(lexem.type != LEX_LPAREN)
 | |
|             parse_error(lexem, "'(' expected after 'section'\n");
 | |
|         next(true);
 | |
|         /* can be any number */
 | |
|         sec->identifier = parse_intexpr(&lctx, cmd_file->constant_list);
 | |
|         /* options ? */
 | |
|         if(lexem.type == LEX_SEMICOLON)
 | |
|         {
 | |
|             do
 | |
|             {
 | |
|                 next(true);
 | |
|                 struct cmd_option_t *opt = xmalloc(sizeof(struct cmd_option_t));
 | |
|                 memset(opt, 0, sizeof(struct cmd_option_t));
 | |
|                 if(lexem.type != LEX_IDENTIFIER)
 | |
|                     parse_error(lexem, "Identifier expected for section option\n");
 | |
|                 opt->name = lexem.str;
 | |
|                 next(false); /* lexem string is kept as option name */
 | |
|                 if(lexem.type != LEX_EQUAL)
 | |
|                     parse_error(lexem, "'=' expected after option identifier\n");
 | |
|                 next(true);
 | |
|                 if(lexem.type == LEX_STRING)
 | |
|                 {
 | |
|                     opt->is_string = true;
 | |
|                     opt->str = lexem.str;
 | |
|                     next(false); /* lexem string is kept as option string */
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     opt->is_string = false;
 | |
|                     opt->val = parse_intexpr(&lctx, cmd_file->constant_list);
 | |
|                 }
 | |
|                 opt->next = sec->opt_list;
 | |
|                 sec->opt_list = opt;
 | |
|             }while(lexem.type == LEX_COLON);
 | |
|         }
 | |
|         if(lexem.type != LEX_RPAREN)
 | |
|             parse_error(lexem, "')' expected after section identifier\n");
 | |
|         next(true);
 | |
|         if(lexem.type == LEX_LBRACE)
 | |
|         {
 | |
|             sec->is_data = false;
 | |
|             /* commands */
 | |
|             while(true)
 | |
|             {
 | |
|                 next(true);
 | |
|                 if(lexem.type == LEX_RBRACE)
 | |
|                     break;
 | |
|                 struct cmd_inst_t *inst = xmalloc(sizeof(struct cmd_inst_t));
 | |
|                 memset(inst, 0, sizeof(struct cmd_inst_t));
 | |
|                 if(lexem.type != LEX_IDENTIFIER)
 | |
|                     parse_error(lexem, "Instruction expected in section\n");
 | |
|                 if(strcmp(lexem.str, "load") == 0)
 | |
|                     inst->type = CMD_LOAD;
 | |
|                 else if(strcmp(lexem.str, "call") == 0)
 | |
|                     inst->type = CMD_CALL;
 | |
|                 else if(strcmp(lexem.str, "jump") == 0)
 | |
|                     inst->type = CMD_JUMP;
 | |
|                 else if(strcmp(lexem.str, "mode") == 0)
 | |
|                     inst->type = CMD_MODE;
 | |
|                 else
 | |
|                     parse_error(lexem, "Instruction expected in section\n");
 | |
|                 next(true);
 | |
| 
 | |
|                 if(inst->type == CMD_LOAD)
 | |
|                 {
 | |
|                     if(lexem.type != LEX_IDENTIFIER)
 | |
|                         parse_error(lexem, "Identifier expected after instruction\n");
 | |
|                     inst->identifier = lexem.str;
 | |
|                     if(db_find_source_by_id(cmd_file, inst->identifier) == NULL)
 | |
|                         parse_error(lexem, "Undefined reference to source '%s'\n", inst->identifier);
 | |
|                     next(false); /* lexem string kept as identifier  */
 | |
|                     if(lexem.type == LEX_RANGLE)
 | |
|                     {
 | |
|                         // load at
 | |
|                         inst->type = CMD_LOAD_AT;
 | |
|                         next(true);
 | |
|                         inst->addr = parse_intexpr(&lctx, cmd_file->constant_list);
 | |
|                     }
 | |
|                     if(lexem.type != LEX_SEMICOLON)
 | |
|                         parse_error(lexem, "';' expected after command\n");
 | |
|                 }
 | |
|                 else if(inst->type == CMD_CALL || inst->type == CMD_JUMP)
 | |
|                 {
 | |
|                     if(lexem.type == LEX_IDENTIFIER)
 | |
|                     {
 | |
|                         inst->identifier = lexem.str;
 | |
|                         if(db_find_source_by_id(cmd_file, inst->identifier) == NULL)
 | |
|                             parse_error(lexem, "Undefined reference to source '%s'\n", inst->identifier);
 | |
|                         next(false); /* lexem string kept as identifier  */
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         inst->type = (inst->type == CMD_CALL) ? CMD_CALL_AT : CMD_JUMP_AT;
 | |
|                         inst->addr = parse_intexpr(&lctx, cmd_file->constant_list);
 | |
|                     }
 | |
|                     
 | |
|                     if(lexem.type == LEX_LPAREN)
 | |
|                     {
 | |
|                         next(true);
 | |
|                         inst->argument = parse_intexpr(&lctx, cmd_file->constant_list);
 | |
|                         if(lexem.type != LEX_RPAREN)
 | |
|                             parse_error(lexem, "Expected closing brace\n");
 | |
|                         next(true);
 | |
|                     }
 | |
|                     if(lexem.type != LEX_SEMICOLON)
 | |
|                         parse_error(lexem, "';' expected after command\n");
 | |
|                 }
 | |
|                 else if(inst->type == CMD_MODE)
 | |
|                 {
 | |
|                     inst->argument = parse_intexpr(&lctx, cmd_file->constant_list);
 | |
|                     if(lexem.type != LEX_SEMICOLON)
 | |
|                         parse_error(lexem, "Expected ';' after command\n");
 | |
|                 }
 | |
|                 else
 | |
|                     parse_error(lexem, "Internal error");
 | |
|                 if(end_list == NULL)
 | |
|                 {
 | |
|                     sec->inst_list = inst;
 | |
|                     end_list = inst;
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     end_list->next = inst;
 | |
|                     end_list = inst;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         else if(lexem.type == LEX_LE)
 | |
|         {
 | |
|             sec->is_data = true;
 | |
|             next(true);
 | |
|             if(lexem.type != LEX_IDENTIFIER)
 | |
|                 parse_error(lexem, "Identifier expected after '<='\n");
 | |
|             sec->source_id = lexem.str;
 | |
|             next(false); /* lexem string is kept as source id */
 | |
|             if(lexem.type != LEX_SEMICOLON)
 | |
|                 parse_error(lexem, "';' expected after identifier\n");
 | |
|         }
 | |
|         else
 | |
|             parse_error(lexem, "'{' or '<=' expected after section directive\n");
 | |
| 
 | |
|         if(end_sec == NULL)
 | |
|         {
 | |
|             cmd_file->section_list = sec;
 | |
|             end_sec = sec;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             end_sec->next = sec;
 | |
|             end_sec = sec;
 | |
|         }
 | |
|     }
 | |
|     #undef lexem
 | |
|     #undef next
 | |
| 
 | |
|     free(buf);
 | |
|     return cmd_file;
 | |
| }
 | |
| 
 | |
| void db_generate_default_sb_version(struct sb_version_t *ver)
 | |
| {
 | |
|     ver->major = ver->minor = ver->revision = 0x999;
 | |
| }
 | |
| 
 | |
| void db_free_option_list(struct cmd_option_t *opt_list)
 | |
| {
 | |
|     while(opt_list)
 | |
|     {
 | |
|         struct cmd_option_t *next = opt_list->next;
 | |
|         fflush(stdout);
 | |
|         free(opt_list->name);
 | |
|         free(opt_list->str);
 | |
|         free(opt_list);
 | |
|         opt_list = next;
 | |
|     }
 | |
| }
 | |
| 
 | |
| void db_free(struct cmd_file_t *file)
 | |
| {
 | |
|     db_free_option_list(file->opt_list);
 | |
|     db_free_option_list(file->constant_list);
 | |
|     struct cmd_source_t *src = file->source_list;
 | |
|     while(src)
 | |
|     {
 | |
|         struct cmd_source_t *next = src->next;
 | |
|         free(src->identifier);
 | |
|         fflush(stdout);
 | |
|         free(src->filename);
 | |
|         if(src->loaded)
 | |
|         {
 | |
|             if(src->type == CMD_SRC_BIN)
 | |
|                 free(src->bin.data);
 | |
|             if(src->type == CMD_SRC_ELF)
 | |
|                 elf_release(&src->elf);
 | |
|         }
 | |
|         free(src);
 | |
|         src = next;
 | |
|     }
 | |
|     struct cmd_section_t *sec = file->section_list;
 | |
|     while(sec)
 | |
|     {
 | |
|         struct cmd_section_t *next = sec->next;
 | |
|         db_free_option_list(sec->opt_list);
 | |
|         free(sec->source_id);
 | |
|         struct cmd_inst_t *inst = sec->inst_list;
 | |
|         while(inst)
 | |
|         {
 | |
|             struct cmd_inst_t *next = inst->next;
 | |
|             free(inst->identifier);
 | |
|             free(inst);
 | |
|             inst = next;
 | |
|         }
 | |
|         free(sec);
 | |
|         sec = next;
 | |
|     }
 | |
|     free(file);
 | |
| }
 |