X-Git-Url: https://jxself.org/git/?a=blobdiff_plain;f=src%2Fsymbols.c;h=8f2a09e52c90a4bd563e73bec844235caa60cb5f;hb=8e63120c630c94c598d4e2d6ba823dac59bce8fa;hp=6d736e940832cf1893b288d8417811a57e61fd24;hpb=d11f2f726ed7feea617476d99cf7505ddd9a27ce;p=inform.git diff --git a/src/symbols.c b/src/symbols.c index 6d736e9..8f2a09e 100644 --- a/src/symbols.c +++ b/src/symbols.c @@ -1,8 +1,8 @@ /* ------------------------------------------------------------------------- */ /* "symbols" : The symbols table; creating stock of reserved words */ /* */ -/* Part of Inform 6.35 */ -/* copyright (c) Graham Nelson 1993 - 2021 */ +/* Part of Inform 6.40 */ +/* copyright (c) Graham Nelson 1993 - 2022 */ /* */ /* Inform is free software: you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ @@ -15,7 +15,7 @@ /* GNU General Public License for more details. */ /* */ /* You should have received a copy of the GNU General Public License */ -/* along with Inform. If not, see https://gnu.org/licenses/ * +/* along with Inform. If not, see https://gnu.org/licenses/ */ /* */ /* ------------------------------------------------------------------------- */ @@ -30,24 +30,32 @@ int no_symbols; /* Total number of symbols defined */ int no_named_constants; /* Copied into story file */ /* ------------------------------------------------------------------------- */ -/* Plus six arrays. Each symbol has its own index n (an int32) and */ +/* Plus an array of symbolinfo. Each symbol has its own index n (an */ +/* int32) in the array. The struct there contains: */ /* */ -/* svals[n] is its value (must be 32 bits wide, i.e. an int32, tho' */ -/* it is used to hold an unsigned 16 bit Z-machine value) */ -/* sflags[n] holds flags (see "header.h" for a list) */ -/* stypes[n] is the "type", distinguishing between the data type of */ +/* value is its value. In Z-code, this holds both the 16-bit value */ +/* and the 16-bit backpatch marker, so it is an int32. */ +/* marker is the backpatch marker in Glulx. */ +/* flags holds flags (see "header.h" for a list of ?_SFLAGS) */ +/* type is the "type", distinguishing between the data type of */ /* different kinds of constants/variables. */ -/* (See the "typename()" below.) */ -/* symbs[n] (needs to be cast to (char *) to be used) is the name */ -/* of the symbol, in the same case form as when created. */ -/* slines[n] is the source line on which the symbol value was first */ +/* (A ?_T constant; see the "typename()" below.) */ +/* name is the name of the symbol, in the same case form as */ +/* when created. */ +/* line is the source line on which the symbol value was first */ /* assigned */ -/* symbol_debug_backpatch_positions[n] */ +/* next_entry is the forward link in the symbol hash table. (See */ +/* start_of_list, below.) */ +/* */ +/* When generating a debug file (-k switch), we also allocate an array */ +/* of symboldebuginfo, which contains: */ +/* */ +/* backpatch_pos */ /* is a file position in the debug information file where */ /* the symbol's value should be written after backpatching, */ /* or else the null position if the value was known and */ /* written beforehand */ -/* replacement_debug_backpatch_positions[n] */ +/* replacement_backpatch_pos */ /* is a file position in the debug information file where */ /* the symbol's name can be erased if it is replaced, or */ /* else null if the name will never need to be replaced */ @@ -55,39 +63,28 @@ int no_named_constants; /* Copied into story file */ /* Comparison is case insensitive. */ /* Note that local variable names are not entered into the symbols table, */ /* as their numbers and scope are too limited for this to be efficient. */ -/* ------------------------------------------------------------------------- */ -/* Caveat editor: some array types are set up to work even on machines */ -/* where sizeof(int32 *) differs from, e.g., sizeof(char *): so do not */ -/* alter the types unless you understand what is going on! */ /* ------------------------------------------------------------------------- */ - int32 **symbs; - int32 *svals; - int *smarks; /* Glulx-only */ - brief_location *slines; - int *sflags; -#ifdef VAX - char *stypes; /* In VAX C, insanely, "signed char" is illegal */ -#else - signed char *stypes; -#endif - maybe_file_position *symbol_debug_backpatch_positions; - maybe_file_position *replacement_debug_backpatch_positions; +symbolinfo *symbols; /* Allocated up to no_symbols */ +static memory_list symbols_memlist; +symboldebuginfo *symbol_debug_info; /* Allocated up to no_symbols */ +static memory_list symbol_debug_info_memlist; /* ------------------------------------------------------------------------- */ /* Memory to hold the text of symbol names: note that this memory is */ /* allocated as needed in chunks of size SYMBOLS_CHUNK_SIZE. */ /* ------------------------------------------------------------------------- */ -#define MAX_SYMBOL_CHUNKS (100) +#define SYMBOLS_CHUNK_SIZE (4096) -static uchar *symbols_free_space, /* Next byte free to hold new names */ +static char *symbols_free_space, /* Next byte free to hold new names */ *symbols_ceiling; /* Pointer to the end of the current allocation of memory for names */ static char** symbol_name_space_chunks; /* For chunks of memory used to hold the name strings of symbols */ static int no_symbol_name_space_chunks; +static memory_list symbol_name_space_chunks_memlist; /* Symbol replacements (used by the "Replace X Y" directive). */ @@ -131,8 +128,8 @@ static int symbol_definitions_size = 0; /* calloced size */ /* idea to choose HASH_TAB_SIZE as large as conveniently possible. */ /* ------------------------------------------------------------------------- */ -static int *next_entry; -static int32 *start_of_list; +static int32 *start_of_list; /* Allocated array of size HASH_TAB_SIZE */ +/* The next_entry field is part of the symbolinfo struct. */ /* ------------------------------------------------------------------------- */ /* Initialisation. */ @@ -187,7 +184,8 @@ extern int strcmpcis(char *p, char *q) /* ------------------------------------------------------------------------- */ extern void add_config_symbol_definition(char *symbol, int32 value) -{ +{ char *str; + if (symbol_definitions_count == symbol_definitions_size) { int oldsize = symbol_definitions_size; if (symbol_definitions_size == 0) @@ -198,7 +196,7 @@ extern void add_config_symbol_definition(char *symbol, int32 value) symbol_definitions_size, "symbol definition table"); } - char *str = my_malloc(strlen(symbol)+1, "symbol name"); + str = my_malloc(strlen(symbol)+1, "symbol name"); strcpy(str, symbol); symbol_definitions[symbol_definitions_count].symbol = str; @@ -210,17 +208,48 @@ extern void add_config_symbol_definition(char *symbol, int32 value) /* Symbol finding, creating, and removing. */ /* ------------------------------------------------------------------------- */ +extern int get_symbol_index(char *p) +{ + /* Return the index in the symbols array of symbol "p", or -1 + if it isn't there. Does not create a new symbol or mark the + symbol as used. */ + + int32 new_entry, this; + char *r; + int hashcode = hash_code_from_string(p); + + this = start_of_list[hashcode]; + + do + { if (this == -1) break; + + r = symbols[this].name; + new_entry = strcmpcis(r, p); + if (new_entry == 0) + { + return this; + } + if (new_entry > 0) break; + + this = symbols[this].next_entry; + } while (this != -1); + + return -1; +} + extern int symbol_index(char *p, int hashcode) { - /* Return the index in the symbs/svals/sflags/stypes/... arrays of symbol - "p", creating a new symbol with that name if it isn't already there. + /* Return the index in the symbols array of symbol "p", creating a + new symbol with that name if it isn't already there. New symbols are created with flag UNKNOWN_SFLAG, value 0x100 (a 2-byte quantity in Z-machine terms) and type CONSTANT_T. The string "p" is undamaged. */ - int32 new_entry, this, last; char *r; + int32 new_entry, this, last; + char *r; + int len; if (hashcode == -1) hashcode = hash_code_from_string(p); @@ -229,7 +258,7 @@ extern int symbol_index(char *p, int hashcode) do { if (this == -1) break; - r = (char *)symbs[this]; + r = symbols[this].name; new_entry = strcmpcis(r, p); if (new_entry == 0) { @@ -240,51 +269,55 @@ extern int symbol_index(char *p, int hashcode) if (new_entry > 0) break; last = this; - this = next_entry[this]; + this = symbols[this].next_entry; } while (this != -1); - if (no_symbols >= MAX_SYMBOLS) - memoryerror("MAX_SYMBOLS", MAX_SYMBOLS); + if (symdef_trace_setting) + printf("Encountered symbol %d '%s'\n", no_symbols, p); + + ensure_memory_list_available(&symbols_memlist, no_symbols+1); + if (debugfile_switch) + ensure_memory_list_available(&symbol_debug_info_memlist, no_symbols+1); if (last == -1) - { next_entry[no_symbols]=start_of_list[hashcode]; + { symbols[no_symbols].next_entry=start_of_list[hashcode]; start_of_list[hashcode]=no_symbols; } else - { next_entry[no_symbols]=this; - next_entry[last]=no_symbols; + { symbols[no_symbols].next_entry=this; + symbols[last].next_entry=no_symbols; } - if (symbols_free_space+strlen(p)+1 >= symbols_ceiling) + len = strlen(p); + if (symbols_free_space+len+1 >= symbols_ceiling) { symbols_free_space = my_malloc(SYMBOLS_CHUNK_SIZE, "symbol names chunk"); symbols_ceiling = symbols_free_space + SYMBOLS_CHUNK_SIZE; - /* If we've passed MAX_SYMBOL_CHUNKS chunks, we print an error - message telling the user to increase SYMBOLS_CHUNK_SIZE. - That is the correct cure, even though the error comes out - worded inaccurately. */ - if (no_symbol_name_space_chunks >= MAX_SYMBOL_CHUNKS) - memoryerror("SYMBOLS_CHUNK_SIZE", SYMBOLS_CHUNK_SIZE); + ensure_memory_list_available(&symbol_name_space_chunks_memlist, no_symbol_name_space_chunks+1); symbol_name_space_chunks[no_symbol_name_space_chunks++] - = (char *) symbols_free_space; - if (symbols_free_space+strlen(p)+1 >= symbols_ceiling) - memoryerror("SYMBOLS_CHUNK_SIZE", SYMBOLS_CHUNK_SIZE); + = symbols_free_space; + if (symbols_free_space+len+1 >= symbols_ceiling) + { + /* This should be impossible, since SYMBOLS_CHUNK_SIZE > MAX_IDENTIFIER_LENGTH. */ + fatalerror("Symbol exceeds the maximum possible length"); + } } - strcpy((char *) symbols_free_space, p); - symbs[no_symbols] = (int32 *) symbols_free_space; - symbols_free_space += strlen((char *)symbols_free_space) + 1; + strcpy(symbols_free_space, p); + symbols[no_symbols].name = symbols_free_space; + symbols_free_space += (len+1); - svals[no_symbols] = 0x100; /* ###-wrong? Would this fix the + symbols[no_symbols].value = 0x100; /* ###-wrong? Would this fix the unbound-symbol-causes-asm-error? */ - sflags[no_symbols] = UNKNOWN_SFLAG; - stypes[no_symbols] = CONSTANT_T; - slines[no_symbols] = get_brief_location(&ErrorReport); + symbols[no_symbols].flags = UNKNOWN_SFLAG; + symbols[no_symbols].marker = 0; + symbols[no_symbols].type = CONSTANT_T; + symbols[no_symbols].line = get_brief_location(&ErrorReport); if (debugfile_switch) { nullify_debug_file_position - (&symbol_debug_backpatch_positions[no_symbols]); + (&symbol_debug_info[no_symbols].backpatch_pos); nullify_debug_file_position - (&replacement_debug_backpatch_positions[no_symbols]); + (&symbol_debug_info[no_symbols].replacement_backpatch_pos); } if (track_unused_routines) @@ -300,19 +333,19 @@ extern void end_symbol_scope(int k) */ int j; - j = hash_code_from_string((char *) symbs[k]); + j = hash_code_from_string(symbols[k].name); if (start_of_list[j] == k) - { start_of_list[j] = next_entry[k]; + { start_of_list[j] = symbols[k].next_entry; return; } j = start_of_list[j]; while (j != -1) { - if (next_entry[j] == k) - { next_entry[j] = next_entry[k]; + if (symbols[j].next_entry == k) + { symbols[j].next_entry = symbols[k].next_entry; return; } - j = next_entry[j]; + j = symbols[j].next_entry; } } @@ -340,6 +373,10 @@ extern char *typename(int type) case OBJECT_T: return("Object"); case CLASS_T: return("Class"); case FAKE_ACTION_T: return("Fake action"); + + /* These are not symbol types, but they get printed in errors. */ + case STRING_REQ_T: return("String"); + case DICT_WORD_REQ_T: return("Dictionary word"); default: return("(Unknown type)"); } @@ -347,40 +384,140 @@ extern char *typename(int type) static void describe_flags(int flags) { if (flags & UNKNOWN_SFLAG) printf("(?) "); - if (flags & USED_SFLAG) printf("(used) "); if (flags & REPLACE_SFLAG) printf("(Replaced) "); + if (flags & USED_SFLAG) printf("(used) "); if (flags & DEFCON_SFLAG) printf("(Defaulted) "); if (flags & STUB_SFLAG) printf("(Stubbed) "); - if (flags & CHANGE_SFLAG) printf("(value will change) "); if (flags & IMPORT_SFLAG) printf("(Imported) "); if (flags & EXPORT_SFLAG) printf("(Exported) "); + if (flags & ALIASED_SFLAG) printf("(aliased) "); + if (flags & CHANGE_SFLAG) printf("(value will change) "); if (flags & SYSTEM_SFLAG) printf("(System) "); if (flags & INSF_SFLAG) printf("(created in sys file) "); if (flags & UERROR_SFLAG) printf("('Unknown' error issued) "); - if (flags & ALIASED_SFLAG) printf("(aliased) "); if (flags & ACTION_SFLAG) printf("(Action name) "); if (flags & REDEFINABLE_SFLAG) printf("(Redefinable) "); + if (flags & STAR_SFLAG) printf("(*) "); } extern void describe_symbol(int k) { printf("%4d %-16s %2d:%04d %04x %s ", - k, (char *) (symbs[k]), - (int)(slines[k].file_index), - (int)(slines[k].line_number), - svals[k], typename(stypes[k])); - describe_flags(sflags[k]); + k, (symbols[k].name), + (int)(symbols[k].line.file_index), + (int)(symbols[k].line.line_number), + symbols[k].value, typename(symbols[k].type)); + describe_flags(symbols[k].flags); } extern void list_symbols(int level) { int k; for (k=0; k=2) || + ((symbols[k].flags & (SYSTEM_SFLAG + UNKNOWN_SFLAG + INSF_SFLAG)) == 0)) { describe_symbol(k); printf("\n"); } } } +/* Check that the operand is of the given symbol type (XXX_T). If wanttype2 is nonzero, that's a second allowable type. + Generate a warning if no match. */ +extern void check_warn_symbol_type(const assembly_operand *AO, int wanttype, int wanttype2, char *context) +{ + symbolinfo *sym; + int symtype; + + if (AO->symindex < 0) + { + /* This argument is not a symbol; it's a local variable, a literal, or a computed expression. */ + /* We can recognize and type-check some literals. */ + if (AO->marker == DWORD_MV) { + if (wanttype != DICT_WORD_REQ_T && wanttype2 != DICT_WORD_REQ_T) + symtype_warning(context, NULL, typename(DICT_WORD_REQ_T), typename(wanttype)); + } + if (AO->marker == STRING_MV) { + if (wanttype != STRING_REQ_T && wanttype2 != STRING_REQ_T) + symtype_warning(context, NULL, typename(STRING_REQ_T), typename(wanttype)); + } + return; + } + + sym = &symbols[AO->symindex]; + symtype = sym->type; + + if (symtype == GLOBAL_VARIABLE_T) + { + /* A global variable could have any value. No way to generate a warning. */ + return; + } + if (symtype == CONSTANT_T) + { + /* A constant could also have any value. This case also includes forward-declared constants (UNKNOWN_SFLAG). */ + /* We try inferring its type by looking at the backpatch marker. Sadly, this only works for objects. (And not in Z-code, where object values are not backpatched.) */ + if (sym->marker == OBJECT_MV) { + /* Continue with inferred type. */ + symtype = OBJECT_T; + } + else { + /* Give up. */ + return; + } + } + + if (!( (symtype == wanttype) + || (wanttype2 != 0 && symtype == wanttype2))) + { + symtype_warning(context, sym->name, typename(symtype), typename(wanttype)); + } +} + +/* Similar, but we allow any type that has a metaclass: Object, Class, String, or Routine. + Generate a warning if no match. */ +extern void check_warn_symbol_has_metaclass(const assembly_operand *AO, char *context) +{ + symbolinfo *sym; + int symtype; + + if (AO->symindex < 0) + { + /* This argument is not a symbol; it's a local variable, a literal, or a computed expression. */ + /* We can recognize and type-check some literals. */ + if (AO->marker == DWORD_MV) { + symtype_warning(context, NULL, typename(DICT_WORD_REQ_T), "Object/Class/Routine/String"); + } + if (AO->marker == STRING_MV) { + /* Strings are good here. */ + } + return; + } + + sym = &symbols[AO->symindex]; + symtype = sym->type; + + if (symtype == GLOBAL_VARIABLE_T) + { + /* A global variable could have any value. No way to generate a warning. */ + return; + } + if (symtype == CONSTANT_T) + { + /* A constant could also have any value. This case also includes forward-declared constants (UNKNOWN_SFLAG). */ + /* We try inferring its type by looking at the backpatch marker. Sadly, this only works for objects. (And not in Z-code, where object values are not backpatched.) */ + if (sym->marker == OBJECT_MV) { + /* Continue with inferred type. */ + symtype = OBJECT_T; + } + else { + /* Give up. */ + return; + } + } + + if (!(symtype == ROUTINE_T || symtype == CLASS_T || symtype == OBJECT_T)) + { + symtype_warning(context, sym->name, typename(symtype), "Object/Class/Routine/String"); + } +} + extern void issue_unused_warnings(void) { int32 i; @@ -393,14 +530,25 @@ extern void issue_unused_warnings(void) /* Now back to mark anything necessary as used */ i = symbol_index("Main", -1); - if (!(sflags[i] & UNKNOWN_SFLAG)) sflags[i] |= USED_SFLAG; + if (!(symbols[i].flags & UNKNOWN_SFLAG)) symbols[i].flags |= USED_SFLAG; for (i=0;i= 0 && (symbols[value].flags & USED_SFLAG) && !(symbols[value].flags & UNKNOWN_SFLAG)) { + value = get_symbol_index("debug_flag"); + if (value >= 0 && (symbols[value].flags & USED_SFLAG) && (symbols[value].flags & UNKNOWN_SFLAG)) { + warning("DEBUG mode is on, but this story or library does not appear to support it"); + } } } @@ -431,112 +579,112 @@ extern void write_the_identifier_names(void) for (i=0; i"); debug_file_printf ("##%s", idname_string); - debug_file_printf("%d", svals[i]); + debug_file_printf("%d", symbols[i].value); debug_file_printf(""); } - action_name_strings[svals[i]] + action_name_strings[symbols[i].value] = compile_string(idname_string, STRCTX_SYMBOL); } } for (i=0; iusage |= DF_USAGE_MAIN; } ix = symbol_index("Main", -1); - if (stypes[ix] == ROUTINE_T) { - uint32 addr = svals[ix] * (glulx_mode ? 1 : scale_factor); + if (symbols[ix].type == ROUTINE_T) { + uint32 addr = symbols[ix].value * (glulx_mode ? 1 : scale_factor); tofunc = df_function_for_address(addr); if (tofunc) tofunc->usage |= DF_USAGE_MAIN; @@ -1120,12 +1265,12 @@ extern void locate_dead_functions(void) for (ent = func->refs; ent; ent=ent->refsnext) { uint32 addr; int symbol = ent->symbol; - if (stypes[symbol] != ROUTINE_T) + if (symbols[symbol].type != ROUTINE_T) continue; - addr = svals[symbol] * (glulx_mode ? 1 : scale_factor); + addr = symbols[symbol].value * (glulx_mode ? 1 : scale_factor); tofunc = df_function_for_address(addr); if (!tofunc) { - error_named("Internal error in stripping: global ROUTINE_T symbol is not found in df_function map:", (char *)symbs[symbol]); + error_named("Internal error in stripping: global ROUTINE_T symbol is not found in df_function map:", symbols[symbol].name); continue; } /* A function may be marked here more than once. That's fine. */ @@ -1177,12 +1322,12 @@ extern void locate_dead_functions(void) for (ent = func->refs; ent; ent=ent->refsnext) { uint32 addr; int symbol = ent->symbol; - if (stypes[symbol] != ROUTINE_T) + if (symbols[symbol].type != ROUTINE_T) continue; - addr = svals[symbol] * (glulx_mode ? 1 : scale_factor); + addr = symbols[symbol].value * (glulx_mode ? 1 : scale_factor); tofunc = df_function_for_address(addr); if (!tofunc) { - error_named("Internal error in stripping: function ROUTINE_T symbol is not found in df_function map:", (char *)symbs[symbol]); + error_named("Internal error in stripping: function ROUTINE_T symbol is not found in df_function map:", symbols[symbol].name); continue; } if (tofunc->usage) @@ -1391,18 +1536,14 @@ extern uint32 df_next_function_iterate(int *funcused) extern void init_symbols_vars(void) { - symbs = NULL; - svals = NULL; - smarks = NULL; - stypes = NULL; - sflags = NULL; - next_entry = NULL; + symbols = NULL; start_of_list = NULL; + symbol_debug_info = NULL; symbol_name_space_chunks = NULL; no_symbol_name_space_chunks = 0; symbols_free_space=NULL; - symbols_ceiling=symbols_free_space; + symbols_ceiling=NULL; no_symbols = 0; @@ -1433,28 +1574,21 @@ extern void symbols_begin_pass(void) extern void symbols_allocate_arrays(void) { - symbs = my_calloc(sizeof(char *), MAX_SYMBOLS, "symbols"); - svals = my_calloc(sizeof(int32), MAX_SYMBOLS, "symbol values"); - if (glulx_mode) - smarks = my_calloc(sizeof(int), MAX_SYMBOLS, "symbol markers"); - slines = my_calloc(sizeof(brief_location), MAX_SYMBOLS, "symbol lines"); - stypes = my_calloc(sizeof(char), MAX_SYMBOLS, "symbol types"); - sflags = my_calloc(sizeof(int), MAX_SYMBOLS, "symbol flags"); + initialise_memory_list(&symbols_memlist, + sizeof(symbolinfo), 6400, (void**)&symbols, + "symbols"); if (debugfile_switch) - { symbol_debug_backpatch_positions = - my_calloc(sizeof(maybe_file_position), MAX_SYMBOLS, - "symbol debug information backpatch positions"); - replacement_debug_backpatch_positions = - my_calloc(sizeof(maybe_file_position), MAX_SYMBOLS, - "replacement debug information backpatch positions"); + { + initialise_memory_list(&symbol_debug_info_memlist, + sizeof(symboldebuginfo), 6400, (void**)&symbol_debug_info, + "symbol debug backpatch info"); } - next_entry = my_calloc(sizeof(int), MAX_SYMBOLS, - "symbol linked-list forward links"); start_of_list = my_calloc(sizeof(int32), HASH_TAB_SIZE, "hash code list beginnings"); - symbol_name_space_chunks - = my_calloc(sizeof(char *), MAX_SYMBOL_CHUNKS, "symbol names chunk addresses"); + initialise_memory_list(&symbol_name_space_chunks_memlist, + sizeof(char *), 32, (void**)&symbol_name_space_chunks, + "symbol names chunk addresses"); if (track_unused_routines) { df_tables_closed = FALSE; @@ -1495,23 +1629,13 @@ extern void symbols_free_arrays(void) my_free(&(symbol_name_space_chunks[i]), "symbol names chunk"); - my_free(&symbol_name_space_chunks, "symbol names chunk addresses"); + deallocate_memory_list(&symbol_name_space_chunks_memlist); - my_free(&symbs, "symbols"); - my_free(&svals, "symbol values"); - my_free(&smarks, "symbol markers"); - my_free(&slines, "symbol lines"); - my_free(&stypes, "symbol types"); - my_free(&sflags, "symbol flags"); + deallocate_memory_list(&symbols_memlist); if (debugfile_switch) - { my_free - (&symbol_debug_backpatch_positions, - "symbol debug information backpatch positions"); - my_free - (&replacement_debug_backpatch_positions, - "replacement debug information backpatch positions"); + { + deallocate_memory_list(&symbol_debug_info_memlist); } - my_free(&next_entry, "symbol linked-list forward links"); my_free(&start_of_list, "hash code list beginnings"); if (symbol_replacements)