// SPDX-License-Identifier: MIT

%language "c++"
%define api.value.type variant
%define api.token.constructor

%code requires {
	#include <stdint.h>
	#include <string>
	#include <variant>
	#include <vector>

	#include "linkdefs.hpp"

	#include "asm/actions.hpp"
	#include "asm/lexer.hpp"
	#include "asm/macro.hpp"
	#include "asm/rpn.hpp"
	#include "asm/section.hpp"

	struct ForArgs {
		int32_t start;
		int32_t stop;
		int32_t step;
	};

	struct StrFmtArgList {
		std::string format;
		std::vector<std::variant<uint32_t, std::string>> args;
	};
}

%code {
	#include <algorithm>
	#include <concepts> // invocable
	#include <inttypes.h>
	#include <optional>
	#include <stdio.h>
	#include <stdlib.h>
	#include <string.h>
	#include <string_view>

	#include "extern/utf8decoder.hpp"
	#include "helpers.hpp"
	#include "util.hpp" // toLower, toUpper

	#include "asm/charmap.hpp"
	#include "asm/fixpoint.hpp"
	#include "asm/fstack.hpp"
	#include "asm/main.hpp"
	#include "asm/opt.hpp"
	#include "asm/output.hpp"
	#include "asm/section.hpp"
	#include "asm/symbol.hpp"
	#include "asm/warning.hpp"

	using namespace std::literals;

	yy::parser::symbol_type yylex(); // Provided by lexer.cpp

	static auto handleSymbolByType(
	    InternedStr symName,
	    std::invocable<Expression const &> auto numCallback,
	    std::invocable<std::string const &> auto strCallback
	) {
		if (Symbol *sym = sym_FindScopedSymbol(symName); sym && sym->type == SYM_EQUS) {
			return strCallback(*sym->getEqus());
		} else {
			Expression expr;
			expr.makeSymbol(symName);
			return numCallback(expr);
		}
	}

	// 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, REG_C, REG_D, REG_E, REG_H, REG_L, REG_HL_IND, REG_A };

	enum { REG_BC_IND, REG_DE_IND, REG_HL_INDINC, REG_HL_INDDEC };

	// REG_AF == REG_SP since LD/INC/ADD/DEC allow SP, while PUSH/POP allow AF
	enum { REG_BC, REG_DE, REG_HL, REG_SP, REG_AF = REG_SP };
	// Names are not needed for AF or SP
	static char const *reg_tt_names[] = { "BC", "DE", "HL" };
	static char const *reg_tt_high_names[] = { "B", "D", "H" };
	static char const *reg_tt_low_names[] = { "C", "E", "L" };

	// CC_NZ == CC_Z ^ 1, and CC_NC == CC_C ^ 1, so `!` can toggle them
	enum { CC_NZ, CC_Z, CC_NC, CC_C };
}

/******************** Tokens ********************/

%token YYEOF 0 "end of file"
%token NEWLINE "end of line"
%token EOB "end of buffer"
%token EOL "end of fragment literal"

// General punctuation
%token COMMA ","
%token COLON ":" DOUBLE_COLON "::"
%token LBRACK "[" RBRACK "]"
%token LBRACKS "[[" RBRACKS "]]"
%token LPAREN "(" RPAREN ")"
%token QUESTIONMARK "?"

// Arithmetic operators
%token OP_ADD "+" OP_SUB "-"
%token OP_MUL "*" OP_DIV "/" OP_MOD "%"
%token OP_EXP "**"

// String operators
%token OP_CAT "++"
%token OP_STREQU "===" OP_STRNE "!=="

// Comparison operators
%token OP_LOGICEQU "==" OP_LOGICNE "!="
%token OP_LOGICLT "<" OP_LOGICGT ">"
%token OP_LOGICLE "<=" OP_LOGICGE ">="

// Logical operators
%token OP_LOGICAND "&&" OP_LOGICOR "||"
%token OP_LOGICNOT "!"

// Binary operators
%token OP_AND "&" OP_OR "|" OP_XOR "^"
%token OP_SHL "<<" OP_SHR ">>" OP_USHR ">>>"
%token OP_NOT "~"

// Operator precedence
%left OP_LOGICOR
%left OP_LOGICAND
%left OP_LOGICEQU OP_LOGICNE OP_LOGICLT OP_LOGICGT OP_LOGICLE OP_LOGICGE
%left OP_ADD OP_SUB
%left OP_AND OP_OR OP_XOR
%left OP_SHL OP_SHR OP_USHR
%left OP_MUL OP_DIV OP_MOD
%left OP_CAT
%precedence NEG // applies to unary OP_LOGICNOT, OP_ADD, OP_SUB, OP_NOT
%right OP_EXP

// Assignment operators (only for variables)
%token POP_EQUAL "="
%token POP_ADDEQ "+=" POP_SUBEQ "-="
%token POP_MULEQ "*=" POP_DIVEQ "/=" POP_MODEQ "%="
%token POP_ANDEQ "&=" POP_OREQ "|=" POP_XOREQ "^="
%token POP_SHLEQ "<<=" POP_SHREQ ">>="

// SM83 registers
%token TOKEN_A "a"
%token TOKEN_B "b" TOKEN_C "c"
%token TOKEN_D "d" TOKEN_E "e"
%token TOKEN_H "h" TOKEN_L "l"
%token MODE_AF "af" MODE_BC "bc" MODE_DE "de" MODE_HL "hl" MODE_SP "sp"
%token MODE_HL_INC "hli/hl+" MODE_HL_DEC "hld/hl-"

// SM83 condition codes
%token CC_Z "z" CC_NZ "nz" CC_NC "nc" // There is no CC_C, only TOKEN_C

// SM83 instructions
%token SM83_ADC "adc"
%token SM83_ADD "add"
%token SM83_AND "and"
%token SM83_BIT "bit"
%token SM83_CALL "call"
%token SM83_CCF "ccf"
%token SM83_CP "cp"
%token SM83_CPL "cpl"
%token SM83_DAA "daa"
%token SM83_DEC "dec"
%token SM83_DI "di"
%token SM83_EI "ei"
%token SM83_HALT "halt"
%token SM83_INC "inc"
%token SM83_JP "jp"
%token SM83_JR "jr"
%token SM83_LDD "ldd"
%token SM83_LDH "ldh"
%token SM83_LDI "ldi"
%token SM83_LD "ld"
%token SM83_NOP "nop"
%token SM83_OR "or"
%token SM83_POP "pop"
%token SM83_PUSH "push"
%token SM83_RES "res"
%token SM83_RETI "reti"
%token SM83_RET "ret"
%token SM83_RLA "rla"
%token SM83_RLCA "rlca"
%token SM83_RLC "rlc"
%token SM83_RL "rl"
%token SM83_RRA "rra"
%token SM83_RRCA "rrca"
%token SM83_RRC "rrc"
%token SM83_RR "rr"
%token SM83_RST "rst"
%token SM83_SBC "sbc"
%token SM83_SCF "scf"
%token SM83_SET "set"
%token SM83_SLA "sla"
%token SM83_SRA "sra"
%token SM83_SRL "srl"
%token SM83_STOP "stop"
%token SM83_SUB "sub"
%token SM83_SWAP "swap"
%token SM83_XOR "xor"

// Statement keywords
%token POP_ALIGN "ALIGN"
%token POP_ASSERT "ASSERT"
%token POP_BREAK "BREAK"
%token POP_CHARMAP "CHARMAP"
%token POP_DB "DB"
%token POP_DL "DL"
%token POP_DS "DS"
%token POP_DW "DW"
%token POP_ELIF "ELIF"
%token POP_ELSE "ELSE"
%token POP_ENDC "ENDC"
%token POP_ENDL "ENDL"
%token POP_ENDM "ENDM"
%token POP_ENDR "ENDR"
%token POP_ENDSECTION "ENDSECTION"
%token POP_ENDU "ENDU"
%token POP_EQU "EQU"
%token POP_EQUS "EQUS"
%token POP_EXPORT "EXPORT"
%token POP_FAIL "FAIL"
%token POP_FATAL "FATAL"
%token POP_FOR "FOR"
%token POP_FRAGMENT "FRAGMENT"
%token POP_IF "IF"
%token POP_INCBIN "INCBIN"
%token POP_INCLUDE "INCLUDE"
%token POP_LOAD "LOAD"
%token POP_MACRO "MACRO"
%token POP_NEWCHARMAP "NEWCHARMAP"
%token POP_NEXTU "NEXTU"
%token POP_OPT "OPT"
%token POP_POPC "POPC"
%token POP_POPO "POPO"
%token POP_POPS "POPS"
%token POP_PRINTLN "PRINTLN"
%token POP_PRINT "PRINT"
%token POP_PURGE "PURGE"
%token POP_PUSHC "PUSHC"
%token POP_PUSHO "PUSHO"
%token POP_PUSHS "PUSHS"
%token POP_RB "RB"
%token POP_REDEF "REDEF"
%token POP_REPT "REPT"
%token POP_RSRESET "RSRESET"
%token POP_RSSET "RSSET"
// There is no POP_RL, only SM83_RL
%token POP_RW "RW"
%token POP_SECTION "SECTION"
%token POP_SETCHARMAP "SETCHARMAP"
%token POP_SHIFT "SHIFT"
%token POP_STATIC_ASSERT "STATIC_ASSERT"
%token POP_UNION "UNION"
%token POP_WARN "WARN"

