ref: ab12c474d27291fe69b25e68d00184b05a601926
dir: /src/asm/parser.y/
/* * This file is part of RGBDS. * * Copyright (c) 1997-2019, Carsten Sorensen and RGBDS contributors. * * SPDX-License-Identifier: MIT */ %{ #include <ctype.h> #include <errno.h> #include <inttypes.h> #include <stdbool.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "asm/charmap.h" #include "asm/fixpoint.h" #include "asm/format.h" #include "asm/fstack.h" #include "asm/lexer.h" #include "asm/macro.h" #include "asm/main.h" #include "asm/opt.h" #include "asm/output.h" #include "asm/rpn.h" #include "asm/section.h" #include "asm/symbol.h" #include "asm/util.h" #include "asm/warning.h" #include "extern/utf8decoder.h" #include "linkdefs.h" #include "platform.h" // strncasecmp, strdup static struct CaptureBody captureBody; // Captures a REPT/FOR or MACRO static void upperstring(char *dest, char const *src) { while (*src) *dest++ = toupper(*src++); *dest = '\0'; } static void lowerstring(char *dest, char const *src) { while (*src) *dest++ = tolower(*src++); *dest = '\0'; } static uint32_t str2int2(uint8_t *s, uint32_t length) { if (length > 4) warning(WARNING_NUMERIC_STRING_1, "Treating string as a number ignores first %" PRIu32 " character%s\n", length - 4, length == 5 ? "" : "s"); else if (length > 1) warning(WARNING_NUMERIC_STRING_2, "Treating %" PRIu32 "-character string as a number\n", length); uint32_t r = 0; for (uint32_t i = length < 4 ? 0 : length - 4; i < length; i++) { r <<= 8; r |= s[i]; } return r; } static const char *strrstr(char const *s1, char const *s2) { size_t len1 = strlen(s1); size_t len2 = strlen(s2); if (len2 > len1) return NULL; for (char const *p = s1 + len1 - len2; p >= s1; p--) if (!strncmp(p, s2, len2)) return p; return NULL; } static void errorInvalidUTF8Byte(uint8_t byte, char const *functionName) { error("%s: Invalid UTF-8 byte 0x%02hhX\n", functionName, byte); } static size_t strlenUTF8(char const *s) { size_t len = 0; uint32_t state = 0; for (uint32_t codep = 0; *s; s++) { uint8_t byte = *s; switch (decode(&state, &codep, byte)) { case 1: errorInvalidUTF8Byte(byte, "STRLEN"); state = 0; // fallthrough case 0: len++; break; } } // Check for partial code point. if (state != 0) error("STRLEN: Incomplete UTF-8 character\n"); return len; } static void strsubUTF8(char *dest, size_t destLen, char const *src, uint32_t pos, uint32_t len) { size_t srcIndex = 0; size_t destIndex = 0; uint32_t state = 0; uint32_t codep = 0; uint32_t curLen = 0; uint32_t curPos = 1; // Advance to starting position in source string. while (src[srcIndex] && curPos < pos) { switch (decode(&state, &codep, src[srcIndex])) { case 1: errorInvalidUTF8Byte(src[srcIndex], "STRSUB"); state = 0; // fallthrough case 0: curPos++; break; } srcIndex++; } // A position 1 past the end of the string is allowed, but will trigger the // "Length too big" warning below if the length is nonzero. if (!src[srcIndex] && pos > curPos) warning(WARNING_BUILTIN_ARG, "STRSUB: Position %" PRIu32 " is past the end of the string\n", pos); // Copy from source to destination. while (src[srcIndex] && destIndex < destLen - 1 && curLen < len) { switch (decode(&state, &codep, src[srcIndex])) { case 1: errorInvalidUTF8Byte(src[srcIndex], "STRSUB"); state = 0; // fallthrough case 0: curLen++; break; } dest[destIndex++] = src[srcIndex++]; } if (curLen < len) warning(WARNING_BUILTIN_ARG, "STRSUB: Length too big: %" PRIu32 "\n", len); // Check for partial code point. if (state != 0) error("STRSUB: Incomplete UTF-8 character\n"); dest[destIndex] = '\0'; } static size_t charlenUTF8(char const *s) { size_t len; for (len = 0; charmap_ConvertNext(&s, NULL); len++) ; return len; } static void charsubUTF8(char *dest, char const *src, uint32_t pos) { size_t charLen = 1; // Advance to starting position in source string. for (uint32_t curPos = 1; charLen && curPos < pos; curPos++) charLen = charmap_ConvertNext(&src, NULL); char const *start = src; if (!charmap_ConvertNext(&src, NULL)) warning(WARNING_BUILTIN_ARG, "CHARSUB: Position %" PRIu32 " is past the end of the string\n", pos); // Copy from source to destination. memcpy(dest, start, src - start); dest[src - start] = '\0'; } static uint32_t adjustNegativePos(int32_t pos, size_t len, char const *functionName) { // STRSUB and CHARSUB adjust negative `pos` arguments the same way, // such that position -1 is the last character of a string. if (pos < 0) pos += len + 1; if (pos < 1) { warning(WARNING_BUILTIN_ARG, "%s: Position starts at 1\n", functionName); pos = 1; } return (uint32_t)pos; } static void strrpl(char *dest, size_t destLen, char const *src, char const *old, char const *rep) { size_t oldLen = strlen(old); size_t repLen = strlen(rep); size_t i = 0; if (!oldLen) { warning(WARNING_EMPTY_STRRPL, "STRRPL: Cannot replace an empty string\n"); strcpy(dest, src); return; } for (char const *next = strstr(src, old); next && *next; next = strstr(src, old)) { // Copy anything before the substring to replace unsigned int lenBefore = next - src; memcpy(dest + i, src, lenBefore < destLen - i ? lenBefore : destLen - i); i += next - src; if (i >= destLen) break; // Copy the replacement substring memcpy(dest + i, rep, repLen < destLen - i ? repLen : destLen - i); i += repLen; if (i >= destLen) break; src = next + oldLen; } if (i < destLen) { size_t srcLen = strlen(src); // Copy anything after the last replaced substring memcpy(dest + i, src, srcLen < destLen - i ? srcLen : destLen - i); i += srcLen; } if (i >= destLen) { warning(WARNING_LONG_STR, "STRRPL: String too long, got truncated\n"); i = destLen - 1; } dest[i] = '\0'; } static void initStrFmtArgList(struct StrFmtArgList *args) { args->nbArgs = 0; args->capacity = INITIAL_STRFMT_ARG_SIZE; args->args = malloc(args->capacity * sizeof(*args->args)); if (!args->args) fatalerror("Failed to allocate memory for STRFMT arg list: %s\n", strerror(errno)); } static size_t nextStrFmtArgListIndex(struct StrFmtArgList *args) { if (args->nbArgs == args->capacity) { args->capacity = (args->capacity + 1) * 2; args->args = realloc(args->args, args->capacity * sizeof(*args->args)); if (!args->args) fatalerror("realloc error while resizing STRFMT arg list: %s\n", strerror(errno)); } return args->nbArgs++; } static void freeStrFmtArgList(struct StrFmtArgList *args) { free(args->format); for (size_t i = 0; i < args->nbArgs; i++) if (!args->args[i].isNumeric) free(args->args[i].string); free(args->args); } static void strfmt(char *dest, size_t destLen, char const *fmt, size_t nbArgs, struct StrFmtArg *args) { size_t a = 0; size_t i = 0; while (i < destLen) { int c = *fmt++; if (c == '\0') { break; } else if (c != '%') { dest[i++] = c; continue; } c = *fmt++; if (c == '%') { dest[i++] = c; continue; } struct FormatSpec spec = fmt_NewSpec(); while (c != '\0') { fmt_UseCharacter(&spec, c); if (fmt_IsFinished(&spec)) break; c = *fmt++; } if (fmt_IsEmpty(&spec)) { error("STRFMT: Illegal '%%' at end of format string\n"); dest[i++] = '%'; break; } else if (!fmt_IsValid(&spec)) { error("STRFMT: Invalid format spec for argument %zu\n", a + 1); dest[i++] = '%'; a++; continue; } else if (a >= nbArgs) { // Will warn after formatting is done. dest[i++] = '%'; a++; continue; } struct StrFmtArg *arg = &args[a++]; static char buf[MAXSTRLEN + 1]; if (arg->isNumeric) fmt_PrintNumber(buf, sizeof(buf), &spec, arg->number); else fmt_PrintString(buf, sizeof(buf), &spec, arg->string); i += snprintf(&dest[i], destLen - i, "%s", buf); } if (a < nbArgs) error("STRFMT: %zu unformatted argument(s)\n", nbArgs - a); else if (a > nbArgs) error("STRFMT: Not enough arguments for format spec, got: %zu, need: %zu\n", nbArgs, a); if (i > destLen - 1) { warning(WARNING_LONG_STR, "STRFMT: String too long, got truncated\n"); i = destLen - 1; } dest[i] = '\0'; } static void compoundAssignment(const char *symName, enum RPNCommand op, int32_t constValue) { struct Expression oldExpr, constExpr, newExpr; int32_t newValue; rpn_Symbol(&oldExpr, symName); rpn_Number(&constExpr, constValue); rpn_BinaryOp(op, &newExpr, &oldExpr, &constExpr); newValue = rpn_GetConstVal(&newExpr); sym_AddVar(symName, newValue); } static void initDsArgList(struct DsArgList *args) { args->nbArgs = 0; args->capacity = INITIAL_DS_ARG_SIZE; args->args = malloc(args->capacity * sizeof(*args->args)); if (!args->args) fatalerror("Failed to allocate memory for ds arg list: %s\n", strerror(errno)); } static void appendDsArgList(struct DsArgList *args, const struct Expression *expr) { if (args->nbArgs == args->capacity) { args->capacity = (args->capacity + 1) * 2; args->args = realloc(args->args, args->capacity * sizeof(*args->args)); if (!args->args) fatalerror("realloc error while resizing ds arg list: %s\n", strerror(errno)); } args->args[args->nbArgs++] = *expr; } static void freeDsArgList(struct DsArgList *args) { free(args->args); } static void failAssert(enum AssertionType type) { switch (type) { case ASSERT_FATAL: fatalerror("Assertion failed\n"); case ASSERT_ERROR: error("Assertion failed\n"); break; case ASSERT_WARN: warning(WARNING_ASSERT, "Assertion failed\n"); break; } } static void failAssertMsg(enum AssertionType type, char const *msg) { switch (type) { case ASSERT_FATAL: fatalerror("Assertion failed: %s\n", msg); case ASSERT_ERROR: error("Assertion failed: %s\n", msg); break; case ASSERT_WARN: warning(WARNING_ASSERT, "Assertion failed: %s\n", msg); break; } } void yyerror(char const *str) { error("%s\n", str); } // The CPU encodes instructions in a logical way, so most instructions actually follow patterns. // These enums thus help with bit twiddling to compute opcodes enum { REG_B = 0, REG_C, REG_D, REG_E, REG_H, REG_L, REG_HL_IND, REG_A }; enum { REG_BC_IND = 0, REG_DE_IND, REG_HL_INDINC, REG_HL_INDDEC, }; enum { REG_BC = 0, REG_DE = 1, REG_HL = 2, // LD/INC/ADD/DEC allow SP, PUSH/POP allow AF REG_SP = 3, REG_AF = 3 }; enum { CC_NZ = 0, CC_Z, CC_NC, CC_C }; %} %union { char symName[MAXSYMLEN + 1]; char string[MAXSTRLEN + 1]; struct Expression expr; int32_t constValue; enum RPNCommand compoundEqual; enum SectionModifier sectMod; struct SectionSpec sectSpec; struct MacroArgs *macroArg; enum AssertionType assertType; struct DsArgList dsArgs; struct { int32_t start; int32_t stop; int32_t step; } forArgs; struct StrFmtArgList strfmtArgs; bool captureTerminated; } %type <expr> relocexpr %type <expr> relocexpr_no_str %type <constValue> const %type <constValue> const_no_str %type <constValue> const_8bit %type <constValue> uconst %type <constValue> rs_uconst %type <constValue> const_3bit %type <expr> reloc_8bit %type <expr> reloc_8bit_no_str %type <expr> reloc_8bit_offset %type <expr> reloc_16bit %type <expr> reloc_16bit_no_str %type <constValue> sectiontype %type <string> string %type <string> strcat_args %type <strfmtArgs> strfmt_args %type <strfmtArgs> strfmt_va_args %type <constValue> sectorg %type <sectSpec> sectattrs %token <constValue> T_NUMBER "number" %token <string> T_STRING "string" %token T_PERIOD "." %token T_COMMA "," %token T_COLON ":" %token T_LBRACK "[" T_RBRACK "]" %token T_LPAREN "(" T_RPAREN ")" %token T_NEWLINE "newline" %token T_OP_LOGICNOT "!" %token T_OP_LOGICAND "&&" T_OP_LOGICOR "||" %token T_OP_LOGICGT ">" T_OP_LOGICLT "<" %token T_OP_LOGICGE ">=" T_OP_LOGICLE "<=" %token T_OP_LOGICNE "!=" T_OP_LOGICEQU "==" %token T_OP_ADD "+" T_OP_SUB "-" %token T_OP_OR "|" T_OP_XOR "^" T_OP_AND "&" %token T_OP_SHL "<<" T_OP_SHR ">>" T_OP_USHR ">>>" %token T_OP_MUL "*" T_OP_DIV "/" T_OP_MOD "%" %token T_OP_NOT "~" %left T_OP_LOGICOR %left T_OP_LOGICAND %left T_OP_LOGICGT T_OP_LOGICLT T_OP_LOGICGE T_OP_LOGICLE T_OP_LOGICNE T_OP_LOGICEQU %left T_OP_ADD T_OP_SUB %left T_OP_OR T_OP_XOR T_OP_AND %left T_OP_SHL T_OP_SHR T_OP_USHR %left T_OP_MUL T_OP_DIV T_OP_MOD %precedence NEG // negation -- unary minus %token T_OP_EXP "**" %left T_OP_EXP %token T_OP_DEF "DEF" %token T_OP_BANK "BANK" %token T_OP_ALIGN "ALIGN" %token T_OP_SIZEOF "SIZEOF" T_OP_STARTOF "STARTOF" %token T_OP_SIN "SIN" T_OP_COS "COS" T_OP_TAN "TAN" %token T_OP_ASIN "ASIN" T_OP_ACOS "ACOS" T_OP_ATAN "ATAN" T_OP_ATAN2 "ATAN2" %token T_OP_FDIV "FDIV" %token T_OP_FMUL "FMUL" %token T_OP_FMOD "FMOD" %token T_OP_POW "POW" %token T_OP_LOG "LOG" %token T_OP_ROUND "ROUND" %token T_OP_CEIL "CEIL" T_OP_FLOOR "FLOOR" %token T_OP_HIGH "HIGH" T_OP_LOW "LOW" %token T_OP_ISCONST "ISCONST" %token T_OP_STRCMP "STRCMP" %token T_OP_STRIN "STRIN" T_OP_STRRIN "STRRIN" %token T_OP_STRSUB "STRSUB" %token T_OP_STRLEN "STRLEN" %token T_OP_STRCAT "STRCAT" %token T_OP_STRUPR "STRUPR" T_OP_STRLWR "STRLWR" %token T_OP_STRRPL "STRRPL" %token T_OP_STRFMT "STRFMT" %token T_OP_CHARLEN "CHARLEN" %token T_OP_CHARSUB "CHARSUB" %token <symName> T_LABEL "label" %token <symName> T_ID "identifier" %token <symName> T_LOCAL_ID "local identifier" %token <symName> T_ANON "anonymous label" %type <symName> def_id %type <symName> redef_id %type <symName> scoped_id %type <symName> scoped_anon_id %token T_POP_EQU "EQU" %token T_POP_EQUAL "=" %token T_POP_EQUS "EQUS" %token T_POP_ADDEQ "+=" T_POP_SUBEQ "-=" %token T_POP_MULEQ "*=" T_POP_DIVEQ "/=" T_POP_MODEQ "%=" %token T_POP_OREQ "|=" T_POP_XOREQ "^=" T_POP_ANDEQ "&=" %token T_POP_SHLEQ "<<=" T_POP_SHREQ ">>=" %type <compoundEqual> compoundeq %token T_POP_INCLUDE "INCLUDE" %token T_POP_PRINT "PRINT" T_POP_PRINTLN "PRINTLN" %token T_POP_IF "IF" T_POP_ELIF "ELIF" T_POP_ELSE "ELSE" T_POP_ENDC "ENDC" %token T_POP_EXPORT "EXPORT" %token T_POP_DB "DB" T_POP_DS "DS" T_POP_DW "DW" T_POP_DL "DL" %token T_POP_SECTION "SECTION" T_POP_FRAGMENT "FRAGMENT" %token T_POP_RB "RB" T_POP_RW "RW" // There is no T_POP_RL, only T_Z80_RL %token T_POP_MACRO "MACRO" %token T_POP_ENDM "ENDM" %token T_POP_RSRESET "RSRESET" T_POP_RSSET "RSSET" %token T_POP_UNION "UNION" T_POP_NEXTU "NEXTU" T_POP_ENDU "ENDU" %token T_POP_INCBIN "INCBIN" T_POP_REPT "REPT" T_POP_FOR "FOR" %token T_POP_CHARMAP "CHARMAP" %token T_POP_NEWCHARMAP "NEWCHARMAP" %token T_POP_SETCHARMAP "SETCHARMAP" %token T_POP_PUSHC "PUSHC" %token T_POP_POPC "POPC" %token T_POP_SHIFT "SHIFT" %token T_POP_ENDR "ENDR" %token T_POP_BREAK "BREAK" %token T_POP_LOAD "LOAD" T_POP_ENDL "ENDL" %token T_POP_FAIL "FAIL" %token T_POP_WARN "WARN" %token T_POP_FATAL "FATAL" %token T_POP_ASSERT "ASSERT" T_POP_STATIC_ASSERT "STATIC_ASSERT" %token T_POP_PURGE "PURGE" %token T_POP_REDEF "REDEF" %token T_POP_POPS "POPS" %token T_POP_PUSHS "PUSHS" %token T_POP_POPO "POPO" %token T_POP_PUSHO "PUSHO" %token T_POP_OPT "OPT" %token T_SECT_ROM0 "ROM0" T_SECT_ROMX "ROMX" %token T_SECT_WRAM0 "WRAM0" T_SECT_WRAMX "WRAMX" T_SECT_HRAM "HRAM" %token T_SECT_VRAM "VRAM" T_SECT_SRAM "SRAM" T_SECT_OAM "OAM" %type <sectMod> sectmod %type <macroArg> macroargs %type <dsArgs> ds_args %type <forArgs> for_args %token T_Z80_ADC "adc" T_Z80_ADD "add" T_Z80_AND "and" %token T_Z80_BIT "bit" %token T_Z80_CALL "call" T_Z80_CCF "ccf" T_Z80_CP "cp" T_Z80_CPL "cpl" %token T_Z80_DAA "daa" T_Z80_DEC "dec" T_Z80_DI "di" %token T_Z80_EI "ei" %token T_Z80_HALT "halt" %token T_Z80_INC "inc" %token T_Z80_JP "jp" T_Z80_JR "jr" %token T_Z80_LD "ld" %token T_Z80_LDI "ldi" %token T_Z80_LDD "ldd" %token T_Z80_LDH "ldh" %token T_Z80_NOP "nop" %token T_Z80_OR "or" %token T_Z80_POP "pop" T_Z80_PUSH "push" %token T_Z80_RES "res" T_Z80_RET "ret" T_Z80_RETI "reti" T_Z80_RST "rst" %token T_Z80_RL "rl" T_Z80_RLA "rla" T_Z80_RLC "rlc" T_Z80_RLCA "rlca" %token T_Z80_RR "rr" T_Z80_RRA "rra" T_Z80_RRC "rrc" T_Z80_RRCA "rrca" %token T_Z80_SBC "sbc" T_Z80_SCF "scf" T_Z80_SET "set" T_Z80_STOP "stop" %token T_Z80_SLA "sla" T_Z80_SRA "sra" T_Z80_SRL "srl" T_Z80_SUB "sub" %token T_Z80_SWAP "swap" %token T_Z80_XOR "xor" %token T_TOKEN_A "a" %token T_TOKEN_B "b" T_TOKEN_C "c" %token T_TOKEN_D "d" T_TOKEN_E "e" %token T_TOKEN_H "h" T_TOKEN_L "l" %token T_MODE_AF "af" T_MODE_BC "bc" T_MODE_DE "de" T_MODE_SP "sp" %token T_MODE_HL "hl" T_MODE_HL_DEC "hld/hl-" T_MODE_HL_INC "hli/hl+" %token T_CC_NZ "nz" T_CC_Z "z" T_CC_NC "nc" // There is no T_CC_C, only T_TOKEN_C %type <constValue> reg_r %type <constValue> reg_ss %type <constValue> reg_rr %type <constValue> reg_tt %type <constValue> ccode_expr %type <constValue> ccode %type <expr> op_a_n %type <constValue> op_a_r %type <expr> op_mem_ind %type <assertType> assert_type %token T_EOB "end of buffer" %token T_EOF 0 "end of file" %start asmfile %% asmfile : lines ; lines : %empty | lines line ; endofline : T_NEWLINE | T_EOB ; plain_directive : label | label cpu_command | label macro | label directive | assignment_directive ; line : plain_directive endofline | line_directive // Directives that manage newlines themselves // Continue parsing the next line on a syntax error | error { lexer_SetMode(LEXER_NORMAL); lexer_ToggleStringExpansion(true); } endofline { fstk_StopRept(); yyerrok; } // Hint about unindented macros parsed as labels | T_LABEL error { lexer_SetMode(LEXER_NORMAL); lexer_ToggleStringExpansion(true); } endofline { struct Symbol *macro = sym_FindExactSymbol($1); if (macro && macro->type == SYM_MACRO) fprintf(stderr, " To invoke `%s` as a macro it must be indented\n", $1); fstk_StopRept(); yyerrok; } ; // For "logistical" reasons, these directives must manage newlines themselves. // This is because we need to switch the lexer's mode *after* the newline has been read, // and to avoid causing some grammar conflicts (token reducing is finicky). // This is DEFINITELY one of the more FRAGILE parts of the codebase, handle with care. line_directive : macrodef | rept | for | break | include | if // It's important that all of these require being at line start for `skipIfBlock` | elif | else ; if : T_POP_IF const T_NEWLINE { lexer_IncIFDepth(); if ($2) lexer_RunIFBlock(); else lexer_SetMode(LEXER_SKIP_TO_ELIF); } ; elif : T_POP_ELIF const T_NEWLINE { if (lexer_GetIFDepth() == 0) fatalerror("Found ELIF outside an IF construct\n"); if (lexer_RanIFBlock()) { if (lexer_ReachedELSEBlock()) fatalerror("Found ELIF after an ELSE block\n"); lexer_SetMode(LEXER_SKIP_TO_ENDC); } else if ($2) { lexer_RunIFBlock(); } else { lexer_SetMode(LEXER_SKIP_TO_ELIF); } } ; else : T_POP_ELSE T_NEWLINE { if (lexer_GetIFDepth() == 0) fatalerror("Found ELSE outside an IF construct\n"); if (lexer_RanIFBlock()) { if (lexer_ReachedELSEBlock()) fatalerror("Found ELSE after an ELSE block\n"); lexer_SetMode(LEXER_SKIP_TO_ENDC); } else { lexer_RunIFBlock(); lexer_ReachELSEBlock(); } } ; endc : T_POP_ENDC { lexer_DecIFDepth(); } ; def_id : T_OP_DEF { lexer_ToggleStringExpansion(false); } T_ID { lexer_ToggleStringExpansion(true); strcpy($$, $3); } ; redef_id : T_POP_REDEF { lexer_ToggleStringExpansion(false); } T_ID { lexer_ToggleStringExpansion(true); strcpy($$, $3); } ; scoped_id : T_ID | T_LOCAL_ID; scoped_anon_id : scoped_id | T_ANON; label : %empty | T_COLON { sym_AddAnonLabel(); } | T_LOCAL_ID { sym_AddLocalLabel($1); } | T_LOCAL_ID T_COLON { sym_AddLocalLabel($1); } | T_LABEL T_COLON { sym_AddLabel($1); } | T_LOCAL_ID T_COLON T_COLON { sym_AddLocalLabel($1); sym_Export($1); } | T_LABEL T_COLON T_COLON { sym_AddLabel($1); sym_Export($1); } ; macro : T_ID { // Parsing 'macroargs' will restore the lexer's normal mode lexer_SetMode(LEXER_RAW); } macroargs { fstk_RunMacro($1, $3); } ; macroargs : %empty { $$ = macro_NewArgs(); } | macroargs T_STRING { macro_AppendArg(&($$), strdup($2)); } ; // These commands start with a T_LABEL. assignment_directive : equ | assignment | rb | rw | rl | equs ; directive : endc | print | println | export | db | dw | dl | ds | section | rsreset | rsset | union | nextu | endu | incbin | charmap | newcharmap | setcharmap | pushc | popc | load | shift | fail | warn | assert | def_equ | redef_equ | def_set | def_rb | def_rw | def_rl | def_equs | redef_equs | purge | pops | pushs | popo | pusho | opt | align ; trailing_comma : %empty | T_COMMA ; compoundeq : T_POP_ADDEQ { $$ = RPN_ADD; } | T_POP_SUBEQ { $$ = RPN_SUB; } | T_POP_MULEQ { $$ = RPN_MUL; } | T_POP_DIVEQ { $$ = RPN_DIV; } | T_POP_MODEQ { $$ = RPN_MOD; } | T_POP_XOREQ { $$ = RPN_XOR; } | T_POP_OREQ { $$ = RPN_OR; } | T_POP_ANDEQ { $$ = RPN_AND; } | T_POP_SHLEQ { $$ = RPN_SHL; } | T_POP_SHREQ { $$ = RPN_SHR; } ; equ : T_LABEL T_POP_EQU const { sym_AddEqu($1, $3); } ; assignment : T_LABEL T_POP_EQUAL const { sym_AddVar($1, $3); } | T_LABEL compoundeq const { compoundAssignment($1, $2, $3); } ; equs : T_LABEL T_POP_EQUS string { sym_AddString($1, $3); } ; rb : T_LABEL T_POP_RB rs_uconst { sym_AddEqu($1, sym_GetConstantValue("_RS")); sym_AddVar("_RS", sym_GetConstantValue("_RS") + $3); } ; rw : T_LABEL T_POP_RW rs_uconst { sym_AddEqu($1, sym_GetConstantValue("_RS")); sym_AddVar("_RS", sym_GetConstantValue("_RS") + 2 * $3); } ; rl : T_LABEL T_Z80_RL rs_uconst { sym_AddEqu($1, sym_GetConstantValue("_RS")); sym_AddVar("_RS", sym_GetConstantValue("_RS") + 4 * $3); } ; align : T_OP_ALIGN uconst { if ($2 > 16) error("Alignment must be between 0 and 16, not %u\n", $2); else sect_AlignPC($2, 0); } | T_OP_ALIGN uconst T_COMMA uconst { if ($2 > 16) error("Alignment must be between 0 and 16, not %u\n", $2); else if ($4 >= 1 << $2) error("Offset must be between 0 and %u, not %u\n", (1 << $2) - 1, $4); else sect_AlignPC($2, $4); } ; opt : T_POP_OPT { // Parsing 'opt_list' will restore the lexer's normal mode lexer_SetMode(LEXER_RAW); } opt_list ; opt_list : opt_list_entry | opt_list opt_list_entry ; opt_list_entry : T_STRING { opt_Parse($1); } ; popo : T_POP_POPO { opt_Pop(); } ; pusho : T_POP_PUSHO { opt_Push(); } ; pops : T_POP_POPS { sect_PopSection(); } ; pushs : T_POP_PUSHS { sect_PushSection(); } ; fail : T_POP_FAIL string { fatalerror("%s\n", $2); } ; warn : T_POP_WARN string { warning(WARNING_USER, "%s\n", $2); } ; assert_type : %empty { $$ = ASSERT_ERROR; } | T_POP_WARN T_COMMA { $$ = ASSERT_WARN; } | T_POP_FAIL T_COMMA { $$ = ASSERT_ERROR; } | T_POP_FATAL T_COMMA { $$ = ASSERT_FATAL; } ; assert : T_POP_ASSERT assert_type relocexpr { if (!rpn_isKnown(&$3)) { if (!out_CreateAssert($2, &$3, "", sect_GetOutputOffset())) error("Assertion creation failed: %s\n", strerror(errno)); } else if ($3.val == 0) { failAssert($2); } rpn_Free(&$3); } | T_POP_ASSERT assert_type relocexpr T_COMMA string { if (!rpn_isKnown(&$3)) { if (!out_CreateAssert($2, &$3, $5, sect_GetOutputOffset())) error("Assertion creation failed: %s\n", strerror(errno)); } else if ($3.val == 0) { failAssertMsg($2, $5); } rpn_Free(&$3); } | T_POP_STATIC_ASSERT assert_type const { if ($3 == 0) failAssert($2); } | T_POP_STATIC_ASSERT assert_type const T_COMMA string { if ($3 == 0) failAssertMsg($2, $5); } ; shift : T_POP_SHIFT { macro_ShiftCurrentArgs(1); } | T_POP_SHIFT const { macro_ShiftCurrentArgs($2); } ; load : T_POP_LOAD sectmod string T_COMMA sectiontype sectorg sectattrs { sect_SetLoadSection($3, $5, $6, &$7, $2); } | T_POP_ENDL { sect_EndLoadSection(); } ; rept : T_POP_REPT uconst T_NEWLINE { $<captureTerminated>$ = lexer_CaptureRept(&captureBody); } endofline { if ($<captureTerminated>4) fstk_RunRept($2, captureBody.lineNo, captureBody.body, captureBody.size); } ; for : T_POP_FOR { lexer_ToggleStringExpansion(false); } T_ID { lexer_ToggleStringExpansion(true); } T_COMMA for_args T_NEWLINE { $<captureTerminated>$ = lexer_CaptureRept(&captureBody); } endofline { if ($<captureTerminated>8) fstk_RunFor($3, $6.start, $6.stop, $6.step, captureBody.lineNo, captureBody.body, captureBody.size); } for_args : const { $$.start = 0; $$.stop = $1; $$.step = 1; } | const T_COMMA const { $$.start = $1; $$.stop = $3; $$.step = 1; } | const T_COMMA const T_COMMA const { $$.start = $1; $$.stop = $3; $$.step = $5; } ; break : label T_POP_BREAK endofline { if (fstk_Break()) lexer_SetMode(LEXER_SKIP_TO_ENDR); } ; macrodef : T_POP_MACRO { lexer_ToggleStringExpansion(false); } T_ID { lexer_ToggleStringExpansion(true); } T_NEWLINE { $<captureTerminated>$ = lexer_CaptureMacroBody(&captureBody); } endofline { if ($<captureTerminated>6) sym_AddMacro($3, captureBody.lineNo, captureBody.body, captureBody.size); } | T_LABEL T_COLON T_POP_MACRO T_NEWLINE { warning(WARNING_OBSOLETE, "`%s: MACRO` is deprecated; use `MACRO %s`\n", $1, $1); $<captureTerminated>$ = lexer_CaptureMacroBody(&captureBody); } endofline { if ($<captureTerminated>5) sym_AddMacro($1, captureBody.lineNo, captureBody.body, captureBody.size); } ; rsset : T_POP_RSSET uconst { sym_AddVar("_RS", $2); } ; rsreset : T_POP_RSRESET { sym_AddVar("_RS", 0); } ; rs_uconst : %empty { $$ = 1; } | uconst ; union : T_POP_UNION { sect_StartUnion(); } ; nextu : T_POP_NEXTU { sect_NextUnionMember(); } ; endu : T_POP_ENDU { sect_EndUnion(); } ; ds : T_POP_DS uconst { sect_Skip($2, true); } | T_POP_DS uconst T_COMMA ds_args trailing_comma { sect_RelBytes($2, $4.args, $4.nbArgs); freeDsArgList(&$4); } ; ds_args : reloc_8bit { initDsArgList(&$$); appendDsArgList(&$$, &$1); } | ds_args T_COMMA reloc_8bit { appendDsArgList(&$1, &$3); $$ = $1; } ; db : T_POP_DB { sect_Skip(1, false); } | T_POP_DB constlist_8bit trailing_comma ; dw : T_POP_DW { sect_Skip(2, false); } | T_POP_DW constlist_16bit trailing_comma ; dl : T_POP_DL { sect_Skip(4, false); } | T_POP_DL constlist_32bit trailing_comma ; def_equ : def_id T_POP_EQU const { sym_AddEqu($1, $3); } ; redef_equ : redef_id T_POP_EQU const { sym_RedefEqu($1, $3); } ; def_set : def_id T_POP_EQUAL const { sym_AddVar($1, $3); } | redef_id T_POP_EQUAL const { sym_AddVar($1, $3); } | def_id compoundeq const { compoundAssignment($1, $2, $3); } | redef_id compoundeq const { compoundAssignment($1, $2, $3); } ; def_rb : def_id T_POP_RB rs_uconst { sym_AddEqu($1, sym_GetConstantValue("_RS")); sym_AddVar("_RS", sym_GetConstantValue("_RS") + $3); } ; def_rw : def_id T_POP_RW rs_uconst { sym_AddEqu($1, sym_GetConstantValue("_RS")); sym_AddVar("_RS", sym_GetConstantValue("_RS") + 2 * $3); } ; def_rl : def_id T_Z80_RL rs_uconst { sym_AddEqu($1, sym_GetConstantValue("_RS")); sym_AddVar("_RS", sym_GetConstantValue("_RS") + 4 * $3); } ; def_equs : def_id T_POP_EQUS string { sym_AddString($1, $3); } ; redef_equs : redef_id T_POP_EQUS string { sym_RedefString($1, $3); } ; purge : T_POP_PURGE { lexer_ToggleStringExpansion(false); } purge_list trailing_comma { lexer_ToggleStringExpansion(true); } ; purge_list : purge_list_entry | purge_list T_COMMA purge_list_entry ; purge_list_entry : scoped_id { sym_Purge($1); } ; export : T_POP_EXPORT export_list trailing_comma ; export_list : export_list_entry | export_list T_COMMA export_list_entry ; export_list_entry : scoped_id { sym_Export($1); } ; include : label T_POP_INCLUDE string endofline { fstk_RunInclude($3); if (failedOnMissingInclude) YYACCEPT; } ; incbin : T_POP_INCBIN string { sect_BinaryFile($2, 0); if (failedOnMissingInclude) YYACCEPT; } | T_POP_INCBIN string T_COMMA const { sect_BinaryFile($2, $4); if (failedOnMissingInclude) YYACCEPT; } | T_POP_INCBIN string T_COMMA const T_COMMA const { sect_BinaryFileSlice($2, $4, $6); if (failedOnMissingInclude) YYACCEPT; } ; charmap : T_POP_CHARMAP string T_COMMA const_8bit { charmap_Add($2, (uint8_t)$4); } ; newcharmap : T_POP_NEWCHARMAP T_ID { charmap_New($2, NULL); } | T_POP_NEWCHARMAP T_ID T_COMMA T_ID { charmap_New($2, $4); } ; setcharmap : T_POP_SETCHARMAP T_ID { charmap_Set($2); } ; pushc : T_POP_PUSHC { charmap_Push(); } ; popc : T_POP_POPC { charmap_Pop(); } ; print : T_POP_PRINT print_exprs trailing_comma ; println : T_POP_PRINTLN { putchar('\n'); fflush(stdout); } | T_POP_PRINTLN print_exprs trailing_comma { putchar('\n'); fflush(stdout); } ; print_exprs : print_expr | print_exprs T_COMMA print_expr ; print_expr : const_no_str { printf("$%" PRIX32, $1); } | string { printf("%s", $1); } ; const_3bit : const { int32_t value = $1; if ((value < 0) || (value > 7)) { error("Immediate value must be 3-bit\n"); $$ = 0; } else { $$ = value & 0x7; } } ; constlist_8bit : constlist_8bit_entry | constlist_8bit T_COMMA constlist_8bit_entry ; constlist_8bit_entry : reloc_8bit_no_str { sect_RelByte(&$1, 0); } | string { uint8_t *output = malloc(strlen($1)); // Cannot be larger than that size_t length = charmap_Convert($1, output); sect_AbsByteGroup(output, length); free(output); } ; constlist_16bit : constlist_16bit_entry | constlist_16bit T_COMMA constlist_16bit_entry ; constlist_16bit_entry : reloc_16bit_no_str { sect_RelWord(&$1, 0); } | string { uint8_t *output = malloc(strlen($1)); // Cannot be larger than that size_t length = charmap_Convert($1, output); sect_AbsWordGroup(output, length); free(output); } ; constlist_32bit : constlist_32bit_entry | constlist_32bit T_COMMA constlist_32bit_entry ; constlist_32bit_entry : relocexpr_no_str { sect_RelLong(&$1, 0); } | string { // Charmaps cannot increase the length of a string uint8_t *output = malloc(strlen($1)); size_t length = charmap_Convert($1, output); sect_AbsLongGroup(output, length); free(output); } ; reloc_8bit : relocexpr { rpn_CheckNBit(&$1, 8); $$ = $1; } ; reloc_8bit_no_str : relocexpr_no_str { rpn_CheckNBit(&$1, 8); $$ = $1; } ; reloc_8bit_offset : T_OP_ADD relocexpr { rpn_CheckNBit(&$2, 8); $$ = $2; } | T_OP_SUB relocexpr { rpn_UNNEG(&$$, &$2); rpn_CheckNBit(&$$, 8); } ; reloc_16bit : relocexpr { rpn_CheckNBit(&$1, 16); $$ = $1; } ; reloc_16bit_no_str : relocexpr_no_str { rpn_CheckNBit(&$1, 16); $$ = $1; } ; relocexpr : relocexpr_no_str | string { // Charmaps cannot increase the length of a string uint8_t *output = malloc(strlen($1)); uint32_t length = charmap_Convert($1, output); uint32_t r = str2int2(output, length); free(output); rpn_Number(&$$, r); } ; relocexpr_no_str : scoped_anon_id { rpn_Symbol(&$$, $1); } | T_NUMBER { rpn_Number(&$$, $1); } | T_OP_LOGICNOT relocexpr %prec NEG { rpn_LOGNOT(&$$, &$2); } | relocexpr T_OP_LOGICOR relocexpr { rpn_BinaryOp(RPN_LOGOR, &$$, &$1, &$3); } | relocexpr T_OP_LOGICAND relocexpr { rpn_BinaryOp(RPN_LOGAND, &$$, &$1, &$3); } | relocexpr T_OP_LOGICEQU relocexpr { rpn_BinaryOp(RPN_LOGEQ, &$$, &$1, &$3); } | relocexpr T_OP_LOGICGT relocexpr { rpn_BinaryOp(RPN_LOGGT, &$$, &$1, &$3); } | relocexpr T_OP_LOGICLT relocexpr { rpn_BinaryOp(RPN_LOGLT, &$$, &$1, &$3); } | relocexpr T_OP_LOGICGE relocexpr { rpn_BinaryOp(RPN_LOGGE, &$$, &$1, &$3); } | relocexpr T_OP_LOGICLE relocexpr { rpn_BinaryOp(RPN_LOGLE, &$$, &$1, &$3); } | relocexpr T_OP_LOGICNE relocexpr { rpn_BinaryOp(RPN_LOGNE, &$$, &$1, &$3); } | relocexpr T_OP_ADD relocexpr { rpn_BinaryOp(RPN_ADD, &$$, &$1, &$3); } | relocexpr T_OP_SUB relocexpr { rpn_BinaryOp(RPN_SUB, &$$, &$1, &$3); } | relocexpr T_OP_XOR relocexpr { rpn_BinaryOp(RPN_XOR, &$$, &$1, &$3); } | relocexpr T_OP_OR relocexpr { rpn_BinaryOp(RPN_OR, &$$, &$1, &$3); } | relocexpr T_OP_AND relocexpr { rpn_BinaryOp(RPN_AND, &$$, &$1, &$3); } | relocexpr T_OP_SHL relocexpr { rpn_BinaryOp(RPN_SHL, &$$, &$1, &$3); } | relocexpr T_OP_SHR relocexpr { rpn_BinaryOp(RPN_SHR, &$$, &$1, &$3); } | relocexpr T_OP_USHR relocexpr { rpn_BinaryOp(RPN_USHR, &$$, &$1, &$3); } | relocexpr T_OP_MUL relocexpr { rpn_BinaryOp(RPN_MUL, &$$, &$1, &$3); } | relocexpr T_OP_DIV relocexpr { rpn_BinaryOp(RPN_DIV, &$$, &$1, &$3); } | relocexpr T_OP_MOD relocexpr { rpn_BinaryOp(RPN_MOD, &$$, &$1, &$3); } | relocexpr T_OP_EXP relocexpr { rpn_BinaryOp(RPN_EXP, &$$, &$1, &$3); } | T_OP_ADD relocexpr %prec NEG { $$ = $2; } | T_OP_SUB relocexpr %prec NEG { rpn_UNNEG(&$$, &$2); } | T_OP_NOT relocexpr %prec NEG { rpn_UNNOT(&$$, &$2); } | T_OP_HIGH T_LPAREN relocexpr T_RPAREN { rpn_HIGH(&$$, &$3); } | T_OP_LOW T_LPAREN relocexpr T_RPAREN { rpn_LOW(&$$, &$3); } | T_OP_ISCONST T_LPAREN relocexpr T_RPAREN { rpn_ISCONST(&$$, &$3); } | T_OP_BANK T_LPAREN scoped_anon_id T_RPAREN { // '@' is also a T_ID; it is handled here rpn_BankSymbol(&$$, $3); } | T_OP_BANK T_LPAREN string T_RPAREN { rpn_BankSection(&$$, $3); } | T_OP_SIZEOF T_LPAREN string T_RPAREN { rpn_SizeOfSection(&$$, $3); } | T_OP_STARTOF T_LPAREN string T_RPAREN { rpn_StartOfSection(&$$, $3); } | T_OP_DEF { lexer_ToggleStringExpansion(false); } T_LPAREN scoped_anon_id T_RPAREN { rpn_Number(&$$, sym_FindScopedValidSymbol($4) != NULL); lexer_ToggleStringExpansion(true); } | T_OP_ROUND T_LPAREN const T_RPAREN { rpn_Number(&$$, fix_Round($3)); } | T_OP_CEIL T_LPAREN const T_RPAREN { rpn_Number(&$$, fix_Ceil($3)); } | T_OP_FLOOR T_LPAREN const T_RPAREN { rpn_Number(&$$, fix_Floor($3)); } | T_OP_FDIV T_LPAREN const T_COMMA const T_RPAREN { rpn_Number(&$$, fix_Div($3, $5)); } | T_OP_FMUL T_LPAREN const T_COMMA const T_RPAREN { rpn_Number(&$$, fix_Mul($3, $5)); } | T_OP_FMOD T_LPAREN const T_COMMA const T_RPAREN { rpn_Number(&$$, fix_Mod($3, $5)); } | T_OP_POW T_LPAREN const T_COMMA const T_RPAREN { rpn_Number(&$$, fix_Pow($3, $5)); } | T_OP_LOG T_LPAREN const T_COMMA const T_RPAREN { rpn_Number(&$$, fix_Log($3, $5)); } | T_OP_SIN T_LPAREN const T_RPAREN { rpn_Number(&$$, fix_Sin($3)); } | T_OP_COS T_LPAREN const T_RPAREN { rpn_Number(&$$, fix_Cos($3)); } | T_OP_TAN T_LPAREN const T_RPAREN { rpn_Number(&$$, fix_Tan($3)); } | T_OP_ASIN T_LPAREN const T_RPAREN { rpn_Number(&$$, fix_ASin($3)); } | T_OP_ACOS T_LPAREN const T_RPAREN { rpn_Number(&$$, fix_ACos($3)); } | T_OP_ATAN T_LPAREN const T_RPAREN { rpn_Number(&$$, fix_ATan($3)); } | T_OP_ATAN2 T_LPAREN const T_COMMA const T_RPAREN { rpn_Number(&$$, fix_ATan2($3, $5)); } | T_OP_STRCMP T_LPAREN string T_COMMA string T_RPAREN { rpn_Number(&$$, strcmp($3, $5)); } | T_OP_STRIN T_LPAREN string T_COMMA string T_RPAREN { char const *p = strstr($3, $5); rpn_Number(&$$, p ? p - $3 + 1 : 0); } | T_OP_STRRIN T_LPAREN string T_COMMA string T_RPAREN { char const *p = strrstr($3, $5); rpn_Number(&$$, p ? p - $3 + 1 : 0); } | T_OP_STRLEN T_LPAREN string T_RPAREN { rpn_Number(&$$, strlenUTF8($3)); } | T_OP_CHARLEN T_LPAREN string T_RPAREN { rpn_Number(&$$, charlenUTF8($3)); } | T_LPAREN relocexpr T_RPAREN { $$ = $2; } ; uconst : const { $$ = $1; if ($$ < 0) fatalerror("Constant mustn't be negative: %d\n", $1); } ; const : relocexpr { $$ = rpn_GetConstVal(&$1); } ; const_no_str : relocexpr_no_str { $$ = rpn_GetConstVal(&$1); } ; const_8bit : reloc_8bit { $$ = rpn_GetConstVal(&$1); } ; string : T_STRING | T_OP_STRSUB T_LPAREN string T_COMMA const T_COMMA uconst T_RPAREN { size_t len = strlenUTF8($3); uint32_t pos = adjustNegativePos($5, len, "STRSUB"); strsubUTF8($$, sizeof($$), $3, pos, $7); } | T_OP_STRSUB T_LPAREN string T_COMMA const T_RPAREN { size_t len = strlenUTF8($3); uint32_t pos = adjustNegativePos($5, len, "STRSUB"); strsubUTF8($$, sizeof($$), $3, pos, pos > len ? 0 : len + 1 - pos); } | T_OP_CHARSUB T_LPAREN string T_COMMA const T_RPAREN { size_t len = charlenUTF8($3); uint32_t pos = adjustNegativePos($5, len, "CHARSUB"); charsubUTF8($$, $3, pos); } | T_OP_STRCAT T_LPAREN T_RPAREN { $$[0] = '\0'; } | T_OP_STRCAT T_LPAREN strcat_args T_RPAREN { strcpy($$, $3); } | T_OP_STRUPR T_LPAREN string T_RPAREN { upperstring($$, $3); } | T_OP_STRLWR T_LPAREN string T_RPAREN { lowerstring($$, $3); } | T_OP_STRRPL T_LPAREN string T_COMMA string T_COMMA string T_RPAREN { strrpl($$, sizeof($$), $3, $5, $7); } | T_OP_STRFMT T_LPAREN strfmt_args T_RPAREN { strfmt($$, sizeof($$), $3.format, $3.nbArgs, $3.args); freeStrFmtArgList(&$3); } | T_POP_SECTION T_LPAREN scoped_anon_id T_RPAREN { struct Symbol *sym = sym_FindScopedValidSymbol($3); if (!sym) fatalerror("Unknown symbol \"%s\"\n", $3); struct Section const *section = sym_GetSection(sym); if (!section) fatalerror("\"%s\" does not belong to any section\n", sym->name); // Section names are capped by rgbasm's maximum string length, // so this currently can't overflow. strcpy($$, section->name); } ; strcat_args : string | strcat_args T_COMMA string { int ret = snprintf($$, sizeof($$), "%s%s", $1, $3); if (ret == -1) fatalerror("snprintf error in STRCAT: %s\n", strerror(errno)); else if ((unsigned int)ret >= sizeof($$)) warning(WARNING_LONG_STR, "STRCAT: String too long '%s%s'\n", $1, $3); } ; strfmt_args : string strfmt_va_args { $$.format = strdup($1); $$.capacity = $2.capacity; $$.nbArgs = $2.nbArgs; $$.args = $2.args; } ; strfmt_va_args : %empty { initStrFmtArgList(&$$); } | strfmt_va_args T_COMMA const_no_str { size_t i = nextStrFmtArgListIndex(&$1); $1.args[i].number = $3; $1.args[i].isNumeric = true; $$ = $1; } | strfmt_va_args T_COMMA string { size_t i = nextStrFmtArgListIndex(&$1); $1.args[i].string = strdup($3); $1.args[i].isNumeric = false; $$ = $1; } ; section : T_POP_SECTION sectmod string T_COMMA sectiontype sectorg sectattrs { sect_NewSection($3, $5, $6, &$7, $2); } ; sectmod : %empty { $$ = SECTION_NORMAL; } | T_POP_UNION { $$ = SECTION_UNION; } | T_POP_FRAGMENT { $$ = SECTION_FRAGMENT; } ; sectiontype : T_SECT_WRAM0 { $$ = SECTTYPE_WRAM0; } | T_SECT_VRAM { $$ = SECTTYPE_VRAM; } | T_SECT_ROMX { $$ = SECTTYPE_ROMX; } | T_SECT_ROM0 { $$ = SECTTYPE_ROM0; } | T_SECT_HRAM { $$ = SECTTYPE_HRAM; } | T_SECT_WRAMX { $$ = SECTTYPE_WRAMX; } | T_SECT_SRAM { $$ = SECTTYPE_SRAM; } | T_SECT_OAM { $$ = SECTTYPE_OAM; } ; sectorg : %empty { $$ = -1; } | T_LBRACK uconst T_RBRACK { if ($2 < 0 || $2 >= 0x10000) { error("Address $%x is not 16-bit\n", $2); $$ = -1; } else { $$ = $2; } } ; sectattrs : %empty { $$.alignment = 0; $$.alignOfs = 0; $$.bank = -1; } | sectattrs T_COMMA T_OP_ALIGN T_LBRACK uconst T_RBRACK { $$.alignment = $5; } | sectattrs T_COMMA T_OP_ALIGN T_LBRACK uconst T_COMMA uconst T_RBRACK { $$.alignment = $5; $$.alignOfs = $7; } | sectattrs T_COMMA T_OP_BANK T_LBRACK uconst T_RBRACK { // We cannot check the validity of this now $$.bank = $5; } ; cpu_command : z80_adc | z80_add | z80_and | z80_bit | z80_call | z80_ccf | z80_cp | z80_cpl | z80_daa | z80_dec | z80_di | z80_ei | z80_halt | z80_inc | z80_jp | z80_jr | z80_ld | z80_ldd | z80_ldi | z80_ldio | z80_nop | z80_or | z80_pop | z80_push | z80_res | z80_ret | z80_reti | z80_rl | z80_rla | z80_rlc | z80_rlca | z80_rr | z80_rra | z80_rrc | z80_rrca | z80_rst | z80_sbc | z80_scf | z80_set | z80_sla | z80_sra | z80_srl | z80_stop | z80_sub | z80_swap | z80_xor ; z80_adc : T_Z80_ADC op_a_n { sect_AbsByte(0xCE); sect_RelByte(&$2, 1); } | T_Z80_ADC op_a_r { sect_AbsByte(0x88 | $2); } ; z80_add : T_Z80_ADD op_a_n { sect_AbsByte(0xC6); sect_RelByte(&$2, 1); } | T_Z80_ADD op_a_r { sect_AbsByte(0x80 | $2); } | T_Z80_ADD T_MODE_HL T_COMMA reg_ss { sect_AbsByte(0x09 | ($4 << 4)); } | T_Z80_ADD T_MODE_SP T_COMMA reloc_8bit { sect_AbsByte(0xE8); sect_RelByte(&$4, 1); } ; z80_and : T_Z80_AND op_a_n { sect_AbsByte(0xE6); sect_RelByte(&$2, 1); } | T_Z80_AND op_a_r { sect_AbsByte(0xA0 | $2); } ; z80_bit : T_Z80_BIT const_3bit T_COMMA reg_r { sect_AbsByte(0xCB); sect_AbsByte(0x40 | ($2 << 3) | $4); } ; z80_call : T_Z80_CALL reloc_16bit { sect_AbsByte(0xCD); sect_RelWord(&$2, 1); } | T_Z80_CALL ccode_expr T_COMMA reloc_16bit { sect_AbsByte(0xC4 | ($2 << 3)); sect_RelWord(&$4, 1); } ; z80_ccf : T_Z80_CCF { sect_AbsByte(0x3F); } ; z80_cp : T_Z80_CP op_a_n { sect_AbsByte(0xFE); sect_RelByte(&$2, 1); } | T_Z80_CP op_a_r { sect_AbsByte(0xB8 | $2); } ; z80_cpl : T_Z80_CPL { sect_AbsByte(0x2F); } ; z80_daa : T_Z80_DAA { sect_AbsByte(0x27); } ; z80_dec : T_Z80_DEC reg_r { sect_AbsByte(0x05 | ($2 << 3)); } | T_Z80_DEC reg_ss { sect_AbsByte(0x0B | ($2 << 4)); } ; z80_di : T_Z80_DI { sect_AbsByte(0xF3); } ; z80_ei : T_Z80_EI { sect_AbsByte(0xFB); } ; z80_halt : T_Z80_HALT { sect_AbsByte(0x76); if (haltnop) { if (warnOnHaltNop) { warnOnHaltNop = false; warning(WARNING_OBSOLETE, "`nop` after `halt` will stop being the default; pass `-H` to opt into it\n"); } sect_AbsByte(0x00); } } ; z80_inc : T_Z80_INC reg_r { sect_AbsByte(0x04 | ($2 << 3)); } | T_Z80_INC reg_ss { sect_AbsByte(0x03 | ($2 << 4)); } ; z80_jp : T_Z80_JP reloc_16bit { sect_AbsByte(0xC3); sect_RelWord(&$2, 1); } | T_Z80_JP ccode_expr T_COMMA reloc_16bit { sect_AbsByte(0xC2 | ($2 << 3)); sect_RelWord(&$4, 1); } | T_Z80_JP T_MODE_HL { sect_AbsByte(0xE9); } ; z80_jr : T_Z80_JR reloc_16bit { sect_AbsByte(0x18); sect_PCRelByte(&$2, 1); } | T_Z80_JR ccode_expr T_COMMA reloc_16bit { sect_AbsByte(0x20 | ($2 << 3)); sect_PCRelByte(&$4, 1); } ; z80_ldi : T_Z80_LDI T_LBRACK T_MODE_HL T_RBRACK T_COMMA T_MODE_A { sect_AbsByte(0x02 | (2 << 4)); } | T_Z80_LDI T_MODE_A T_COMMA T_LBRACK T_MODE_HL T_RBRACK { sect_AbsByte(0x0A | (2 << 4)); } ; z80_ldd : T_Z80_LDD T_LBRACK T_MODE_HL T_RBRACK T_COMMA T_MODE_A { sect_AbsByte(0x02 | (3 << 4)); } | T_Z80_LDD T_MODE_A T_COMMA T_LBRACK T_MODE_HL T_RBRACK { sect_AbsByte(0x0A | (3 << 4)); } ; z80_ldio : T_Z80_LDH T_MODE_A T_COMMA op_mem_ind { rpn_CheckHRAM(&$4, &$4); sect_AbsByte(0xF0); sect_RelByte(&$4, 1); } | T_Z80_LDH op_mem_ind T_COMMA T_MODE_A { rpn_CheckHRAM(&$2, &$2); sect_AbsByte(0xE0); sect_RelByte(&$2, 1); } | T_Z80_LDH T_MODE_A T_COMMA c_ind { sect_AbsByte(0xF2); } | T_Z80_LDH c_ind T_COMMA T_MODE_A { sect_AbsByte(0xE2); } ; c_ind : T_LBRACK T_MODE_C T_RBRACK | T_LBRACK relocexpr T_OP_ADD T_MODE_C T_RBRACK { if (!rpn_isKnown(&$2) || $2.val != 0xff00) error("Expected constant expression equal to $FF00 for \"$ff00+c\"\n"); } ; z80_ld : z80_ld_mem | z80_ld_cind | z80_ld_rr | z80_ld_ss | z80_ld_hl | z80_ld_sp | z80_ld_r | z80_ld_a ; z80_ld_hl : T_Z80_LD T_MODE_HL T_COMMA T_MODE_SP reloc_8bit_offset { sect_AbsByte(0xF8); sect_RelByte(&$5, 1); } | T_Z80_LD T_MODE_HL T_COMMA reloc_16bit { sect_AbsByte(0x01 | (REG_HL << 4)); sect_RelWord(&$4, 1); } ; z80_ld_sp : T_Z80_LD T_MODE_SP T_COMMA T_MODE_HL { sect_AbsByte(0xF9); } | T_Z80_LD T_MODE_SP T_COMMA reloc_16bit { sect_AbsByte(0x01 | (REG_SP << 4)); sect_RelWord(&$4, 1); } ; z80_ld_mem : T_Z80_LD op_mem_ind T_COMMA T_MODE_SP { sect_AbsByte(0x08); sect_RelWord(&$2, 1); } | T_Z80_LD op_mem_ind T_COMMA T_MODE_A { if (optimizeLoads && rpn_isKnown(&$2) && $2.val >= 0xFF00) { if (warnOnLdOpt) { warnOnLdOpt = false; warning(WARNING_OBSOLETE, "ld optimization will stop being the default; pass `-l` to opt into it\n"); } sect_AbsByte(0xE0); sect_AbsByte($2.val & 0xFF); rpn_Free(&$2); } else { sect_AbsByte(0xEA); sect_RelWord(&$2, 1); } } ; z80_ld_cind : T_Z80_LD c_ind T_COMMA T_MODE_A { sect_AbsByte(0xE2); } ; z80_ld_rr : T_Z80_LD reg_rr T_COMMA T_MODE_A { sect_AbsByte(0x02 | ($2 << 4)); } ; z80_ld_r : T_Z80_LD reg_r T_COMMA reloc_8bit { sect_AbsByte(0x06 | ($2 << 3)); sect_RelByte(&$4, 1); } | T_Z80_LD reg_r T_COMMA reg_r { if (($2 == REG_HL_IND) && ($4 == REG_HL_IND)) error("LD [HL],[HL] not a valid instruction\n"); else sect_AbsByte(0x40 | ($2 << 3) | $4); } ; z80_ld_a : T_Z80_LD reg_r T_COMMA c_ind { if ($2 == REG_A) sect_AbsByte(0xF2); else error("Destination operand must be A\n"); } | T_Z80_LD reg_r T_COMMA reg_rr { if ($2 == REG_A) sect_AbsByte(0x0A | ($4 << 4)); else error("Destination operand must be A\n"); } | T_Z80_LD reg_r T_COMMA op_mem_ind { if ($2 == REG_A) { if (optimizeLoads && rpn_isKnown(&$4) && $4.val >= 0xFF00) { if (warnOnLdOpt) { warnOnLdOpt = false; warning(WARNING_OBSOLETE, "ld optimization will stop being the default; pass `-l` to opt into it\n"); } sect_AbsByte(0xF0); sect_AbsByte($4.val & 0xFF); rpn_Free(&$4); } else { sect_AbsByte(0xFA); sect_RelWord(&$4, 1); } } else { error("Destination operand must be A\n"); rpn_Free(&$4); } } ; z80_ld_ss : T_Z80_LD T_MODE_BC T_COMMA reloc_16bit { sect_AbsByte(0x01 | (REG_BC << 4)); sect_RelWord(&$4, 1); } | T_Z80_LD T_MODE_DE T_COMMA reloc_16bit { sect_AbsByte(0x01 | (REG_DE << 4)); sect_RelWord(&$4, 1); } // HL is taken care of in z80_ld_hl // SP is taken care of in z80_ld_sp ; z80_nop : T_Z80_NOP { sect_AbsByte(0x00); } ; z80_or : T_Z80_OR op_a_n { sect_AbsByte(0xF6); sect_RelByte(&$2, 1); } | T_Z80_OR op_a_r { sect_AbsByte(0xB0 | $2); } ; z80_pop : T_Z80_POP reg_tt { sect_AbsByte(0xC1 | ($2 << 4)); } ; z80_push : T_Z80_PUSH reg_tt { sect_AbsByte(0xC5 | ($2 << 4)); } ; z80_res : T_Z80_RES const_3bit T_COMMA reg_r { sect_AbsByte(0xCB); sect_AbsByte(0x80 | ($2 << 3) | $4); } ; z80_ret : T_Z80_RET { sect_AbsByte(0xC9); } | T_Z80_RET ccode_expr { sect_AbsByte(0xC0 | ($2 << 3)); } ; z80_reti : T_Z80_RETI { sect_AbsByte(0xD9); } ; z80_rl : T_Z80_RL reg_r { sect_AbsByte(0xCB); sect_AbsByte(0x10 | $2); } ; z80_rla : T_Z80_RLA { sect_AbsByte(0x17); } ; z80_rlc : T_Z80_RLC reg_r { sect_AbsByte(0xCB); sect_AbsByte(0x00 | $2); } ; z80_rlca : T_Z80_RLCA { sect_AbsByte(0x07); } ; z80_rr : T_Z80_RR reg_r { sect_AbsByte(0xCB); sect_AbsByte(0x18 | $2); } ; z80_rra : T_Z80_RRA { sect_AbsByte(0x1F); } ; z80_rrc : T_Z80_RRC reg_r { sect_AbsByte(0xCB); sect_AbsByte(0x08 | $2); } ; z80_rrca : T_Z80_RRCA { sect_AbsByte(0x0F); } ; z80_rst : T_Z80_RST reloc_8bit { rpn_CheckRST(&$2, &$2); if (!rpn_isKnown(&$2)) sect_RelByte(&$2, 0); else sect_AbsByte(0xC7 | $2.val); rpn_Free(&$2); } ; z80_sbc : T_Z80_SBC op_a_n { sect_AbsByte(0xDE); sect_RelByte(&$2, 1); } | T_Z80_SBC op_a_r { sect_AbsByte(0x98 | $2); } ; z80_scf : T_Z80_SCF { sect_AbsByte(0x37); } ; z80_set : T_Z80_SET const_3bit T_COMMA reg_r { sect_AbsByte(0xCB); sect_AbsByte(0xC0 | ($2 << 3) | $4); } ; z80_sla : T_Z80_SLA reg_r { sect_AbsByte(0xCB); sect_AbsByte(0x20 | $2); } ; z80_sra : T_Z80_SRA reg_r { sect_AbsByte(0xCB); sect_AbsByte(0x28 | $2); } ; z80_srl : T_Z80_SRL reg_r { sect_AbsByte(0xCB); sect_AbsByte(0x38 | $2); } ; z80_stop : T_Z80_STOP { sect_AbsByte(0x10); sect_AbsByte(0x00); } | T_Z80_STOP reloc_8bit { sect_AbsByte(0x10); sect_RelByte(&$2, 1); } ; z80_sub : T_Z80_SUB op_a_n { sect_AbsByte(0xD6); sect_RelByte(&$2, 1); } | T_Z80_SUB op_a_r { sect_AbsByte(0x90 | $2); } ; z80_swap : T_Z80_SWAP reg_r { sect_AbsByte(0xCB); sect_AbsByte(0x30 | $2); } ; z80_xor : T_Z80_XOR op_a_n { sect_AbsByte(0xEE); sect_RelByte(&$2, 1); } | T_Z80_XOR op_a_r { sect_AbsByte(0xA8 | $2); } ; op_mem_ind : T_LBRACK reloc_16bit T_RBRACK { $$ = $2; } ; op_a_r : reg_r | T_MODE_A T_COMMA reg_r { $$ = $3; } ; op_a_n : reloc_8bit | T_MODE_A T_COMMA reloc_8bit { $$ = $3; } ; T_MODE_A : T_TOKEN_A | T_OP_HIGH T_LPAREN T_MODE_AF T_RPAREN ; T_MODE_B : T_TOKEN_B | T_OP_HIGH T_LPAREN T_MODE_BC T_RPAREN ; T_MODE_C : T_TOKEN_C | T_OP_LOW T_LPAREN T_MODE_BC T_RPAREN ; T_MODE_D : T_TOKEN_D | T_OP_HIGH T_LPAREN T_MODE_DE T_RPAREN ; T_MODE_E : T_TOKEN_E | T_OP_LOW T_LPAREN T_MODE_DE T_RPAREN ; T_MODE_H : T_TOKEN_H | T_OP_HIGH T_LPAREN T_MODE_HL T_RPAREN ; T_MODE_L : T_TOKEN_L | T_OP_LOW T_LPAREN T_MODE_HL T_RPAREN ; ccode_expr : ccode | T_OP_LOGICNOT ccode_expr { $$ = $2 ^ 1; } ; ccode : T_CC_NZ { $$ = CC_NZ; } | T_CC_Z { $$ = CC_Z; } | T_CC_NC { $$ = CC_NC; } | T_TOKEN_C { $$ = CC_C; } ; reg_r : T_MODE_B { $$ = REG_B; } | T_MODE_C { $$ = REG_C; } | T_MODE_D { $$ = REG_D; } | T_MODE_E { $$ = REG_E; } | T_MODE_H { $$ = REG_H; } | T_MODE_L { $$ = REG_L; } | T_LBRACK T_MODE_HL T_RBRACK { $$ = REG_HL_IND; } | T_MODE_A { $$ = REG_A; } ; reg_tt : T_MODE_BC { $$ = REG_BC; } | T_MODE_DE { $$ = REG_DE; } | T_MODE_HL { $$ = REG_HL; } | T_MODE_AF { $$ = REG_AF; } ; reg_ss : T_MODE_BC { $$ = REG_BC; } | T_MODE_DE { $$ = REG_DE; } | T_MODE_HL { $$ = REG_HL; } | T_MODE_SP { $$ = REG_SP; } ; reg_rr : T_LBRACK T_MODE_BC T_RBRACK { $$ = REG_BC_IND; } | T_LBRACK T_MODE_DE T_RBRACK { $$ = REG_DE_IND; } | hl_ind_inc { $$ = REG_HL_INDINC; } | hl_ind_dec { $$ = REG_HL_INDDEC; } ; hl_ind_inc : T_LBRACK T_MODE_HL_INC T_RBRACK | T_LBRACK T_MODE_HL T_OP_ADD T_RBRACK ; hl_ind_dec : T_LBRACK T_MODE_HL_DEC T_RBRACK | T_LBRACK T_MODE_HL T_OP_SUB T_RBRACK ; %%