forked from len0rd/rockbox
		
	
		
			
				
	
	
		
			999 lines
		
	
	
	
		
			30 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			999 lines
		
	
	
	
		
			30 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_option_t *db_add_opt(struct cmd_option_t **opt, const char *identifier, bool is_str)
 | |
| {
 | |
|     while(*opt)
 | |
|         opt = &(*opt)->next;
 | |
|     *opt = xmalloc(sizeof(struct cmd_option_t));
 | |
|     memset(*opt, 0, sizeof(struct cmd_option_t));
 | |
|     (*opt)->name = strdup(identifier);
 | |
|     (*opt)->is_string = is_str;
 | |
|     return *opt;
 | |
| }
 | |
| 
 | |
| void db_add_str_opt(struct cmd_option_t **opt, const char *name, const char *value)
 | |
| {
 | |
|     db_add_opt(opt, name, true)->str = strdup(value);
 | |
| }
 | |
| 
 | |
| void db_add_int_opt(struct cmd_option_t **opt, const char *name, uint32_t value)
 | |
| {
 | |
|     db_add_opt(opt, name, false)->val = value;
 | |
| }
 | |
| 
 | |
| static struct cmd_source_t *db_add_src(struct cmd_source_t **src, const char *identifier, bool is_extern)
 | |
| {
 | |
|     while(*src)
 | |
|         src = &(*src)->next;
 | |
|     *src = xmalloc(sizeof(struct cmd_source_t));
 | |
|     memset(*src, 0, sizeof(struct cmd_source_t));
 | |
|     (*src)->identifier = strdup(identifier);
 | |
|     (*src)->is_extern = is_extern;
 | |
|     return *src;
 | |
| }
 | |
| 
 | |
| void db_add_source(struct cmd_file_t *cmd_file, const char *identifier, const char *filename)
 | |
| {
 | |
|     db_add_src(&cmd_file->source_list, identifier, false)->filename = strdup(filename);
 | |
| }
 | |
| 
 | |
| void db_add_extern_source(struct cmd_file_t *cmd_file, const char *identifier, int extern_nr)
 | |
| {
 | |
|     db_add_src(&cmd_file->source_list, identifier, true)->extern_nr = extern_nr;
 | |
| }
 | |
| 
 | |
| static struct cmd_inst_t *db_add_inst(struct cmd_inst_t **list, enum cmd_inst_type_t type,
 | |
|     uint32_t argument)
 | |
| {
 | |
|     while(*list)
 | |
|         list = &(*list)->next;
 | |
|     *list = xmalloc(sizeof(struct cmd_inst_t));
 | |
|     memset(*list, 0, sizeof(struct cmd_inst_t));
 | |
|     (*list)->type = type;
 | |
|     (*list)->argument = argument;
 | |
|     return *list;
 | |
| }
 | |
| 
 | |
| void db_add_inst_id(struct cmd_section_t *cmd_section, enum cmd_inst_type_t type,
 | |
|     const char *identifier, uint32_t argument)
 | |
| {
 | |
|     db_add_inst(&cmd_section->inst_list, type, argument)->identifier = strdup(identifier);
 | |
| }
 | |
| 
 | |
| void db_add_inst_addr(struct cmd_section_t *cmd_section, enum cmd_inst_type_t type,
 | |
|     uint32_t addr, uint32_t argument)
 | |
| {
 | |
|     db_add_inst(&cmd_section->inst_list, type, argument)->addr = addr;
 | |
| }
 | |
| 
 | |
| struct cmd_section_t *db_add_section(struct cmd_file_t *cmd_file, uint32_t identifier, bool data)
 | |
| {
 | |
|     struct cmd_section_t **prev = &cmd_file->section_list;
 | |
|     while(*prev)
 | |
|         prev = &(*prev)->next;
 | |
|     *prev = xmalloc(sizeof(struct cmd_section_t));
 | |
|     memset(*prev, 0, sizeof(struct cmd_section_t));
 | |
|     (*prev)->identifier = identifier;
 | |
|     (*prev)->is_data = data;
 | |
|     return *prev;
 | |
| }
 | |
| 
 | |
| 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 const char *parse_sb_subversion(const char *str, uint16_t *v)
 | |
| {
 | |
|     int len = 0;
 | |
|     *v = 0;
 | |
|     while(isdigit(str[len]) && len < 3)
 | |
|         *v = (*v) << 4 | (str[len++] - '0');
 | |
|     if(len == 0)
 | |
|         *v = INVALID_SB_SUBVERSION;
 | |
|     return str + len;
 | |
| }
 | |
| 
 | |
| bool db_parse_sb_version(struct sb_version_t *ver, const char *str)
 | |