// Function keywords
%token OP_ACOS "ACOS"
%token OP_ASIN "ASIN"
%token OP_ATAN "ATAN"
%token OP_ATAN2 "ATAN2"
%token OP_BANK "BANK"
%token OP_BITWIDTH "BITWIDTH"
%token OP_BYTELEN "BYTELEN"
%token OP_CEIL "CEIL"
%token OP_CHARCMP "CHARCMP"
%token OP_CHARLEN "CHARLEN"
%token OP_CHARSIZE "CHARSIZE"
%token OP_CHARSUB "CHARSUB"
%token OP_CHARVAL "CHARVAL"
%token OP_COS "COS"
%token OP_DEF "DEF"
%token OP_FDIV "FDIV"
%token OP_FLOOR "FLOOR"
%token OP_FMOD "FMOD"
%token OP_FMUL "FMUL"
%token OP_HIGH "HIGH"
%token OP_INCHARMAP "INCHARMAP"
%token OP_ISCONST "ISCONST"
%token OP_LOG "LOG"
%token OP_LOW "LOW"
%token OP_POW "POW"
%token OP_READFILE "READFILE"
%token OP_REVCHAR "REVCHAR"
%token OP_ROUND "ROUND"
%token OP_SIN "SIN"
%token OP_SIZEOF "SIZEOF"
%token OP_STARTOF "STARTOF"
%token OP_STRBYTE "STRBYTE"
%token OP_STRCAT "STRCAT"
%token OP_STRCHAR "STRCHAR"
%token OP_STRCMP "STRCMP"
%token OP_STRFIND "STRFIND"
%token OP_STRFMT "STRFMT"
%token OP_STRIN "STRIN"
%token OP_STRLEN "STRLEN"
%token OP_STRLWR "STRLWR"
%token OP_STRRFIND "STRRFIND"
%token OP_STRRIN "STRRIN"
%token OP_STRRPL "STRRPL"
%token OP_STRSLICE "STRSLICE"
%token OP_STRSUB "STRSUB"
%token OP_STRUPR "STRUPR"
%token OP_TAN "TAN"
%token OP_TZCOUNT "TZCOUNT"

// Section types
%token SECT_HRAM "HRAM"
%token SECT_OAM "OAM"
%token SECT_ROM0 "ROM0"
%token SECT_ROMX "ROMX"
%token SECT_SRAM "SRAM"
%token SECT_VRAM "VRAM"
%token SECT_WRAM0 "WRAM0"
%token SECT_WRAMX "WRAMX"

// Literals
%token <int32_t> NUMBER "number"
%token <std::string> STRING "string"
%token <std::string> CHARACTER "character"
%token <InternedStr> SYMBOL "symbol"
%token <InternedStr> LABEL "label"
%token <InternedStr> LOCAL "local label"
%token <InternedStr> ANON "anonymous label"
%token <InternedStr> QMACRO "quiet macro"

/******************** Data types ********************/

// RPN expressions
%type <Expression> relocexpr
// `relocexpr_no_str` exists because strings usually count as numeric expressions, but some
// contexts treat numbers and strings differently, e.g. `db "string"` or `print "string"`.
%type <Expression> relocexpr_no_str
%type <Expression> reloc_3bit
%type <Expression> reloc_8bit
%type <Expression> reloc_16bit

// Constant numbers
%type <int32_t> iconst
%type <int32_t> uconst
// Constant numbers used only in specific contexts
%type <int32_t> precision_arg
%type <int32_t> rs_uconst
%type <int32_t> sect_org
%type <int32_t> shift_const

// Strings
%type <std::string> string
%type <std::string> string_literal
%type <std::string> strcat_args
// Strings used for identifiers
%type <InternedStr> def_id
%type <InternedStr> redef_id
%type <InternedStr> def_numeric
%type <InternedStr> def_equ
%type <InternedStr> redef_equ
%type <InternedStr> def_set
%type <InternedStr> def_rb
%type <InternedStr> def_rw
%type <InternedStr> def_rl
%type <InternedStr> def_equs
%type <InternedStr> redef_equs
%type <InternedStr> scoped_sym
// `scoped_sym_no_anon` exists because anonymous labels usually count as "scoped symbols", but some
// contexts treat anonymous labels and other labels/symbols differently, e.g. `purge` or `export`.
%type <InternedStr> scoped_sym_no_anon
%type <InternedStr> fragment_literal
%type <InternedStr> fragment_literal_name

// SM83 instruction parameters
%type <int32_t> reg_r
%type <int32_t> reg_r_no_a
%type <int32_t> reg_a
%type <int32_t> reg_ss
%type <int32_t> reg_rr
%type <int32_t> reg_tt
%type <int32_t> reg_tt_no_af
%type <int32_t> reg_bc_or_de
%type <int32_t> ccode_expr
%type <int32_t> ccode
%type <Expression> op_a_n
%type <int32_t> op_a_r
%type <Expression> op_mem_ind
%type <Expression> op_sp_offset

// Data types used only in specific contexts
%type <AlignmentSpec> align_spec
%type <AssertionType> assert_type
%type <Capture> capture_macro
%type <Capture> capture_rept
%type <RPNCommand> compound_eq
%type <std::vector<int32_t>> charmap_args
%type <std::vector<Expression>> ds_args
%type <ForArgs> for_args
%type <std::shared_ptr<MacroArgs>> macro_args
%type <std::vector<InternedStr>> purge_args
%type <SectionSpec> sect_attrs
%type <SectionModifier> sect_mod
%type <SectionType> sect_type
%type <StrFmtArgList> strfmt_args
%type <StrFmtArgList> strfmt_va_args
%type <bool> maybe_quiet

%%

/******************** Parser rules ********************/

// Assembly files.

asm_file: lines;

lines:
	  %empty
	| lines diff_mark line
	// Continue parsing the next line on a syntax error
	| error {
		lexer_SetMode(LEXER_NORMAL);
		lexer_ToggleStringExpansion(true);
	} endofline {
		yyerrok;
	}
;

diff_mark:
	  %empty // OK
	| OP_ADD {
		::error(
		    "syntax error, unexpected '+' at the beginning of the line (is it a leftover diff "
		    "mark?)"
		);
	}
	| OP_SUB {
		::error(
		    "syntax error, unexpected '-' at the beginning of the line (is it a leftover diff "
		    "mark?)"
		);
	}
;

// Lines and line directives.

line:
	  plain_directive endofline
	| line_directive // Directives that manage newlines themselves
;

endofline: NEWLINE | EOB | EOL;

// 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:
	  macro_def
	| rept
	| for
	| break
	| include
	| if
	| endc
	// It's important that all of these require being at line start for `skipIfBlock`
	| elif
	| else
;

if:
	POP_IF iconst NEWLINE {
		act_If($2);
	}
;

elif:
	POP_ELIF iconst NEWLINE {
		act_Elif($2);
	}
;

else:
	POP_ELSE NEWLINE {
		act_Else();
	}
;

// Directives, labels, functions, and values.

plain_directive:
	  label
	| label data
	| label macro_invocation
	| label directive
;

endc:
	POP_ENDC endofline {
		act_Endc();
	}
;

def_id:
	OP_DEF {
		lexer_ToggleStringExpansion(false);
	} SYMBOL {
		lexer_ToggleStringExpansion(true);
		$$ = $3;
	}
;

redef_id:
	POP_REDEF {
		lexer_ToggleStringExpansion(false);
	} SYMBOL {
		lexer_ToggleStringExpansion(true);
		$$ = $3;
	}
;

scoped_sym_no_anon: SYMBOL | LABEL | LOCAL;

scoped_sym: scoped_sym_no_anon | ANON;

label:
	  %empty
	| LABEL COLON {
		sym_AddLabel($1);
	}
	| LABEL DOUBLE_COLON {
		sym_AddLabel($1);
		sym_Export($1);
	}
	| LOCAL {
		sym_AddLocalLabel($1);
	}
	| LOCAL COLON {
		sym_AddLocalLabel($1);
	}
	| LOCAL DOUBLE_COLON {
		sym_AddLocalLabel($1);
		sym_Export($1);
	}
	| COLON {
		sym_AddAnonLabel();
	}
;

macro_invocation:
	SYMBOL {
		// Parsing 'macro_args' will restore the lexer's normal mode
		lexer_SetMode(LEXER_RAW);
	} macro_args {
		fstk_RunMacro($1, $3, false);
	}
	| QMACRO {
		// Parsing 'macro_args' will restore the lexer's normal mode
		lexer_SetMode(LEXER_RAW);
	} macro_args {
		fstk_RunMacro($1, $3, true);
	}
;

macro_args:
	%empty {
		$$ = std::make_shared<MacroArgs>();
	}
	| macro_args STRING {
		$$ = std::move($1);
		$$->appendArg(std::make_shared<std::string>($2));
	}
;

directive:
	  print
	| println
	| export
	| export_def
	| section
	| rsreset
	| rsset
	| union
	| nextu
	| endu
	| incbin
	| charmap
	| newcharmap
	| setcharmap
	| pushc
	| popc
	| pushc_setcharmap
	| load
	| shift
	| fail
	| warn
	| assert
	| def_numeric
	| def_equs
	| redef_equs
	| purge
	| pops
	| pushs
	| pushs_section
	| endsection
	| popo
	| pusho
	| opt
	| align
;

def_numeric:
	  def_equ
	| redef_equ
	| def_set
	| def_rb
	| def_rw
	| def_rl
;

trailing_comma: %empty | COMMA;

compound_eq:
	POP_ADDEQ {
		$$ = RPN_ADD;
	}
	| POP_SUBEQ {
		$$ = RPN_SUB;
	}
	| POP_MULEQ {
		$$ = RPN_MUL;
	}
	| POP_DIVEQ {
		$$ = RPN_DIV;
	}
	| POP_MODEQ {
		$$ = RPN_MOD;
	}
	| POP_XOREQ {
		$$ = RPN_XOR;
	}
	| POP_OREQ {
		$$ = RPN_OR;
	}
	| POP_ANDEQ {
		$$ = RPN_AND;
	}
	| POP_SHLEQ {
		$$ = RPN_SHL;
	}
	| POP_SHREQ {
		$$ = RPN_SHR;
	}
;

align:
	POP_ALIGN align_spec {
		sect_AlignPC($2.alignment, $2.alignOfs);
	}
;

align_spec:
	uconst {
		$$ = act_Alignment($1, 0);
	}
	| uconst COMMA iconst {
		$$ = act_Alignment($1, $3);
	}
;

opt:
	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:
	STRING {
		opt_Parse($1.c_str());
	}
;

popo:
	POP_POPO {
		opt_Pop();
	}
;

pusho:
	POP_PUSHO {
		opt_Push();
		// Parsing 'pusho_opt_list' will restore the lexer's normal mode
		lexer_SetMode(LEXER_RAW);
	} pusho_opt_list
;

pusho_opt_list:
	%empty {
		lexer_SetMode(LEXER_NORMAL);
	}
	| opt_list
;

pops:
	POP_POPS {
		sect_PopSection();
	}
;

pushs:
	POP_PUSHS {
		sect_PushSection();
	}
;

endsection:
	POP_ENDSECTION {
		sect_EndSection();
	}
;

fail:
	POP_FAIL string {
		fatal("%s", $2.c_str());
	}
;

warn:
	POP_WARN string {
		warning(WARNING_USER, "%s", $2.c_str());
	}
;

assert_type:
	%empty {
		$$ = ASSERT_ERROR;
	}
	| POP_WARN COMMA {
		$$ = ASSERT_WARN;
	}
	| POP_FAIL COMMA {
		$$ = ASSERT_ERROR;
	}
	| POP_FATAL COMMA {
		$$ = ASSERT_FATAL;
	}
;

assert:
	POP_ASSERT assert_type relocexpr {
		act_Assert($2, $3, "");
	}
	| POP_ASSERT assert_type relocexpr COMMA string {
		act_Assert($2, $3, $5);
	}
	| POP_STATIC_ASSERT assert_type iconst {
		act_StaticAssert($2, $3, "");
	}
	| POP_STATIC_ASSERT assert_type iconst COMMA string {
		act_StaticAssert($2, $3, $5);
	}
;

shift:
	POP_SHIFT shift_const {
		if (MacroArgs *macroArgs = fstk_GetCurrentMacroArgs(); macroArgs) {
			macroArgs->shiftArgs($2);
		} else {
			::error("Cannot shift macro arguments outside of a macro");
		}
	}
;

shift_const:
	%empty {
		$$ = 1;
	}
	| iconst
;

load:
	POP_LOAD sect_mod string COMMA sect_type sect_org sect_attrs {
		sect_SetLoadSection($3, $5, $6, $7, $2);
	}
	| POP_ENDL {
		sect_EndLoadSection(nullptr);
	}
;

maybe_quiet:
	%empty {
		$$ = false;
	}
	| QUESTIONMARK {
		$$ = true;
	}
;

rept:
	POP_REPT maybe_quiet uconst NEWLINE capture_rept endofline {
		if ($5.span.ptr) {
			fstk_RunRept($3, $5.lineNo, $5.span, $2);
		}
	}
;

for:
	POP_FOR {
		lexer_ToggleStringExpansion(false);
	} maybe_quiet SYMBOL {
		lexer_ToggleStringExpansion(true);
	} COMMA for_args NEWLINE capture_rept endofline {
		if ($9.span.ptr) {
			fstk_RunFor($4, $7.start, $7.stop, $7.step, $9.lineNo, $9.span, $3);
		}
	}
;

capture_rept:
	%empty {
		$$ = lexer_CaptureRept();
	}
;

for_args:
	iconst {
		$$.start = 0;
		$$.stop = $1;
		$$.step = 1;
	}
	| iconst COMMA iconst {
		$$.start = $1;
		$$.stop = $3;
		$$.step = 1;
	}
	| iconst COMMA iconst COMMA iconst {
		$$.start = $1;
		$$.stop = $3;
		$$.step = $5;
	}
;

break:
	label POP_BREAK endofline {
		if (fstk_Break()) {
			lexer_SetMode(LEXER_SKIP_TO_ENDR);
		}
	}
;

macro_def:
	POP_MACRO {
		lexer_ToggleStringExpansion(false);
	} maybe_quiet SYMBOL {
		lexer_ToggleStringExpansion(true);
	} NEWLINE capture_macro endofline {
		if ($7.span.ptr) {
			sym_AddMacro($4, $7.lineNo, $7.span, $3);
		}
	}
;

capture_macro:
	%empty {
		$$ = lexer_CaptureMacro();
	}
;

rsset:
	POP_RSSET uconst {
		sym_SetRSValue($2);
	}
;

rsreset:
	POP_RSRESET {
		sym_SetRSValue(0);
	}
;

rs_uconst:
	%empty {
		$$ = 1;
	}
	| uconst
;

union:
	POP_UNION {
		sect_StartUnion();
	}
;

nextu:
	POP_NEXTU {
		sect_NextUnionMember();
	}
;

endu:
	POP_ENDU {
		sect_EndUnion();
	}
;

def_equ:
	def_id POP_EQU iconst {
		$$ = $1;
		sym_AddEqu($$, $3);
	}
;

redef_equ:
	redef_id POP_EQU iconst {
		$$ = $1;
		sym_RedefEqu($$, $3);
	}
;

def_set:
	def_id POP_EQUAL iconst {
		$$ = $1;
		sym_AddVar($$, $3);
	}
	| redef_id POP_EQUAL iconst {
		$$ = $1;
		sym_AddVar($$, $3);
	}
	| def_id compound_eq iconst {
		$$ = $1;
		act_CompoundAssignment($$, $2, $3);
	}
	| redef_id compound_eq iconst {
		$$ = $1;
		act_CompoundAssignment($$, $2, $3);
	}
;

def_rb:
	def_id POP_RB rs_uconst {
		$$ = $1;
		uint32_t rs = sym_GetRSValue();
		sym_AddEqu($$, rs);
		sym_SetRSValue(rs + $3);
	}
;

def_rw:
	def_id POP_RW rs_uconst {
		$$ = $1;
		uint32_t rs = sym_GetRSValue();
		sym_AddEqu($$, rs);
		sym_SetRSValue(rs + 2 * $3);
	}
;

def_rl:
	def_id SM83_RL rs_uconst {
		$$ = $1;
		uint32_t rs = sym_GetRSValue();
		sym_AddEqu($$, rs);
		sym_SetRSValue(rs + 4 * $3);
	}
;

def_equs:
	def_id POP_EQUS string {
		$$ = $1;
		sym_AddString($$, std::make_shared<std::string>($3));
	}
;

redef_equs:
	redef_id POP_EQUS string {
		$$ = $1;
		sym_RedefString($$, std::make_shared<std::string>($3));
	}
;

purge:
	POP_PURGE {
		lexer_ToggleStringExpansion(false);
	} purge_args trailing_comma {
		for (InternedStr arg : $3) {
			sym_Purge(arg);
		}
		lexer_ToggleStringExpansion(true);
	}
;

purge_args:
	scoped_sym_no_anon {
		$$.push_back($1);
	}
	| purge_args COMMA scoped_sym_no_anon {
		$$ = std::move($1);
		$$.push_back($3);
	}
;

export: POP_EXPORT export_list trailing_comma;