| {
 | |
|     str = parse_sb_subversion(str, &ver->major);
 | |
|     if(ver->major == INVALID_SB_SUBVERSION || *str != '.')
 | |
|         return false;
 | |
|     str = parse_sb_subversion(str + 1, &ver->minor);
 | |
|     if(ver->minor == INVALID_SB_SUBVERSION || *str != '.')
 | |
|         return false;
 | |
|     str = parse_sb_subversion(str + 1, &ver->revision);
 | |
|     if(ver->revision == INVALID_SB_SUBVERSION || *str != 0)
 | |
|         return false;
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| static bool db_generate_sb_subversion(uint16_t subver, char *str)
 | |
| {
 | |
|     str[0] = '0' + ((subver >> 8) & 0xf);
 | |
|     str[1] = '0' + ((subver >> 4) & 0xf);
 | |
|     str[2] = '0' + (subver & 0xf);
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| bool db_generate_sb_version(struct sb_version_t *ver, char *str, int size)
 | |
| {
 | |
|     if(size < 12)
 | |
|         return false;
 | |
|     str[3] = '.';
 | |
|     str[7] = '.';
 | |
|     str[11] = 0;
 | |
|     return db_generate_sb_subversion(ver->major, str) &&
 | |
|         db_generate_sb_subversion(ver->minor, str + 4) &&
 | |
|         db_generate_sb_subversion(ver->revision, str + 8);
 | |
| }
 | |
| 
 | |
| #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)
 | |
|     {
 | |
|         if(g_debug)
 | |
|             perror("Cannot open db file");
 | |
|         return NULL;
 | |
|     }
 | |
|     fseek(f, 0, SEEK_END);
 | |
|     size = ftell(f);
 | |
|     fseek(f, 0, SEEK_SET);
 | |
|     char *buf = xmalloc(size);
 | |
|     if(fread(buf, size, 1, f) != 1)
 | |
|     {
 | |
|         if(g_debug)
 | |
|             perror("Cannot read db file");
 | |
|         return NULL;
 | |
|     }
 | |
|     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++)
 | |
|         db_add_int_opt(&cmd_file->constant_list, init_const_name[i], init_const_value[i]);
 | |
| 
 | |
|     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)
 | |
|         {
 | |
|             next(true);
 | |
|             if(lexem.type == LEX_RBRACE)
 | |
|                 break;
 | |
|             if(lexem.type != LEX_IDENTIFIER)
 | |
|                 parse_error(lexem, "Identifier expected in constants\n");
 | |
|             const char *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);
 | |
|             db_add_int_opt(&cmd_file->constant_list, name, parse_intexpr(&lctx, cmd_file->constant_list));
 | |
|             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;
 | |
|             if(lexem.type != LEX_IDENTIFIER)
 | |
|                 parse_error(lexem, "Identifier expected in options\n");
 | |
|             const char *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)
 | |
|             {
 | |
|                 db_add_str_opt(&cmd_file->opt_list, name, lexem.str);
 | |
|                 next(true);
 | |
|             }
 | |
|             else
 | |
|                 db_add_int_opt(&cmd_file->opt_list, name, parse_intexpr(&lctx, cmd_file->constant_list));
 | |
|             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;
 | |
|         if(lexem.type != LEX_IDENTIFIER)
 | |
|             parse_error(lexem, "identifier expected in sources\n");
 | |
|         const char *srcid = lexem.str;
 | |
|         if(db_find_source_by_id(cmd_file, srcid) != NULL)
 | |
|             parse_error(lexem, "Duplicate source identifier\n");
 | |
|         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)
 | |
|         {
 | |
|             db_add_source(cmd_file, srcid, lexem.str);
 | |
|             next(true);
 | |
|         }
 | |
|         else if(lexem.type == LEX_IDENTIFIER && !strcmp(lexem.str, "extern"))
 | |
|         {
 | |
|             next(true);
 | |
|             if(lexem.type != LEX_LPAREN)
 | |
|                 parse_error(lexem, "'(' expected after 'extern'\n");
 | |
|             next(true);
 | |
|             db_add_extern_source(cmd_file, srcid, 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");
 | |
|     }
 | |
| 
 | |
|     /* sections */
 | |
|     while(true)
 | |
|     {
 | |
|         next(true);
 | |
|         if(lexem.type == LEX_EOF)
 | |
|             break;
 | |
|         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 */
 | |
|         struct cmd_section_t *sec = db_add_section(cmd_file, parse_intexpr(&lctx, cmd_file->constant_list), false);
 | |
|         /* options ? */
 | |
|         if(lexem.type == LEX_SEMICOLON)
 | |
|         {
 | |
|             do
 | |
|             {
 | |
|                 next(true);
 | |
|                 if(lexem.type != LEX_IDENTIFIER)
 | |
|                     parse_error(lexem, "Identifier expected for section option\n");
 | |
|                 const char *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)
 | |
|                 {
 | |
|                     db_add_str_opt(&sec->opt_list, name, lexem.str);
 | |
|                     next(true);
 | |
|                 }
 | |
|                 else
 | |
|                     db_add_int_opt(&sec->opt_list, name, parse_intexpr(&lctx, cmd_file->constant_list));
 | |
|             }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 = db_add_inst(&sec->inst_list, CMD_LOAD, 0);
 | |
|                 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");
 | |
|             }
 | |
|         }
 | |
|         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");
 | |
|     }
 | |
|     #undef lexem
 | |
|     #undef next
 | |
| 
 | |
|     free(buf);
 | |
|     return cmd_file;
 | |