export_list:
	  export_list_entry
	| export_list COMMA export_list_entry
;

export_list_entry:
	scoped_sym_no_anon {
		sym_Export($1);
	}
;

export_def:
	POP_EXPORT def_numeric {
		sym_Export($2);
	}
;

include:
	label POP_INCLUDE maybe_quiet string endofline {
		if (fstk_RunInclude($4, $3)) {
			YYACCEPT;
		}
	}
;

incbin:
	POP_INCBIN string {
		if (sect_BinaryFile($2, 0)) {
			YYACCEPT;
		}
	}
	| POP_INCBIN string COMMA uconst {
		if (sect_BinaryFile($2, $4)) {
			YYACCEPT;
		}
	}
	| POP_INCBIN string COMMA uconst COMMA uconst {
		if (sect_BinaryFileSlice($2, $4, $6)) {
			YYACCEPT;
		}
	}
;

charmap:
	POP_CHARMAP string COMMA charmap_args trailing_comma {
		charmap_Add($2, std::move($4));
	}
	| POP_CHARMAP CHARACTER COMMA charmap_args trailing_comma {
		charmap_Add($2, std::move($4));
	}
;

charmap_args:
	iconst {
		$$.push_back(std::move($1));
	}
	| charmap_args COMMA iconst {
		$$ = std::move($1);
		$$.push_back(std::move($3));
	}
;

newcharmap:
	POP_NEWCHARMAP SYMBOL {
		charmap_New($2, nullptr);
	}
	| POP_NEWCHARMAP SYMBOL COMMA SYMBOL {
		charmap_New($2, &$4);
	}
;

setcharmap:
	POP_SETCHARMAP SYMBOL {
		charmap_Set($2);
	}
;

pushc:
	POP_PUSHC {
		charmap_Push();
	}
;

pushc_setcharmap:
	POP_PUSHC SYMBOL {
		charmap_Push();
		charmap_Set($2);
	}
;

popc:
	POP_POPC {
		charmap_Pop();
	}
;

print: POP_PRINT print_exprs trailing_comma;

println:
	POP_PRINTLN {
		putchar('\n');
		fflush(stdout);
	}
	| POP_PRINTLN print_exprs trailing_comma {
		putchar('\n');
		fflush(stdout);
	}
;

print_exprs:
	  print_expr
	| print_exprs COMMA print_expr
;

print_expr:
	relocexpr_no_str {
		printf("$%" PRIX32, $1.getConstVal());
	}
	| string_literal {
		// Allow printing NUL characters
		fwrite($1.data(), 1, $1.length(), stdout);
	}
	| scoped_sym {
		handleSymbolByType(
		    $1,
		    [](Expression const &expr) { printf("$%" PRIX32, expr.getConstVal()); },
		    [](std::string const &str) { fwrite(str.data(), 1, str.length(), stdout); }
		);
	}
;

reloc_3bit:
	relocexpr {
		$$ = std::move($1);
		$$.checkNBit(3);
	}
;

constlist_8bit:
	  constlist_8bit_entry
	| constlist_8bit COMMA constlist_8bit_entry
;

constlist_8bit_entry:
	relocexpr_no_str {
		$1.checkNBit(8);
		sect_RelByte($1, 0);
	}
	| string_literal {
		std::vector<int32_t> output = charmap_Convert($1);
		sect_ByteString(output);
	}
	| scoped_sym {
		handleSymbolByType(
		    $1,
		    [](Expression const &expr) {
			    expr.checkNBit(8);
			    sect_RelByte(expr, 0);
		    },
		    [](std::string const &str) {
			    std::vector<int32_t> output = charmap_Convert(str);
			    sect_ByteString(output);
		    }
		);
	}
;

constlist_16bit:
	  constlist_16bit_entry
	| constlist_16bit COMMA constlist_16bit_entry
;

constlist_16bit_entry:
	relocexpr_no_str {
		$1.checkNBit(16);
		sect_RelWord($1, 0);
	}
	| string_literal {
		std::vector<int32_t> output = charmap_Convert($1);
		sect_WordString(output);
	}
	| scoped_sym {
		handleSymbolByType(
		    $1,
		    [](Expression const &expr) {
			    expr.checkNBit(16);
			    sect_RelWord(expr, 0);
		    },
		    [](std::string const &str) {
			    std::vector<int32_t> output = charmap_Convert(str);
			    sect_WordString(output);
		    }
		);
	}
	| fragment_literal {
		Expression expr;
		expr.makeSymbol($1);
		expr.checkNBit(16);
		sect_RelWord(expr, 0);
	}
;

constlist_32bit:
	  constlist_32bit_entry
	| constlist_32bit COMMA constlist_32bit_entry
;

constlist_32bit_entry:
	relocexpr_no_str {
		sect_RelLong($1, 0);
	}
	| string_literal {
		std::vector<int32_t> output = charmap_Convert($1);
		sect_LongString(output);
	}
	| scoped_sym {
		handleSymbolByType(
		    $1,
		    [](Expression const &expr) { sect_RelLong(expr, 0); },
		    [](std::string const &str) {
			    std::vector<int32_t> output = charmap_Convert(str);
			    sect_LongString(output);
		    }
		);
	}
;

reloc_8bit:
	relocexpr {
		$$ = std::move($1);
		$$.checkNBit(8);
	}
;

reloc_16bit:
	relocexpr {
		$$ = std::move($1);
		$$.checkNBit(16);
	}
	| fragment_literal {
		$$.makeSymbol($1);
	}
;

fragment_literal:
	LBRACKS fragment_literal_name asm_file RBRACKS {
		sect_PopSection();
		$$ = $2;
	}
;

fragment_literal_name:
	%empty {
		$$ = sect_PushSectionFragmentLiteral();
		sym_AddLabel($$);
	}
;

relocexpr:
	relocexpr_no_str {
		$$ = std::move($1);
	}
	| string_literal {
		$$.makeNumber(act_StringToNum($1));
	}
	| scoped_sym {
		$$ = handleSymbolByType(
		    $1,
		    [](Expression const &expr) { return expr; },
		    [](std::string const &str) {
			    Expression expr;
			    expr.makeNumber(act_StringToNum(str));
			    return expr;
		    }
		);
	}
;

relocexpr_no_str:
	NUMBER {
		$$.makeNumber($1);
	}
	| CHARACTER {
		$$.makeNumber(act_CharToNum($1));
	}
	| string OP_STREQU string {
		$$.makeNumber($1.compare($3) == 0);
	}
	| string OP_STRNE string {
		$$.makeNumber($1.compare($3) != 0);
	}
	| OP_LOGICNOT relocexpr %prec NEG {
		$$.makeUnaryOp(RPN_LOGNOT, std::move($2));
	}
	| relocexpr OP_LOGICOR relocexpr {
		$$.makeBinaryOp(RPN_LOGOR, std::move($1), $3);
	}
	| relocexpr OP_LOGICAND relocexpr {
		$$.makeBinaryOp(RPN_LOGAND, std::move($1), $3);
	}
	| relocexpr OP_LOGICEQU relocexpr {
		$$.makeBinaryOp(RPN_LOGEQ, std::move($1), $3);
	}
	| relocexpr OP_LOGICGT relocexpr {
		$$.makeBinaryOp(RPN_LOGGT, std::move($1), $3);
	}
	| relocexpr OP_LOGICLT relocexpr {
		$$.makeBinaryOp(RPN_LOGLT, std::move($1), $3);
	}
	| relocexpr OP_LOGICGE relocexpr {
		$$.makeBinaryOp(RPN_LOGGE, std::move($1), $3);
	}
	| relocexpr OP_LOGICLE relocexpr {
		$$.makeBinaryOp(RPN_LOGLE, std::move($1), $3);
	}
	| relocexpr OP_LOGICNE relocexpr {
		$$.makeBinaryOp(RPN_LOGNE, std::move($1), $3);
	}
	| relocexpr OP_ADD relocexpr {
		$$.makeBinaryOp(RPN_ADD, std::move($1), $3);
	}
	| relocexpr OP_SUB relocexpr {
		$$.makeBinaryOp(RPN_SUB, std::move($1), $3);
	}
	| relocexpr OP_XOR relocexpr {
		$$.makeBinaryOp(RPN_XOR, std::move($1), $3);
	}
	| relocexpr OP_OR relocexpr {
		$$.makeBinaryOp(RPN_OR, std::move($1), $3);
	}
	| relocexpr OP_AND relocexpr {
		$$.makeBinaryOp(RPN_AND, std::move($1), $3);
	}
	| relocexpr OP_SHL relocexpr {
		$$.makeBinaryOp(RPN_SHL, std::move($1), $3);
	}
	| relocexpr OP_SHR relocexpr {
		$$.makeBinaryOp(RPN_SHR, std::move($1), $3);
	}
	| relocexpr OP_USHR relocexpr {
		$$.makeBinaryOp(RPN_USHR, std::move($1), $3);
	}
	| relocexpr OP_MUL relocexpr {
		$$.makeBinaryOp(RPN_MUL, std::move($1), $3);
	}
	| relocexpr OP_DIV relocexpr {
		$$.makeBinaryOp(RPN_DIV, std::move($1), $3);
	}
	| relocexpr OP_MOD relocexpr {
		$$.makeBinaryOp(RPN_MOD, std::move($1), $3);
	}
	| relocexpr OP_EXP relocexpr {
		$$.makeBinaryOp(RPN_EXP, std::move($1), $3);
	}
	| OP_ADD relocexpr %prec NEG {
		$$ = std::move($2);
	}
	| OP_SUB relocexpr %prec NEG {
		$$.makeUnaryOp(RPN_NEG, std::move($2));
	}
	| OP_NOT relocexpr %prec NEG {
		$$.makeUnaryOp(RPN_NOT, std::move($2));
	}
	| OP_HIGH LPAREN relocexpr RPAREN {
		$$.makeUnaryOp(RPN_HIGH, std::move($3));
	}
	| OP_LOW LPAREN relocexpr RPAREN {
		$$.makeUnaryOp(RPN_LOW, std::move($3));
	}
	| OP_BITWIDTH LPAREN relocexpr RPAREN {
		$$.makeUnaryOp(RPN_BITWIDTH, std::move($3));
	}
	| OP_TZCOUNT LPAREN relocexpr RPAREN {
		$$.makeUnaryOp(RPN_TZCOUNT, std::move($3));
	}
	| OP_ISCONST LPAREN relocexpr RPAREN {
		$$.makeNumber($3.isKnown());
	}
	| OP_BANK LPAREN scoped_sym RPAREN {
		// '@' is also a SYMBOL; it is handled here
		$$.makeBankSymbol($3);
	}
	| OP_BANK LPAREN string_literal RPAREN {
		$$.makeBankSection($3);
	}
	| OP_SIZEOF LPAREN string RPAREN {
		$$.makeSizeOfSection($3);
	}
	| OP_STARTOF LPAREN string RPAREN {
		$$.makeStartOfSection($3);
	}
	| OP_SIZEOF LPAREN sect_type RPAREN {
		$$.makeSizeOfSectionType($3);
	}
	| OP_STARTOF LPAREN sect_type RPAREN {
		$$.makeStartOfSectionType($3);
	}
	| OP_SIZEOF LPAREN MODE_R8 RPAREN {
		$$.makeNumber(1);
	}
	| OP_SIZEOF LPAREN MODE_R16 RPAREN {
		$$.makeNumber(2);
	}
	| OP_DEF {
		lexer_ToggleStringExpansion(false);
	} LPAREN scoped_sym RPAREN {
		$$.makeNumber(sym_FindScopedValidSymbol($4) != nullptr);
		lexer_ToggleStringExpansion(true);
	}
	| OP_ROUND LPAREN iconst precision_arg RPAREN {
		$$.makeNumber(fix_Round($3, $4));
	}
	| OP_CEIL LPAREN iconst precision_arg RPAREN {
		$$.makeNumber(fix_Ceil($3, $4));
	}
	| OP_FLOOR LPAREN iconst precision_arg RPAREN {
		$$.makeNumber(fix_Floor($3, $4));
	}
	| OP_FDIV LPAREN iconst COMMA iconst precision_arg RPAREN {
		$$.makeNumber(fix_Div($3, $5, $6));
	}
	| OP_FMUL LPAREN iconst COMMA iconst precision_arg RPAREN {
		$$.makeNumber(fix_Mul($3, $5, $6));
	}
	| OP_FMOD LPAREN iconst COMMA iconst precision_arg RPAREN {
		$$.makeNumber(fix_Mod($3, $5, $6));
	}
	| OP_POW LPAREN iconst COMMA iconst precision_arg RPAREN {
		$$.makeNumber(fix_Pow($3, $5, $6));
	}
	| OP_LOG LPAREN iconst COMMA iconst precision_arg RPAREN {
		$$.makeNumber(fix_Log($3, $5, $6));
	}
	| OP_SIN LPAREN iconst precision_arg RPAREN {
		$$.makeNumber(fix_Sin($3, $4));
	}
	| OP_COS LPAREN iconst precision_arg RPAREN {
		$$.makeNumber(fix_Cos($3, $4));
	}
	| OP_TAN LPAREN iconst precision_arg RPAREN {
		$$.makeNumber(fix_Tan($3, $4));
	}
	| OP_ASIN LPAREN iconst precision_arg RPAREN {
		$$.makeNumber(fix_ASin($3, $4));
	}
	| OP_ACOS LPAREN iconst precision_arg RPAREN {
		$$.makeNumber(fix_ACos($3, $4));
	}
	| OP_ATAN LPAREN iconst precision_arg RPAREN {
		$$.makeNumber(fix_ATan($3, $4));
	}
	| OP_ATAN2 LPAREN iconst COMMA iconst precision_arg RPAREN {
		$$.makeNumber(fix_ATan2($3, $5, $6));
	}
	| OP_STRCMP LPAREN string COMMA string RPAREN {
		$$.makeNumber($3.compare($5));
	}
	| OP_STRFIND LPAREN string COMMA string RPAREN {
		size_t pos = $3.find($5);
		$$.makeNumber(pos != std::string::npos ? pos : -1);
	}
	| OP_STRRFIND LPAREN string COMMA string RPAREN {
		size_t pos = $3.rfind($5);
		$$.makeNumber(pos != std::string::npos ? pos : -1);
	}
	| OP_STRIN LPAREN string COMMA string RPAREN {
		warning(WARNING_OBSOLETE, "`STRIN` is deprecated; use 0-indexed `STRFIND` instead");
		size_t pos = $3.find($5);
		$$.makeNumber(pos != std::string::npos ? pos + 1 : 0);
	}
	| OP_STRRIN LPAREN string COMMA string RPAREN {
		warning(WARNING_OBSOLETE, "`STRRIN` is deprecated; use 0-indexed `STRRFIND` instead");
		size_t pos = $3.rfind($5);
		$$.makeNumber(pos != std::string::npos ? pos + 1 : 0);
	}
	| OP_STRLEN LPAREN string RPAREN {
		$$.makeNumber(act_StringLen($3, true));
	}
	| OP_BYTELEN LPAREN string RPAREN {
		$$.makeNumber($3.length());
	}
	| OP_CHARLEN LPAREN string RPAREN {
		$$.makeNumber(act_CharLen($3));
	}
	| OP_INCHARMAP LPAREN string RPAREN {
		$$.makeNumber(charmap_HasChar($3));
	}
	| OP_CHARCMP LPAREN string COMMA string RPAREN {
		$$.makeNumber(act_CharCmp($3, $5));
	}
	| OP_CHARSIZE LPAREN string RPAREN {
		size_t charSize = charmap_CharSize($3);
		if (charSize == 0) {
			::error("CHARSIZE: No character mapping for \"%s\"", $3.c_str());
		}
		$$.makeNumber(charSize);
	}
	| OP_CHARVAL LPAREN string COMMA iconst RPAREN {
		$$.makeNumber(act_CharVal($3, $5));
	}
	| OP_CHARVAL LPAREN string RPAREN {
		$$.makeNumber(act_CharVal($3));
	}
	| OP_STRBYTE LPAREN string COMMA iconst RPAREN {
		$$.makeNumber(act_StringByte($3, $5));
	}
	| LPAREN relocexpr RPAREN {
		$$ = std::move($2);
	}
;

uconst:
	iconst {
		$$ = $1;
		if ($$ < 0) {
			fatal("Constant must not be negative: %d", $$);
		}
	}
;

iconst:
	relocexpr {
		$$ = $1.getConstVal();
	}
;

precision_arg:
	%empty {
		$$ = options.fixPrecision;
	}
	| COMMA iconst {
		$$ = $2;
		if ($$ < 1 || $$ > 31) {
			::error("Fixed-point precision must be between 1 and 31, not %" PRId32, $$);
			$$ = options.fixPrecision;
		}
	}
;