| }
 | |
| 
 | |
| 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;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static bool db_generate_options(FILE *f, const char *secname, struct cmd_option_t *list)
 | |
| {
 | |
|     fprintf(f, "%s\n", secname);
 | |
|     fprintf(f, "{\n");
 | |
|     while(list)
 | |
|     {
 | |
|         fprintf(f, "    %s = ", list->name);
 | |
|         if(list->is_string)
 | |
|             fprintf(f, "\"%s\";\n", list->str); // FIXME handle escape
 | |
|         else
 | |
|             fprintf(f, "0x%x;\n", list->val);
 | |
|         list = list->next;
 | |
|     }
 | |
|     fprintf(f, "}\n");
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| static bool db_generate_section_options(FILE *f, struct cmd_option_t *list)
 | |
| {
 | |
|     bool first = true;
 | |
|     while(list)
 | |
|     {
 | |
|         fprintf(f, "%c %s = ", first ? ';' : ',', list->name);
 | |
|         if(list->is_string)
 | |
|             fprintf(f, "\"%s\"", list->str); // FIXME handle escape
 | |
|         else
 | |
|             fprintf(f, "0x%x", list->val);
 | |
|         first = false;
 | |
|         list = list->next;
 | |
|     }
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| static bool db_generate_sources(FILE *f, struct cmd_source_t *list)
 | |
| {
 | |
|     fprintf(f, "sources\n"),
 | |
|     fprintf(f, "{\n");
 | |
|     while(list)
 | |
|     {
 | |
|         fprintf(f, "    %s = ", list->identifier);
 | |
|         if(list->is_extern)
 | |
|             fprintf(f, "extern(%d);\n", list->extern_nr);
 | |
|         else
 | |
|             fprintf(f, "\"%s\";\n", list->filename); // FIXME handle escape
 | |
|         list = list->next;
 | |
|     }
 | |
|     fprintf(f, "}\n");
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| static bool db_generate_section(FILE *f, struct cmd_section_t *section)
 | |
| {
 | |
|     fprintf(f, "section(%#x", section->identifier);
 | |
|     db_generate_section_options(f, section->opt_list);
 | |
|     if(section->is_data)
 | |
|     {
 | |
|         fprintf(f, ") <= %s;\n", section->source_id);
 | |
|         return true;
 | |
|     }
 | |
|     fprintf(f, ")\n{\n");
 | |
|     struct cmd_inst_t *inst = section->inst_list;
 | |
|     while(inst)
 | |
|     {
 | |
|         fprintf(f, "    ");
 | |
|         switch(inst->type)
 | |
|         {
 | |
|             case CMD_LOAD:
 | |
|                 fprintf(f, "load %s;\n", inst->identifier);
 | |
|                 break;
 | |
|             case CMD_LOAD_AT:
 | |
|                 fprintf(f, "load %s > %#x;\n", inst->identifier, inst->addr);
 | |
|                 break;
 | |
|             case CMD_CALL:
 | |
|                 fprintf(f, "call %s(%#x);\n", inst->identifier, inst->argument);
 | |
|                 break;
 | |
|             case CMD_CALL_AT:
 | |
|                 fprintf(f, "call %#x(%#x);\n", inst->addr, inst->argument);
 | |
|                 break;
 | |
|             case CMD_JUMP:
 | |
|                 fprintf(f, "jump %s(%#x);\n", inst->identifier, inst->argument);
 | |
|                 break;
 | |
|             case CMD_JUMP_AT:
 | |
|                 fprintf(f, "jump %#x(%#x);\n", inst->addr, inst->argument);
 | |
|                 break;
 | |
|             case CMD_MODE:
 | |
|                 fprintf(f, "mode %#x;\n", inst->argument);
 | |
|                 break;
 | |
|             default:
 | |
|                 bug("die");
 | |
|         }
 | |
|         inst = inst->next;
 | |
|     }
 | |
|     fprintf(f, "}\n");
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| static bool db_generate_sections(FILE *f, struct cmd_section_t *section)
 | |
| {
 | |
|     while(section)
 | |
|         if(!db_generate_section(f, section))
 | |
|             return false;
 | |
|         else
 | |
|             section = section->next;
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| bool db_generate_file(struct cmd_file_t *file, const char *filename, void *user, db_color_printf printf)
 | |
| {
 | |
|     FILE *f = fopen(filename, "w");
 | |
|     if(f == NULL)
 | |
|         return printf(user, true, GREY, "Cannot open '%s' for writing: %m\n", filename), false;
 | |
|     if(!db_generate_options(f, "constants", file->constant_list))
 | |
|         goto Lerr;
 | |
|     if(!db_generate_options(f, "options", file->opt_list))
 | |
|         goto Lerr;
 | |
|     if(!db_generate_sources(f, file->source_list))
 | |
|         goto Lerr;
 | |
|     if(!db_generate_sections(f, file->section_list))
 | |
|         goto Lerr;
 | |
| 
 | |
|     fclose(f);
 | |
|     return true;
 | |
| 
 | |
|     Lerr:
 | |
|     fclose(f);
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| 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);
 | |
| }
 |