string_literal:
	STRING {
		$$ = std::move($1);
	}
	| string OP_CAT string {
		$$ = std::move($1);
		$$.append($3);
	}
	| OP_READFILE LPAREN string RPAREN {
		if (std::optional<std::string> contents = act_ReadFile($3, UINT32_MAX); contents) {
			$$ = std::move(*contents);
		} else {
			YYACCEPT;
		}
	}
	| OP_READFILE LPAREN string COMMA uconst RPAREN {
		if (std::optional<std::string> contents = act_ReadFile($3, $5); contents) {
			$$ = std::move(*contents);
		} else {
			YYACCEPT;
		}
	}
	| OP_STRSLICE LPAREN string COMMA iconst COMMA iconst RPAREN {
		$$ = act_StringSlice($3, $5, $7);
	}
	| OP_STRSLICE LPAREN string COMMA iconst RPAREN {
		$$ = act_StringSlice($3, $5, std::nullopt);
	}
	| OP_STRSUB LPAREN string COMMA iconst COMMA uconst RPAREN {
		$$ = act_StringSub($3, $5, $7);
	}
	| OP_STRSUB LPAREN string COMMA iconst RPAREN {
		$$ = act_StringSub($3, $5, std::nullopt);
	}
	| OP_STRCHAR LPAREN string COMMA iconst RPAREN {
		$$ = act_StringChar($3, $5);
	}
	| OP_CHARSUB LPAREN string COMMA iconst RPAREN {
		$$ = act_CharSub($3, $5);
	}
	| OP_REVCHAR LPAREN charmap_args RPAREN {
		bool unique;
		$$ = charmap_Reverse($3, unique);
		if (!unique) {
			::error("REVCHAR: Multiple character mappings to values");
		} else if ($$.empty()) {
			::error("REVCHAR: No character mapping to values");
		}
	}
	| OP_STRCAT LPAREN RPAREN {
		$$.clear();
	}
	| OP_STRCAT LPAREN strcat_args RPAREN {
		$$ = std::move($3);
	}
	| OP_STRUPR LPAREN string RPAREN {
		$$ = std::move($3);
		std::transform(RANGE($$), $$.begin(), toUpper);
	}
	| OP_STRLWR LPAREN string RPAREN {
		$$ = std::move($3);
		std::transform(RANGE($$), $$.begin(), toLower);
	}
	| OP_STRRPL LPAREN string COMMA string COMMA string RPAREN {
		$$ = act_StringReplace($3, $5, $7);
	}
	| OP_STRFMT LPAREN strfmt_args RPAREN {
		$$ = act_StringFormat($3.format, $3.args);
	}
	| POP_SECTION LPAREN scoped_sym RPAREN {
		$$ = act_SectionName($3);
	}
;

string:
	string_literal {
		$$ = std::move($1);
	}
	| scoped_sym {
		if (Symbol *sym = sym_FindScopedSymbol($1); sym && sym->type == SYM_EQUS) {
			$$ = *sym->getEqus();
		} else {
			::error("`%s` is not a string symbol", $1.c_str());
		}
	}
;

strcat_args:
	string {
		$$ = std::move($1);
	}
	| strcat_args COMMA string {
		$$ = std::move($1);
		$$.append($3);
	}
;

strfmt_args:
	  %empty {}
	| string strfmt_va_args {
		$$ = std::move($2);
		$$.format = std::move($1);
	}
;

strfmt_va_args:
	  %empty {}
	| strfmt_va_args COMMA relocexpr_no_str {
		$$ = std::move($1);
		$$.args.push_back(static_cast<uint32_t>($3.getConstVal()));
	}
	| strfmt_va_args COMMA string_literal {
		$$ = std::move($1);
		$$.args.push_back(std::move($3));
	}
	| strfmt_va_args COMMA scoped_sym {
		$$ = std::move($1);
		handleSymbolByType(
		    $3,
		    [&](Expression const &expr) {
			    $$.args.push_back(static_cast<uint32_t>(expr.getConstVal()));
		    },
		    [&](std::string const &str) { $$.args.push_back(str); }
		);
	}
;

section:
	POP_SECTION sect_mod string COMMA sect_type sect_org sect_attrs {
		sect_NewSection($3, $5, $6, $7, $2);
	}
;

pushs_section:
	POP_PUSHS sect_mod string COMMA sect_type sect_org sect_attrs {
		sect_PushSection();
		sect_NewSection($3, $5, $6, $7, $2);
	}
;

sect_mod:
	%empty {
		$$ = SECTION_NORMAL;
	}
	| POP_UNION {
		$$ = SECTION_UNION;
	}
	| POP_FRAGMENT {
		$$ = SECTION_FRAGMENT;
	}
;

sect_type:
	SECT_WRAM0 {
		$$ = SECTTYPE_WRAM0;
	}
	| SECT_VRAM {
		$$ = SECTTYPE_VRAM;
	}
	| SECT_ROMX {
		$$ = SECTTYPE_ROMX;
	}
	| SECT_ROM0 {
		$$ = SECTTYPE_ROM0;
	}
	| SECT_HRAM {
		$$ = SECTTYPE_HRAM;
	}
	| SECT_WRAMX {
		$$ = SECTTYPE_WRAMX;
	}
	| SECT_SRAM {
		$$ = SECTTYPE_SRAM;
	}
	| SECT_OAM {
		$$ = SECTTYPE_OAM;
	}
;

sect_org:
	%empty {
		$$ = -1;
	}
	| LBRACK uconst RBRACK {
		$$ = $2;
		if ($$ < 0 || $$ > 0xFFFF) {
			::error("Address $%x is not 16-bit", $$);
			$$ = -1;
		}
	}
;

sect_attrs:
	%empty {
		$$.alignment = 0;
		$$.alignOfs = 0;
		$$.bank = -1;
	}
	| sect_attrs COMMA POP_ALIGN LBRACK align_spec RBRACK {
		$$ = $1;
		$$.alignment = $5.alignment;
		$$.alignOfs = $5.alignOfs;
	}
	| sect_attrs COMMA OP_BANK LBRACK uconst RBRACK {
		$$ = $1;
		$$.bank = $5; // We cannot check the validity of this yet
	}
;

// CPU instructions and data declarations

data:
	  datum
	| datum DOUBLE_COLON data
;

datum:
	  db
	| dw
	| dl
	| ds
	| sm83_adc
	| sm83_add
	| sm83_and
	| sm83_bit
	| sm83_call
	| sm83_ccf
	| sm83_cp
	| sm83_cpl
	| sm83_daa
	| sm83_dec
	| sm83_di
	| sm83_ei
	| sm83_halt
	| sm83_inc
	| sm83_jp
	| sm83_jr
	| sm83_ld
	| sm83_ldd
	| sm83_ldh
	| sm83_ldi
	| sm83_nop
	| sm83_or
	| sm83_pop
	| sm83_push
	| sm83_res
	| sm83_ret
	| sm83_reti
	| sm83_rl
	| sm83_rla
	| sm83_rlc
	| sm83_rlca
	| sm83_rr
	| sm83_rra
	| sm83_rrc
	| sm83_rrca
	| sm83_rst
	| sm83_sbc
	| sm83_scf
	| sm83_set
	| sm83_sla
	| sm83_sra
	| sm83_srl
	| sm83_stop
	| sm83_sub
	| sm83_swap
	| sm83_xor
;

ds:
	POP_DS uconst {
		sect_Skip($2, true);
	}
	| POP_DS uconst COMMA ds_args trailing_comma {
		sect_RelBytes($2, $4);
	}
	| POP_DS POP_ALIGN LBRACK align_spec RBRACK trailing_comma {
		uint32_t n = sect_GetAlignBytes($4.alignment, $4.alignOfs);
		sect_Skip(n, true);
		sect_AlignPC($4.alignment, $4.alignOfs);
	}
	| POP_DS POP_ALIGN LBRACK align_spec RBRACK COMMA ds_args trailing_comma {
		uint32_t n = sect_GetAlignBytes($4.alignment, $4.alignOfs);
		sect_RelBytes(n, $7);
		sect_AlignPC($4.alignment, $4.alignOfs);
	}
;

ds_args:
	reloc_8bit {
		$$.push_back(std::move($1));
	}
	| ds_args COMMA reloc_8bit {
		$$ = std::move($1);
		$$.push_back(std::move($3));
	}
;

db:
	POP_DB {
		sect_Skip(1, false);
	}
	| POP_DB constlist_8bit trailing_comma
;

dw:
	POP_DW {
		sect_Skip(2, false);
	}
	| POP_DW constlist_16bit trailing_comma
;

dl:
	POP_DL {
		sect_Skip(4, false);
	}
	| POP_DL constlist_32bit trailing_comma
;

sm83_adc:
	SM83_ADC op_a_n {
		sect_ConstByte(0xCE);
		sect_RelByte($2, 1);
	}
	| SM83_ADC op_a_r {
		sect_ConstByte(0x88 | $2);
	}
;

sm83_add:
	SM83_ADD op_a_n {
		sect_ConstByte(0xC6);
		sect_RelByte($2, 1);
	}
	| SM83_ADD op_a_r {
		sect_ConstByte(0x80 | $2);
	}
	| SM83_ADD MODE_HL COMMA reg_ss {
		sect_ConstByte(0x09 | ($4 << 4));
	}
	| SM83_ADD MODE_SP COMMA reloc_8bit {
		sect_ConstByte(0xE8);
		sect_RelByte($4, 1);
	}
;

sm83_and:
	SM83_AND op_a_n {
		sect_ConstByte(0xE6);
		sect_RelByte($2, 1);
	}
	| SM83_AND op_a_r {
		sect_ConstByte(0xA0 | $2);
	}
;

sm83_bit:
	SM83_BIT reloc_3bit COMMA reg_r {
		uint8_t mask = static_cast<uint8_t>(0x40 | $4);
		$2.addCheckBitIndex(mask);
		sect_ConstByte(0xCB);
		if (!$2.isKnown()) {
			sect_RelByte($2, 0);
		} else {
			sect_ConstByte(mask | ($2.value() << 3));
		}
	}
;

sm83_call:
	SM83_CALL reloc_16bit {
		sect_ConstByte(0xCD);
		sect_RelWord($2, 1);
	}
	| SM83_CALL ccode_expr COMMA reloc_16bit {
		sect_ConstByte(0xC4 | ($2 << 3));
		sect_RelWord($4, 1);
	}
;

sm83_ccf:
	SM83_CCF {
		sect_ConstByte(0x3F);
	}
;

sm83_cp:
	SM83_CP op_a_n {
		sect_ConstByte(0xFE);
		sect_RelByte($2, 1);
	}
	| SM83_CP op_a_r {
		sect_ConstByte(0xB8 | $2);
	}
;

sm83_cpl:
	SM83_CPL {
		sect_ConstByte(0x2F);
	}
	| SM83_CPL MODE_A {
		sect_ConstByte(0x2F);
	}
;

sm83_daa:
	SM83_DAA {
		sect_ConstByte(0x27);
	}
;

sm83_dec:
	SM83_DEC reg_r {
		sect_ConstByte(0x05 | ($2 << 3));
	}
	| SM83_DEC reg_ss {
		sect_ConstByte(0x0B | ($2 << 4));
	}
;

sm83_di:
	SM83_DI {
		sect_ConstByte(0xF3);
	}
;

sm83_ei:
	SM83_EI {
		sect_ConstByte(0xFB);
	}
;

sm83_halt:
	SM83_HALT {
		sect_ConstByte(0x76);
	}
;

sm83_inc:
	SM83_INC reg_r {
		sect_ConstByte(0x04 | ($2 << 3));
	}
	| SM83_INC reg_ss {
		sect_ConstByte(0x03 | ($2 << 4));
	}
;

sm83_jp:
	SM83_JP reloc_16bit {
		sect_ConstByte(0xC3);
		sect_RelWord($2, 1);
	}
	| SM83_JP ccode_expr COMMA reloc_16bit {
		sect_ConstByte(0xC2 | ($2 << 3));
		sect_RelWord($4, 1);
	}
	| SM83_JP MODE_HL {
		sect_ConstByte(0xE9);
	}
;

sm83_jr:
	SM83_JR reloc_16bit {
		sect_ConstByte(0x18);
		sect_PCRelByte($2, 1);
	}
	| SM83_JR ccode_expr COMMA reloc_16bit {
		sect_ConstByte(0x20 | ($2 << 3));
		sect_PCRelByte($4, 1);
	}
;

sm83_ldi:
	SM83_LDI LBRACK MODE_HL RBRACK COMMA MODE_A {
		sect_ConstByte(0x02 | (2 << 4));
	}
	| SM83_LDI MODE_A COMMA LBRACK MODE_HL RBRACK {
		sect_ConstByte(0x0A | (2 << 4));
	}
;

sm83_ldd:
	SM83_LDD LBRACK MODE_HL RBRACK COMMA MODE_A {
		sect_ConstByte(0x02 | (3 << 4));
	}
	| SM83_LDD MODE_A COMMA LBRACK MODE_HL RBRACK {
		sect_ConstByte(0x0A | (3 << 4));
	}
;

sm83_ldh:
	SM83_LDH MODE_A COMMA op_mem_ind {
		$4.addCheckHRAM();
		sect_ConstByte(0xF0);
		if (!$4.isKnown()) {
			sect_RelByte($4, 1);
		} else {
			sect_ConstByte($4.value());
		}
	}
	| SM83_LDH op_mem_ind COMMA MODE_A {
		$2.addCheckHRAM();
		sect_ConstByte(0xE0);
		if (!$2.isKnown()) {
			sect_RelByte($2, 1);
		} else {
			sect_ConstByte($2.value());
		}
	}
	| SM83_LDH MODE_A COMMA c_ind {
		sect_ConstByte(0xF2);
	}
	| SM83_LDH MODE_A COMMA ff00_c_ind {
		sect_ConstByte(0xF2);
	}
	| SM83_LDH c_ind COMMA MODE_A {
		sect_ConstByte(0xE2);
	}
	| SM83_LDH ff00_c_ind COMMA MODE_A {
		sect_ConstByte(0xE2);
	}
;

c_ind: LBRACK MODE_C RBRACK;

ff00_c_ind:
	LBRACK relocexpr OP_ADD MODE_C RBRACK {
		// This has to use `relocexpr`, not `iconst`, to avoid a shift/reduce conflict
		if ($2.getConstVal() != 0xFF00) {
			::error("Base value must be equal to $FF00 for [$FF00+C]");
		}
	}
;

sm83_ld:
	  sm83_ld_mem
	| sm83_ld_c_ind
	| sm83_ld_rr
	| sm83_ld_ss
	| sm83_ld_hl
	| sm83_ld_sp
	| sm83_ld_r_no_a
	| sm83_ld_a
;

sm83_ld_hl:
	SM83_LD MODE_HL COMMA MODE_SP op_sp_offset {
		sect_ConstByte(0xF8);
		sect_RelByte($5, 1);
	}
	| SM83_LD MODE_HL COMMA reloc_16bit {
		sect_ConstByte(0x01 | (REG_HL << 4));
		sect_RelWord($4, 1);
	}
	| SM83_LD MODE_HL COMMA reg_tt_no_af {
		::error(
		    "\"LD HL, %s\" is not a valid instruction; use \"LD H, %s\" and \"LD L, %s\"",
		    reg_tt_names[$4],
		    reg_tt_high_names[$4],
		    reg_tt_low_names[$4]
		);
	}
;

sm83_ld_sp:
	SM83_LD MODE_SP COMMA MODE_HL {
		sect_ConstByte(0xF9);
	}
	| SM83_LD MODE_SP COMMA reg_bc_or_de {
		::error("\"LD SP, %s\" is not a valid instruction", reg_tt_names[$4]);
	}
	| SM83_LD MODE_SP COMMA reloc_16bit {
		sect_ConstByte(0x01 | (REG_SP << 4));
		sect_RelWord($4, 1);
	}
;

sm83_ld_mem:
	SM83_LD op_mem_ind COMMA MODE_SP {
		sect_ConstByte(0x08);
		sect_RelWord($2, 1);
	}
	| SM83_LD op_mem_ind COMMA MODE_A {
		sect_ConstByte(0xEA);
		sect_RelWord($2, 1);
	}
;

sm83_ld_c_ind:
	SM83_LD ff00_c_ind COMMA MODE_A {
		sect_ConstByte(0xE2);
	}
;

sm83_ld_rr:
	SM83_LD reg_rr COMMA MODE_A {
		sect_ConstByte(0x02 | ($2 << 4));
	}
;

sm83_ld_r_no_a:
	SM83_LD reg_r_no_a COMMA reloc_8bit {
		sect_ConstByte(0x06 | ($2 << 3));
		sect_RelByte($4, 1);
	}
	| SM83_LD reg_r_no_a COMMA reg_r {
		if ($2 == REG_HL_IND && $4 == REG_HL_IND) {
			::error("\"LD [HL], [HL]\" is not a valid instruction");
		} else {
			sect_ConstByte(0x40 | ($2 << 3) | $4);
		}
	}
;

sm83_ld_a:
	SM83_LD reg_a COMMA reloc_8bit {
		sect_ConstByte(0x06 | ($2 << 3));
		sect_RelByte($4, 1);
	}
	| SM83_LD reg_a COMMA reg_r {
		sect_ConstByte(0x40 | ($2 << 3) | $4);
	}
	| SM83_LD reg_a COMMA ff00_c_ind {
		sect_ConstByte(0xF2);
	}
	| SM83_LD reg_a COMMA reg_rr {
		sect_ConstByte(0x0A | ($4 << 4));
	}
	| SM83_LD reg_a COMMA op_mem_ind {
		sect_ConstByte(0xFA);
		sect_RelWord($4, 1);
	}
;

sm83_ld_ss:
	SM83_LD reg_bc_or_de COMMA reloc_16bit {
		sect_ConstByte(0x01 | ($2 << 4));
		sect_RelWord($4, 1);
	}
	| SM83_LD reg_bc_or_de COMMA reg_tt_no_af {
		::error(
		    "\"LD %s, %s\" is not a valid instruction; use \"LD %s, %s\" and \"LD %s, %s\"",
		    reg_tt_names[$2],
		    reg_tt_names[$4],
		    reg_tt_high_names[$2],
		    reg_tt_high_names[$4],
		    reg_tt_low_names[$2],
		    reg_tt_low_names[$4]
		);
	}
	// HL is taken care of in sm83_ld_hl
	// SP is taken care of in sm83_ld_sp
;

sm83_nop:
	SM83_NOP {
		sect_ConstByte(0x00);
	}
;

sm83_or:
	SM83_OR op_a_n {
		sect_ConstByte(0xF6);
		sect_RelByte($2, 1);
	}
	| SM83_OR op_a_r {
		sect_ConstByte(0xB0 | $2);
	}
;

sm83_pop:
	SM83_POP reg_tt {
		sect_ConstByte(0xC1 | ($2 << 4));
	}
;

sm83_push:
	SM83_PUSH reg_tt {
		sect_ConstByte(0xC5 | ($2 << 4));
	}
;

sm83_res:
	SM83_RES reloc_3bit COMMA reg_r {
		uint8_t mask = static_cast<uint8_t>(0x80 | $4);
		$2.addCheckBitIndex(mask);
		sect_ConstByte(0xCB);
		if (!$2.isKnown()) {
			sect_RelByte($2, 0);
		} else {
			sect_ConstByte(mask | ($2.value() << 3));
		}
	}
;

sm83_ret:
	SM83_RET {
		sect_ConstByte(0xC9);
	}
	| SM83_RET ccode_expr {
		sect_ConstByte(0xC0 | ($2 << 3));
	}
;

sm83_reti:
	SM83_RETI {
		sect_ConstByte(0xD9);
	}
;

sm83_rl:
	SM83_RL reg_r {
		sect_ConstByte(0xCB);
		sect_ConstByte(0x10 | $2);
	}
;

sm83_rla:
	SM83_RLA {
		sect_ConstByte(0x17);
	}
;

sm83_rlc:
	SM83_RLC reg_r {
		sect_ConstByte(0xCB);
		sect_ConstByte(0x00 | $2);
	}
;

sm83_rlca:
	SM83_RLCA {
		sect_ConstByte(0x07);
	}
;

sm83_rr:
	SM83_RR reg_r {
		sect_ConstByte(0xCB);
		sect_ConstByte(0x18 | $2);
	}
;

sm83_rra:
	SM83_RRA {
		sect_ConstByte(0x1F);
	}
;

sm83_rrc:
	SM83_RRC reg_r {
		sect_ConstByte(0xCB);
		sect_ConstByte(0x08 | $2);
	}
;

sm83_rrca:
	SM83_RRCA {
		sect_ConstByte(0x0F);
	}
;

sm83_rst:
	SM83_RST reloc_8bit {
		$2.addCheckRST();
		if (!$2.isKnown()) {
			sect_RelByte($2, 0);
		} else {
			sect_ConstByte(0xC7 | $2.value());
		}
	}
;

sm83_sbc:
	SM83_SBC op_a_n {
		sect_ConstByte(0xDE);
		sect_RelByte($2, 1);
	}
	| SM83_SBC op_a_r {
		sect_ConstByte(0x98 | $2);
	}
;

sm83_scf:
	SM83_SCF {
		sect_ConstByte(0x37);
	}
;

sm83_set:
	SM83_SET reloc_3bit COMMA reg_r {
		uint8_t mask = static_cast<uint8_t>(0xC0 | $4);
		$2.addCheckBitIndex(mask);
		sect_ConstByte(0xCB);
		if (!$2.isKnown()) {
			sect_RelByte($2, 0);
		} else {
			sect_ConstByte(mask | ($2.value() << 3));
		}
	}
;

sm83_sla:
	SM83_SLA reg_r {
		sect_ConstByte(0xCB);
		sect_ConstByte(0x20 | $2);
	}
;

sm83_sra:
	SM83_SRA reg_r {
		sect_ConstByte(0xCB);
		sect_ConstByte(0x28 | $2);
	}
;

sm83_srl:
	SM83_SRL reg_r {
		sect_ConstByte(0xCB);
		sect_ConstByte(0x38 | $2);
	}
;

sm83_stop:
	SM83_STOP {
		sect_ConstByte(0x10);
		sect_ConstByte(0x00);
	}
	| SM83_STOP reloc_8bit {
		sect_ConstByte(0x10);
		sect_RelByte($2, 1);
	}
;

sm83_sub:
	SM83_SUB op_a_n {
		sect_ConstByte(0xD6);
		sect_RelByte($2, 1);
	}
	| SM83_SUB op_a_r {
		sect_ConstByte(0x90 | $2);
	}
;

sm83_swap:
	SM83_SWAP reg_r {
		sect_ConstByte(0xCB);
		sect_ConstByte(0x30 | $2);
	}
;

sm83_xor:
	SM83_XOR op_a_n {
		sect_ConstByte(0xEE);
		sect_RelByte($2, 1);
	}
	| SM83_XOR op_a_r {
		sect_ConstByte(0xA8 | $2);
	}
;

// Registers or values.

op_mem_ind:
	LBRACK reloc_16bit RBRACK {
		$$ = std::move($2);
	}
;

op_a_r:
	  reg_r
	| MODE_A COMMA reg_r {
		$$ = $3;
	}
;

op_a_n:
	reloc_8bit {
		$$ = std::move($1);
	}
	| MODE_A COMMA reloc_8bit {
		$$ = std::move($3);
	}
;

op_sp_offset:
	OP_ADD relocexpr {
		$$ = std::move($2);
		$$.checkNBit(8);
	}
	| OP_SUB relocexpr {
		$$.makeUnaryOp(RPN_NEG, std::move($2));
		$$.checkNBit(8);
	}
	| %empty {
		::error("\"LD HL, SP\" is not a valid instruction; use \"LD HL, SP + 0\"");
	}
;

// Registers and condition codes.

MODE_R8:
	  MODE_A
	| MODE_B
	| MODE_C
	| MODE_D
	| MODE_E
	| MODE_H
	| MODE_L
	| LBRACK MODE_BC RBRACK
	| LBRACK MODE_DE RBRACK
	| LBRACK MODE_HL RBRACK
	| hl_ind_inc
	| hl_ind_dec
;

MODE_R16:
	  MODE_AF
	| MODE_BC
	| MODE_DE
	| MODE_HL
	| MODE_SP
;

MODE_A:
	  TOKEN_A
	| OP_HIGH LPAREN MODE_AF RPAREN
;

MODE_B:
	  TOKEN_B
	| OP_HIGH LPAREN MODE_BC RPAREN
;

MODE_C:
	  TOKEN_C
	| OP_LOW LPAREN MODE_BC RPAREN
;

MODE_D:
	  TOKEN_D
	| OP_HIGH LPAREN MODE_DE RPAREN
;

MODE_E:
	  TOKEN_E
	| OP_LOW LPAREN MODE_DE RPAREN
;

MODE_H:
	  TOKEN_H
	| OP_HIGH LPAREN MODE_HL RPAREN
;

MODE_L:
	  TOKEN_L
	| OP_LOW LPAREN MODE_HL RPAREN
;

ccode_expr:
	  ccode
	| OP_LOGICNOT ccode_expr {
		$$ = $2 ^ 1;
	}
;

ccode:
	CC_NZ {
		$$ = CC_NZ;
	}
	| CC_Z {
		$$ = CC_Z;
	}
	| CC_NC {
		$$ = CC_NC;
	}
	| TOKEN_C {
		$$ = CC_C;
	}
;

reg_r: reg_r_no_a | reg_a;

reg_r_no_a:
	MODE_B {
		$$ = REG_B;
	}
	| MODE_C {
		$$ = REG_C;
	}
	| MODE_D {
		$$ = REG_D;
	}
	| MODE_E {
		$$ = REG_E;
	}
	| MODE_H {
		$$ = REG_H;
	}
	| MODE_L {
		$$ = REG_L;
	}
	| LBRACK MODE_HL RBRACK {
		$$ = REG_HL_IND;
	}
;

reg_a:
	MODE_A {
		$$ = REG_A;
	}
;

reg_tt:
	reg_tt_no_af
	| MODE_AF {
		$$ = REG_AF;
	}
;

reg_ss:
	reg_tt_no_af
	| MODE_SP {
		$$ = REG_SP;
	}
;

reg_tt_no_af:
	reg_bc_or_de
	| MODE_HL {
		$$ = REG_HL;
	}
;

reg_bc_or_de:
	MODE_BC {
		$$ = REG_BC;
	}
	| MODE_DE {
		$$ = REG_DE;
	}
;

reg_rr:
	LBRACK MODE_BC RBRACK {
		$$ = REG_BC_IND;
	}
	| LBRACK MODE_DE RBRACK {
		$$ = REG_DE_IND;
	}
	| hl_ind_inc {
		$$ = REG_HL_INDINC;
	}
	| hl_ind_dec {
		$$ = REG_HL_INDDEC;
	}
;

hl_ind_inc:
	  LBRACK MODE_HL_INC RBRACK
	| LBRACK MODE_HL OP_ADD RBRACK
;

hl_ind_dec:
	  LBRACK MODE_HL_DEC RBRACK
	| LBRACK MODE_HL OP_SUB RBRACK
;

%%

/******************** Error handler ********************/

void yy::parser::error(std::string const &str) {
	::error("%s", str.c_str());
}
