/* BASIC2026 in-process assembler.  The public front-end is CPU-neutral; this
 * first backend implements the documented Z80 instruction set. */
#define B2026_ASM_MAX_SOURCE (16U*1024U*1024U)
#define B2026_ASM_MAX_SYMBOLS 65536
#define B2026_ASM_MAX_DIAGNOSTICS 100

typedef struct {char *name;long value;int defined;int line;} B2026AsmSymbol;
typedef struct {
    B2026AsmSymbol *symbols;int symbol_count,symbol_cap;
    unsigned char *bytes;size_t byte_count,byte_cap;
    unsigned int base,pc,segment_start,segment_end,execution,fill;
    int pass,line,errors,warnings,ended,first_org_seen;
    char message[512];
} B2026Assembler;

static char*b2026_asm_strdup(const char*s){size_t n;char*r;if(s==NULL)s="";n=strlen(s);r=(char*)malloc(n+1U);if(r!=NULL)memcpy(r,s,n+1U);return r;}
static int b2026_asm_ieq(const char*a,const char*b){unsigned char x,y;if(a==NULL||b==NULL)return 0;while(*a&&*b){x=(unsigned char)*a++;y=(unsigned char)*b++;if(x>='a'&&x<='z')x-=32;if(y>='a'&&y<='z')y-=32;if(x!=y)return 0;}return *a=='\0'&&*b=='\0';}
static char*b2026_asm_trim(char*s){char*e;while(*s&&isspace((unsigned char)*s))++s;e=s+strlen(s);while(e>s&&isspace((unsigned char)e[-1]))--e;*e='\0';return s;}
static void b2026_asm_upper(char*s){for(;*s;++s)if(*s>='a'&&*s<='z')*s=(char)(*s-32);}
static int b2026_asm_ident(int c);
static void b2026_asm_strip_comment(char*s){size_t i=0U;int quoted=0;while(s[i]){unsigned char c=(unsigned char)s[i];if(c=='"'){quoted=!quoted;++i;continue;}if(quoted){if(c=='\\'&&s[i+1])i+=2U;else ++i;continue;}if(c==';'){s[i]='\0';return;}if(c=='\''){int af_prime=i>=2U&&(s[i-2]=='A'||s[i-2]=='a')&&(s[i-1]=='F'||s[i-1]=='f')&&(i<3U||!b2026_asm_ident((unsigned char)s[i-3]))&&(s[i+1]=='\0'||isspace((unsigned char)s[i+1])||s[i+1]==','||s[i+1]==')');int char_literal=s[i+1]!='\0'&&s[i+2]=='\'';if(af_prime){++i;continue;}if(char_literal){i+=3U;continue;}s[i]='\0';return;}++i;}}
static int b2026_asm_ident0(int c){return isalpha((unsigned char)c)||c=='_'||c=='.'||c=='@'||c=='?';}
static int b2026_asm_ident(int c){return isalnum((unsigned char)c)||c=='_'||c=='.'||c=='@'||c=='?';}
static void b2026_asm_fail(B2026Assembler*a,const char*fmt,...){va_list ap;if(a->errors>=B2026_ASM_MAX_DIAGNOSTICS)return;++a->errors;if(a->message[0])return;va_start(ap,fmt);vsnprintf(a->message,sizeof(a->message),fmt,ap);va_end(ap);}
static B2026AsmSymbol*b2026_asm_symbol(B2026Assembler*a,const char*name,int create){int i;char up[128];size_t n=strlen(name);if(n<1U||n>=sizeof(up))return NULL;memcpy(up,name,n+1U);b2026_asm_upper(up);for(i=0;i<a->symbol_count;++i)if(!strcmp(a->symbols[i].name,up))return &a->symbols[i];if(!create||a->symbol_count>=B2026_ASM_MAX_SYMBOLS)return NULL;if(a->symbol_count==a->symbol_cap){int cap=a->symbol_cap?a->symbol_cap*2:128;B2026AsmSymbol*p=(B2026AsmSymbol*)realloc(a->symbols,(size_t)cap*sizeof(*p));if(p==NULL)return NULL;a->symbols=p;a->symbol_cap=cap;}i=a->symbol_count++;memset(&a->symbols[i],0,sizeof(a->symbols[i]));a->symbols[i].name=b2026_asm_strdup(up);return a->symbols[i].name?&a->symbols[i]:NULL;}

typedef struct {B2026Assembler*a;const char*p;int known;} B2026AsmExpr;
static long b2026_asm_expr_or(B2026AsmExpr*e);
static void b2026_asm_espace(B2026AsmExpr*e){while(isspace((unsigned char)*e->p))++e->p;}
static long b2026_asm_primary(B2026AsmExpr*e){long v=0;char id[128];int n=0,base=10;const char*s;B2026AsmSymbol*sym;b2026_asm_espace(e);if(*e->p=='('){++e->p;v=b2026_asm_expr_or(e);b2026_asm_espace(e);if(*e->p==')')++e->p;else b2026_asm_fail(e->a,"line %d: missing ')'",e->a->line);return v;}if(*e->p=='$'&&!isxdigit((unsigned char)e->p[1])){++e->p;return (long)e->a->pc;}if(*e->p=='\''&&e->p[1]&&e->p[2]=='\''){v=(unsigned char)e->p[1];e->p+=3;return v;}if(b2026_asm_ident0((unsigned char)*e->p)){while(b2026_asm_ident((unsigned char)*e->p)&&n<127)id[n++]=*e->p++;id[n]='\0';b2026_asm_upper(id);b2026_asm_espace(e);if((!strcmp(id,"LOW")||!strcmp(id,"HIGH"))&&*e->p=='('){++e->p;v=b2026_asm_expr_or(e);b2026_asm_espace(e);if(*e->p==')')++e->p;else b2026_asm_fail(e->a,"line %d: missing ')'",e->a->line);return !strcmp(id,"LOW")?(v&255):((v>>8)&255);}sym=b2026_asm_symbol(e->a,id,0);if(sym==NULL||!sym->defined){if(e->a->pass==2)b2026_asm_fail(e->a,"line %d: undefined symbol %s",e->a->line,id);e->known=0;return 0;}return sym->value;}s=e->p;if(*s=='$'){base=16;++s;}else if(s[0]=='&'&&(s[1]=='H'||s[1]=='h')){base=16;s+=2;}else if(s[0]=='0'&&(s[1]=='x'||s[1]=='X')){base=16;s+=2;}else if(s[0]=='%' ){base=2;++s;}else if(s[0]=='0'&&(s[1]=='b'||s[1]=='B')){base=2;s+=2;}if(isxdigit((unsigned char)*s)){char num[128];int k=0;while((isalnum((unsigned char)*s)||*s=='_')&&k<127){if(*s!='_')num[k++]=*s;s++;}num[k]='\0';if(k>1&&(num[k-1]=='H'||num[k-1]=='h')){num[--k]='\0';base=16;}else if(k>1&&(num[k-1]=='B'||num[k-1]=='b')&&base==10){num[--k]='\0';base=2;}v=strtol(num,NULL,base);e->p=s;return v;}b2026_asm_fail(e->a,"line %d: invalid expression",e->a->line);e->known=0;return 0;}
static long b2026_asm_unary(B2026AsmExpr*e){b2026_asm_espace(e);if(*e->p=='+'){++e->p;return b2026_asm_unary(e);}if(*e->p=='-'){++e->p;return -b2026_asm_unary(e);}if(*e->p=='~'){++e->p;return ~b2026_asm_unary(e);}return b2026_asm_primary(e);}
static long b2026_asm_mul(B2026AsmExpr*e){long v=b2026_asm_unary(e),r;char op;for(;;){b2026_asm_espace(e);op=*e->p;if(op!='*'&&op!='/'&&op!='%')break;++e->p;r=b2026_asm_unary(e);if((op=='/'||op=='%')&&r==0){b2026_asm_fail(e->a,"line %d: division by zero",e->a->line);r=1;}v=op=='*'?v*r:(op=='/'?v/r:v%r);}return v;}
static long b2026_asm_add(B2026AsmExpr*e){long v=b2026_asm_mul(e);for(;;){long r;b2026_asm_espace(e);if(*e->p!='+'&&*e->p!='-')break;if(*e->p++=='+'){r=b2026_asm_mul(e);v+=r;}else{r=b2026_asm_mul(e);v-=r;}}return v;}
static long b2026_asm_shift(B2026AsmExpr*e){long v=b2026_asm_add(e);for(;;){b2026_asm_espace(e);if(e->p[0]=='<'&&e->p[1]=='<'){e->p+=2;v<<=b2026_asm_add(e);}else if(e->p[0]=='>'&&e->p[1]=='>'){e->p+=2;v>>=b2026_asm_add(e);}else break;}return v;}
static long b2026_asm_and(B2026AsmExpr*e){long v=b2026_asm_shift(e);for(;;){b2026_asm_espace(e);if(*e->p!='&')break;++e->p;v&=b2026_asm_shift(e);}return v;}
static long b2026_asm_xor(B2026AsmExpr*e){long v=b2026_asm_and(e);for(;;){b2026_asm_espace(e);if(*e->p!='^')break;++e->p;v^=b2026_asm_and(e);}return v;}
static long b2026_asm_expr_or(B2026AsmExpr*e){long v=b2026_asm_xor(e);for(;;){b2026_asm_espace(e);if(*e->p!='|')break;++e->p;v|=b2026_asm_xor(e);}return v;}
static long b2026_asm_eval(B2026Assembler*a,const char*s,int*known){B2026AsmExpr e;e.a=a;e.p=s;e.known=1;{long v=b2026_asm_expr_or(&e);b2026_asm_espace(&e);if(*e.p&&*e.p!=','&&*e.p!=')')b2026_asm_fail(a,"line %d: invalid expression tail",a->line);*known=e.known;return v;}}

static int b2026_asm_reserve(B2026Assembler*a,size_t extra){size_t need=a->byte_count+extra,cap;unsigned char*p;if(need>65536U){b2026_asm_fail(a,"line %d: output exceeds 64KiB",a->line);return 0;}if(a->pass==1)return 1;if(need<=a->byte_cap)return 1;cap=a->byte_cap?a->byte_cap:256U;while(cap<need)cap*=2U;p=(unsigned char*)realloc(a->bytes,cap);if(p==NULL){b2026_asm_fail(a,"line %d: out of memory",a->line);return 0;}a->bytes=p;a->byte_cap=cap;return 1;}
static void b2026_asm_emit(B2026Assembler*a,int v){if(!b2026_asm_reserve(a,1U))return;if(a->pass==2)a->bytes[a->byte_count]=(unsigned char)(v&255);++a->byte_count;++a->pc;if(a->pc>0)a->segment_end=a->pc-1U;}
static void b2026_asm_word(B2026Assembler*a,long v){b2026_asm_emit(a,(int)v);b2026_asm_emit(a,(int)(v>>8));}
static void b2026_asm_u8(B2026Assembler*a,long v,int known){if(a->pass==2&&known&&(v<0||v>255))b2026_asm_fail(a,"line %d: 8-bit value out of range",a->line);b2026_asm_emit(a,(int)v);}
static void b2026_asm_s8(B2026Assembler*a,long v,int known){if(a->pass==2&&known&&(v<-128||v>127))b2026_asm_fail(a,"line %d: signed displacement out of range",a->line);b2026_asm_emit(a,(int)v);}
static void b2026_asm_u16(B2026Assembler*a,long v,int known){if(a->pass==2&&known&&(v<0||v>65535))b2026_asm_fail(a,"line %d: 16-bit value out of range",a->line);b2026_asm_word(a,v);}

static int b2026_asm_split(char*s,char*out[],int max){int n=0,depth=0,quote=0;char*p=s,*start=s;for(;;){char c=*p;if(c=='"')quote=!quote;else if(!quote&&c=='(')++depth;else if(!quote&&c==')'&&depth>0)--depth;if((c==','&&!quote&&depth==0)||c=='\0'){if(n<max){*p='\0';out[n++]=b2026_asm_trim(start);}start=p+1;if(c=='\0')break;}++p;}return n;}
static int b2026_asm_r8(const char*s){static const char*n[]={"B","C","D","E","H","L","(HL)","A"};int i;for(i=0;i<8;++i)if(b2026_asm_ieq(s,n[i]))return i;return -1;}
static int b2026_asm_rp(const char*s){if(b2026_asm_ieq(s,"BC"))return 0;if(b2026_asm_ieq(s,"DE"))return 1;if(b2026_asm_ieq(s,"HL"))return 2;if(b2026_asm_ieq(s,"SP"))return 3;return -1;}
static int b2026_asm_rp2(const char*s){if(b2026_asm_ieq(s,"BC"))return 0;if(b2026_asm_ieq(s,"DE"))return 1;if(b2026_asm_ieq(s,"HL"))return 2;if(b2026_asm_ieq(s,"AF"))return 3;return -1;}
static int b2026_asm_cc(const char*s){static const char*n[]={"NZ","Z","NC","C","PO","PE","P","M"};int i;for(i=0;i<8;++i)if(b2026_asm_ieq(s,n[i]))return i;return -1;}
static int b2026_asm_indexed(B2026Assembler*a,const char*s,int*prefix,long*disp,int*known){size_t n=strlen(s);char tmp[256],*p;if(n<4U||n>=sizeof(tmp)||s[0]!='('||s[n-1]!=')')return 0;memcpy(tmp,s+1,n-2);tmp[n-2]='\0';p=b2026_asm_trim(tmp);if(!b2026_strncasecmp(p,"IX",2)){*prefix=0xDD;p+=2;}else if(!b2026_strncasecmp(p,"IY",2)){*prefix=0xFD;p+=2;}else return 0;p=b2026_asm_trim(p);if(!*p){*disp=0;*known=1;return 1;}*disp=b2026_asm_eval(a,p,known);return 1;}
static int b2026_asm_abs_mem(const char*s,char*out,size_t cap){size_t n=strlen(s);if(n<3U||s[0]!='('||s[n-1]!=')'||n-1U>=cap)return 0;memcpy(out,s+1,n-2U);out[n-2U]='\0';return 1;}
static int b2026_asm_single(const char*m){struct P{const char*n;int op;}static const p[]={{"NOP",0x00},{"RLCA",0x07},{"RRCA",0x0F},{"RLA",0x17},{"RRA",0x1F},{"DAA",0x27},{"CPL",0x2F},{"SCF",0x37},{"CCF",0x3F},{"HALT",0x76},{"EXX",0xD9},{"DI",0xF3},{"EI",0xFB},{"RET",0xC9}};size_t i;for(i=0;i<sizeof(p)/sizeof(p[0]);++i)if(!strcmp(m,p[i].n))return p[i].op;return -1;}
static int b2026_asm_ed(const char*m){struct P{const char*n;int op;}static const p[]={{"NEG",0x44},{"RETN",0x45},{"RETI",0x4D},{"RRD",0x67},{"RLD",0x6F},{"LDI",0xA0},{"CPI",0xA1},{"INI",0xA2},{"OUTI",0xA3},{"LDD",0xA8},{"CPD",0xA9},{"IND",0xAA},{"OUTD",0xAB},{"LDIR",0xB0},{"CPIR",0xB1},{"INIR",0xB2},{"OTIR",0xB3},{"LDDR",0xB8},{"CPDR",0xB9},{"INDR",0xBA},{"OTDR",0xBB}};size_t i;for(i=0;i<sizeof(p)/sizeof(p[0]);++i)if(!strcmp(m,p[i].n))return p[i].op;return -1;}

static void b2026_asm_instruction(B2026Assembler*a,char*m,char*ops){char*o[3]={0};int n=ops&&*ops?b2026_asm_split(ops,o,3):0;int op=b2026_asm_single(m),ed=b2026_asm_ed(m),r,s,rr,cc,k,pfx;long v,d;char mem[256];if(op>=0&&n==0){b2026_asm_emit(a,op);return;}if(ed>=0&&n==0){b2026_asm_emit(a,0xED);b2026_asm_emit(a,ed);return;}
    if(!strcmp(m,"LD")&&n==2){r=b2026_asm_r8(o[0]);s=b2026_asm_r8(o[1]);if(r>=0&&s>=0){if(r==6&&s==6)goto bad;b2026_asm_emit(a,0x40+r*8+s);return;}if(r>=0){if(r!=6&&b2026_asm_indexed(a,o[1],&pfx,&d,&k)){b2026_asm_emit(a,pfx);b2026_asm_emit(a,0x46+r*8);b2026_asm_s8(a,d,k);return;}if(r==7&&b2026_asm_ieq(o[1],"(BC)")){b2026_asm_emit(a,0x0A);return;}if(r==7&&b2026_asm_ieq(o[1],"(DE)")){b2026_asm_emit(a,0x1A);return;}if(b2026_asm_abs_mem(o[1],mem,sizeof(mem))){v=b2026_asm_eval(a,mem,&k);if(r==7){b2026_asm_emit(a,0x3A);b2026_asm_u16(a,v,k);return;}}v=b2026_asm_eval(a,o[1],&k);b2026_asm_emit(a,0x06+r*8);b2026_asm_u8(a,v,k);return;}if(s>=0){if(s!=6&&b2026_asm_indexed(a,o[0],&pfx,&d,&k)){b2026_asm_emit(a,pfx);b2026_asm_emit(a,0x70+s);b2026_asm_s8(a,d,k);return;}if(s==7&&b2026_asm_ieq(o[0],"(BC)")){b2026_asm_emit(a,0x02);return;}if(s==7&&b2026_asm_ieq(o[0],"(DE)")){b2026_asm_emit(a,0x12);return;}if(s==7&&b2026_asm_abs_mem(o[0],mem,sizeof(mem))){v=b2026_asm_eval(a,mem,&k);b2026_asm_emit(a,0x32);b2026_asm_u16(a,v,k);return;}}
        if(b2026_asm_ieq(o[0],"SP")&&(b2026_asm_ieq(o[1],"HL")||b2026_asm_ieq(o[1],"IX")||b2026_asm_ieq(o[1],"IY"))){if(!b2026_asm_ieq(o[1],"HL"))b2026_asm_emit(a,b2026_asm_ieq(o[1],"IX")?0xDD:0xFD);b2026_asm_emit(a,0xF9);return;}
        if(b2026_asm_indexed(a,o[0],&pfx,&d,&k)){v=b2026_asm_eval(a,o[1],&k);b2026_asm_emit(a,pfx);b2026_asm_emit(a,0x36);b2026_asm_s8(a,d,k);b2026_asm_u8(a,v,k);return;}
        rr=b2026_asm_rp(o[0]);if(rr>=0){if(b2026_asm_abs_mem(o[1],mem,sizeof(mem))){v=b2026_asm_eval(a,mem,&k);if(rr==2)b2026_asm_emit(a,0x2A);else{b2026_asm_emit(a,0xED);b2026_asm_emit(a,0x4B+rr*0x10);}b2026_asm_u16(a,v,k);return;}v=b2026_asm_eval(a,o[1],&k);b2026_asm_emit(a,0x01+rr*0x10);b2026_asm_u16(a,v,k);return;}rr=b2026_asm_rp(o[1]);if(rr>=0&&b2026_asm_abs_mem(o[0],mem,sizeof(mem))){v=b2026_asm_eval(a,mem,&k);if(rr==2)b2026_asm_emit(a,0x22);else{b2026_asm_emit(a,0xED);b2026_asm_emit(a,0x43+rr*0x10);}b2026_asm_u16(a,v,k);return;}if((b2026_asm_ieq(o[0],"IX")||b2026_asm_ieq(o[0],"IY"))){pfx=b2026_asm_ieq(o[0],"IX")?0xDD:0xFD;if(b2026_asm_abs_mem(o[1],mem,sizeof(mem))){v=b2026_asm_eval(a,mem,&k);b2026_asm_emit(a,pfx);b2026_asm_emit(a,0x2A);b2026_asm_u16(a,v,k);}else{v=b2026_asm_eval(a,o[1],&k);b2026_asm_emit(a,pfx);b2026_asm_emit(a,0x21);b2026_asm_u16(a,v,k);}return;}if((b2026_asm_ieq(o[1],"IX")||b2026_asm_ieq(o[1],"IY"))&&b2026_asm_abs_mem(o[0],mem,sizeof(mem))){pfx=b2026_asm_ieq(o[1],"IX")?0xDD:0xFD;v=b2026_asm_eval(a,mem,&k);b2026_asm_emit(a,pfx);b2026_asm_emit(a,0x22);b2026_asm_u16(a,v,k);return;}if(b2026_asm_ieq(o[0],"I")&&b2026_asm_ieq(o[1],"A")){b2026_asm_emit(a,0xED);b2026_asm_emit(a,0x47);return;}if(b2026_asm_ieq(o[0],"R")&&b2026_asm_ieq(o[1],"A")){b2026_asm_emit(a,0xED);b2026_asm_emit(a,0x4F);return;}if(b2026_asm_ieq(o[0],"A")&&b2026_asm_ieq(o[1],"I")){b2026_asm_emit(a,0xED);b2026_asm_emit(a,0x57);return;}if(b2026_asm_ieq(o[0],"A")&&b2026_asm_ieq(o[1],"R")){b2026_asm_emit(a,0xED);b2026_asm_emit(a,0x5F);return;}}
    if((!strcmp(m,"INC")||!strcmp(m,"DEC"))&&n==1){int dec=!strcmp(m,"DEC");r=b2026_asm_r8(o[0]);if(r>=0){b2026_asm_emit(a,(dec?0x05:0x04)+r*8);return;}rr=b2026_asm_rp(o[0]);if(rr>=0){b2026_asm_emit(a,(dec?0x0B:0x03)+rr*0x10);return;}if(b2026_asm_indexed(a,o[0],&pfx,&d,&k)){b2026_asm_emit(a,pfx);b2026_asm_emit(a,dec?0x35:0x34);b2026_asm_s8(a,d,k);return;}if(b2026_asm_ieq(o[0],"IX")||b2026_asm_ieq(o[0],"IY")){b2026_asm_emit(a,b2026_asm_ieq(o[0],"IX")?0xDD:0xFD);b2026_asm_emit(a,dec?0x2B:0x23);return;}}
    if(!strcmp(m,"ADD")&&n==2&&(b2026_asm_ieq(o[0],"IX")||b2026_asm_ieq(o[0],"IY"))){pfx=b2026_asm_ieq(o[0],"IX")?0xDD:0xFD;rr=b2026_asm_rp(o[1]);if(rr==2)rr=-1;if(b2026_asm_ieq(o[1],"IX")&&pfx==0xDD)rr=2;if(b2026_asm_ieq(o[1],"IY")&&pfx==0xFD)rr=2;if(rr>=0){b2026_asm_emit(a,pfx);b2026_asm_emit(a,0x09+rr*0x10);return;}}
    {static const char*alun[]={"ADD","ADC","SUB","SBC","AND","XOR","OR","CP"};static const int base[]={0x80,0x88,0x90,0x98,0xA0,0xA8,0xB0,0xB8};static const int imm[]={0xC6,0xCE,0xD6,0xDE,0xE6,0xEE,0xF6,0xFE};int ai=-1,i;for(i=0;i<8;++i)if(!strcmp(m,alun[i]))ai=i;if(ai>=0){char*x=NULL;if(n==1&&ai>=2)x=o[0];else if(n==2&&b2026_asm_ieq(o[0],"A"))x=o[1];if(x){r=b2026_asm_r8(x);if(r>=0){b2026_asm_emit(a,base[ai]+r);return;}if(b2026_asm_indexed(a,x,&pfx,&d,&k)){b2026_asm_emit(a,pfx);b2026_asm_emit(a,base[ai]+6);b2026_asm_s8(a,d,k);return;}v=b2026_asm_eval(a,x,&k);b2026_asm_emit(a,imm[ai]);b2026_asm_u8(a,v,k);return;}if(n==2&&(ai==0||ai==1||ai==3)&&b2026_asm_ieq(o[0],"HL")&&(rr=b2026_asm_rp(o[1]))>=0){if(ai==0)b2026_asm_emit(a,0x09+rr*0x10);else{b2026_asm_emit(a,0xED);b2026_asm_emit(a,(ai==1?0x4A:0x42)+rr*0x10);}return;}}}
    if((!strcmp(m,"JP")||!strcmp(m,"CALL"))&&(n==1||n==2)){int call=!strcmp(m,"CALL");if(n==1){if(!call&&(b2026_asm_ieq(o[0],"(HL)")||b2026_asm_ieq(o[0],"(IX)")||b2026_asm_ieq(o[0],"(IY)"))){if(!b2026_asm_ieq(o[0],"(HL)"))b2026_asm_emit(a,b2026_asm_ieq(o[0],"(IX)")?0xDD:0xFD);b2026_asm_emit(a,0xE9);return;}v=b2026_asm_eval(a,o[0],&k);b2026_asm_emit(a,call?0xCD:0xC3);b2026_asm_u16(a,v,k);return;}cc=b2026_asm_cc(o[0]);if(cc>=0){v=b2026_asm_eval(a,o[1],&k);b2026_asm_emit(a,(call?0xC4:0xC2)+cc*8);b2026_asm_u16(a,v,k);return;}}
    if((!strcmp(m,"JR")||!strcmp(m,"DJNZ"))&&(n==1||n==2)){unsigned int after=a->pc+2U;if(!strcmp(m,"DJNZ")&&n==1){v=b2026_asm_eval(a,o[0],&k);b2026_asm_emit(a,0x10);b2026_asm_s8(a,v-(long)after,k);return;}if(n==1){v=b2026_asm_eval(a,o[0],&k);b2026_asm_emit(a,0x18);b2026_asm_s8(a,v-(long)after,k);return;}cc=b2026_asm_cc(o[0]);if(cc>=0&&cc<4){v=b2026_asm_eval(a,o[1],&k);b2026_asm_emit(a,0x20+cc*8);b2026_asm_s8(a,v-(long)after,k);return;}}
    if(!strcmp(m,"RET")&&n==1&&(cc=b2026_asm_cc(o[0]))>=0){b2026_asm_emit(a,0xC0+cc*8);return;}
    if(!strcmp(m,"RST")&&n==1){v=b2026_asm_eval(a,o[0],&k);if(a->pass==2&&k&&(v<0||v>56||(v&7)))b2026_asm_fail(a,"line %d: RST vector must be 0,8,...,56",a->line);b2026_asm_emit(a,0xC7+(int)v);return;}
    if((!strcmp(m,"PUSH")||!strcmp(m,"POP"))&&n==1){int pop=!strcmp(m,"POP");rr=b2026_asm_rp2(o[0]);if(rr>=0){b2026_asm_emit(a,(pop?0xC1:0xC5)+rr*0x10);return;}if(b2026_asm_ieq(o[0],"IX")||b2026_asm_ieq(o[0],"IY")){b2026_asm_emit(a,b2026_asm_ieq(o[0],"IX")?0xDD:0xFD);b2026_asm_emit(a,pop?0xE1:0xE5);return;}}
    if(!strcmp(m,"EX")&&n==2){if(b2026_asm_ieq(o[0],"DE")&&b2026_asm_ieq(o[1],"HL")){b2026_asm_emit(a,0xEB);return;}if(b2026_asm_ieq(o[0],"AF")&&b2026_asm_ieq(o[1],"AF'")){b2026_asm_emit(a,0x08);return;}if(b2026_asm_ieq(o[0],"(SP)")&&(b2026_asm_ieq(o[1],"HL")||b2026_asm_ieq(o[1],"IX")||b2026_asm_ieq(o[1],"IY"))){if(!b2026_asm_ieq(o[1],"HL"))b2026_asm_emit(a,b2026_asm_ieq(o[1],"IX")?0xDD:0xFD);b2026_asm_emit(a,0xE3);return;}}
    if(!strcmp(m,"IM")&&n==1){v=b2026_asm_eval(a,o[0],&k);if(v<0||v>2)goto bad;b2026_asm_emit(a,0xED);b2026_asm_emit(a,v==0?0x46:(v==1?0x56:0x5E));return;}
    if((!strcmp(m,"IN")||!strcmp(m,"OUT"))&&n==2){int out=!strcmp(m,"OUT");if((!out&&b2026_asm_ieq(o[1],"(C)"))||(out&&b2026_asm_ieq(o[0],"(C)"))){r=b2026_asm_r8(out?o[1]:o[0]);if(r>=0&&r!=6){b2026_asm_emit(a,0xED);b2026_asm_emit(a,(out?0x41:0x40)+r*8);return;}}if(!out&&b2026_asm_ieq(o[0],"A")&&b2026_asm_abs_mem(o[1],mem,sizeof(mem))){v=b2026_asm_eval(a,mem,&k);b2026_asm_emit(a,0xDB);b2026_asm_u8(a,v,k);return;}if(out&&b2026_asm_ieq(o[1],"A")&&b2026_asm_abs_mem(o[0],mem,sizeof(mem))){v=b2026_asm_eval(a,mem,&k);b2026_asm_emit(a,0xD3);b2026_asm_u8(a,v,k);return;}}
    {static const char*rot[]={"RLC","RRC","RL","RR","SLA","SRA","SRL"};static const int rb[]={0,8,16,24,32,40,56};int ri=-1,i,bitbase=-1;for(i=0;i<7;++i)if(!strcmp(m,rot[i]))ri=i;if(!strcmp(m,"BIT"))bitbase=0x40;else if(!strcmp(m,"RES"))bitbase=0x80;else if(!strcmp(m,"SET"))bitbase=0xC0;if(ri>=0&&n==1){r=b2026_asm_r8(o[0]);if(r>=0){b2026_asm_emit(a,0xCB);b2026_asm_emit(a,rb[ri]+r);return;}if(b2026_asm_indexed(a,o[0],&pfx,&d,&k)){b2026_asm_emit(a,pfx);b2026_asm_emit(a,0xCB);b2026_asm_s8(a,d,k);b2026_asm_emit(a,rb[ri]+6);return;}}if(bitbase>=0&&n==2){v=b2026_asm_eval(a,o[0],&k);if(a->pass==2&&k&&(v<0||v>7))b2026_asm_fail(a,"line %d: bit number must be 0..7",a->line);r=b2026_asm_r8(o[1]);if(r>=0){b2026_asm_emit(a,0xCB);b2026_asm_emit(a,bitbase+(int)v*8+r);return;}if(b2026_asm_indexed(a,o[1],&pfx,&d,&k)){b2026_asm_emit(a,pfx);b2026_asm_emit(a,0xCB);b2026_asm_s8(a,d,k);b2026_asm_emit(a,bitbase+(int)v*8+6);return;}}}
bad:b2026_asm_fail(a,"line %d: unsupported or invalid Z80 instruction: %s",a->line,m);
}

static int b2026_asm_define(B2026Assembler*a,const char*name,long value,int line){B2026AsmSymbol*s=b2026_asm_symbol(a,name,1);if(s==NULL){b2026_asm_fail(a,"line %d: invalid label or too many labels",line);return 0;}if(s->defined&&s->line!=line){b2026_asm_fail(a,"line %d: duplicate label %s",line,name);return 0;}s->value=value;s->defined=1;s->line=line;return 1;}
static void b2026_asm_data(B2026Assembler*a,char*ops,int word){char*items[512];int n=b2026_asm_split(ops,items,512),i,k;for(i=0;i<n;++i){char*x=b2026_asm_trim(items[i]);size_t len=strlen(x);if(!word&&len>=2U&&x[0]=='"'&&x[len-1]=='"'){size_t j;for(j=1;j+1<len;++j){if(x[j]=='\\'&&j+2<len){++j;if(x[j]=='n')b2026_asm_emit(a,'\n');else if(x[j]=='r')b2026_asm_emit(a,'\r');else if(x[j]=='t')b2026_asm_emit(a,'\t');else b2026_asm_emit(a,(unsigned char)x[j]);}else b2026_asm_emit(a,(unsigned char)x[j]);}}else{long v=b2026_asm_eval(a,x,&k);if(word)b2026_asm_u16(a,v,k);else b2026_asm_u8(a,v,k);}}}
static void b2026_asm_line(B2026Assembler*a,char*line){char*p,*q,*mn,*ops="",label[128];int k;long v;b2026_asm_strip_comment(line);p=b2026_asm_trim(line);if(!*p)return;q=p;if(b2026_asm_ident0((unsigned char)*q)){while(b2026_asm_ident((unsigned char)*q))++q;if(*q==':'){size_t n=(size_t)(q-p);if(n>=sizeof(label)){b2026_asm_fail(a,"line %d: label too long",a->line);return;}memcpy(label,p,n);label[n]='\0';if(a->pass==1)b2026_asm_define(a,label,(long)a->pc,a->line);p=b2026_asm_trim(q+1);if(!*p)return;}else{char*after=b2026_asm_trim(q);if(!b2026_strncasecmp(after,"EQU",3)&&(after[3]=='\0'||isspace((unsigned char)after[3]))){size_t n=(size_t)(q-p);memcpy(label,p,n);label[n]='\0';v=b2026_asm_eval(a,b2026_asm_trim(after+3),&k);if(a->pass!=2&&k)b2026_asm_define(a,label,v,a->line);return;}if(*after=='='){size_t n=(size_t)(q-p);memcpy(label,p,n);label[n]='\0';v=b2026_asm_eval(a,b2026_asm_trim(after+1),&k);if(a->pass!=2&&k)b2026_asm_define(a,label,v,a->line);return;}}}mn=p;while(*p&&!isspace((unsigned char)*p))++p;if(*p){*p++='\0';ops=b2026_asm_trim(p);}b2026_asm_upper(mn);
    if(!strcmp(mn,"ORG")){v=b2026_asm_eval(a,ops,&k);if(!k){b2026_asm_fail(a,"line %d: ORG must be resolvable",a->line);return;}if(v<0||v>65535){b2026_asm_fail(a,"line %d: ORG out of range",a->line);return;}if(!a->first_org_seen){a->first_org_seen=1;if((unsigned int)v!=a->segment_start){b2026_asm_fail(a,"line %d: first ORG must match the computed segment start",a->line);return;}}if((unsigned int)v<a->pc){b2026_asm_fail(a,"line %d: backward ORG is not allowed",a->line);return;}while(a->pc<(unsigned int)v)b2026_asm_emit(a,a->fill);return;}
    if(!strcmp(mn,"DB")||!strcmp(mn,"DEFB")||!strcmp(mn,"BYTE")){b2026_asm_data(a,ops,0);return;}if(!strcmp(mn,"DW")||!strcmp(mn,"DEFW")||!strcmp(mn,"WORD")){b2026_asm_data(a,ops,1);return;}
    if(!strcmp(mn,"DS")||!strcmp(mn,"DEFS")||!strcmp(mn,"RESB")){char*x[2];int n=b2026_asm_split(ops,x,2),i;v=b2026_asm_eval(a,x[0],&k);if(!k||v<0||v>65536){b2026_asm_fail(a,"line %d: invalid reserve size",a->line);return;}for(i=0;i<v;++i)b2026_asm_emit(a,n>1?(int)b2026_asm_eval(a,x[1],&k):(int)a->fill);return;}
    if(!strcmp(mn,"ALIGN")){char*x[2];int n=b2026_asm_split(ops,x,2);v=b2026_asm_eval(a,x[0],&k);if(!k||v<1||v>65536){b2026_asm_fail(a,"line %d: invalid alignment",a->line);return;}while(a->pc%(unsigned int)v)b2026_asm_emit(a,n>1?(int)b2026_asm_eval(a,x[1],&k):(int)a->fill);return;}
    if(!strcmp(mn,"END")){if(*ops){v=b2026_asm_eval(a,ops,&k);if(a->pass==2&&k){if(v<0||v>65535)b2026_asm_fail(a,"line %d: END address out of range",a->line);else a->execution=(unsigned int)v;}}a->ended=1;return;}b2026_asm_instruction(a,mn,ops);
}
static int b2026_asm_pass(B2026Assembler*a,const char*source,int pass){char*copy,*p,*next;size_t n=strlen(source);copy=b2026_asm_strdup(source);if(copy==NULL){b2026_asm_fail(a,"out of memory");return 0;}a->pass=pass;a->pc=a->segment_start;a->byte_count=0;a->line=0;a->ended=0;a->first_org_seen=0;a->segment_end=a->segment_start? a->segment_start-1U:0U;p=copy;while(p&&*p&&!a->ended&&a->errors<B2026_ASM_MAX_DIAGNOSTICS){++a->line;next=strchr(p,'\n');if(next)*next++='\0';{size_t l=strlen(p);if(l&&p[l-1]=='\r')p[l-1]='\0';}b2026_asm_line(a,p);p=next;}free(copy);(void)n;return a->errors==0;}

static void b2026_asm_pair_n(const char*t,const char*j,const char*e,double v){hash_set_number(t,j,v);hash_set_number(t,e,v);}static void b2026_asm_pair_s(const char*t,const char*j,const char*e,const char*v){hash_set_string(t,j,v?v:"");hash_set_string(t,e,v?v:"");}
static void b2026_asm_result_clear(const char*t){HashVariable*h=ensure_local_hash(get_hash_frame(t),t);clear_hash_entries(h);}
static int b2026_asm_replace_array(const char*name,const unsigned char*old,size_t old_n,const unsigned char*add,size_t add_n){size_t total=old_n+add_n,i,alloc;int upper=option_base+(int)total-1,*ref;B2026_Var*data;ArrayVariable*target;if(total>65536U)return 0;alloc=upper>=0?(size_t)upper+1U:1U;data=(B2026_Var*)calloc(alloc,sizeof(*data));if(data==NULL)return 0;ref=(int*)malloc(sizeof(int));if(ref==NULL){free(data);return 0;}*ref=1;for(i=0;i<old_n;++i)set_b2026_number(&data[option_base+(int)i],old[i]);for(i=0;i<add_n;++i)set_b2026_number(&data[option_base+(int)(old_n+i)],add[i]);target=ensure_local_array(get_array_frame(name),name);release_array_storage(target);target->data=data;target->ref_count_ptr=ref;target->size=upper;target->read_count=(int)total;target->bypass_cow=0;return 1;}
static int b2026_asm_existing(const char*name,unsigned char**out,size_t*n){ArrayVariable*v=get_array_variable(name);size_t i,count;*out=NULL;*n=0;if(v==NULL||v->data==NULL||v->size<option_base)return 1;count=(size_t)(v->size-option_base+1);*out=(unsigned char*)malloc(count?count:1U);if(*out==NULL)return 0;for(i=0;i<count;++i){B2026_Var*x=&v->data[option_base+(int)i];double d;if(b2026_var_is_string(x)||b2026_var_is_null(x)){free(*out);*out=NULL;return 0;}d=b2026_var_as_number(x);if(!isfinite(d)||floor(d)!=d||d<0||d>255){free(*out);*out=NULL;return 0;}(*out)[i]=(unsigned char)d;}*n=count;return 1;}
static int b2026_asm_mode_append(const char*mode){return mode&&(b2026_asm_ieq(mode,"APPEND")||!strcmp(mode,"追加")||!strcmp(mode,"追記"));}
static void b2026_asm_log(const char*msg){char text[1024];snprintf(text,sizeof(text),"ASSEMBLE failed: %s / 【ASSEMBLEエラー】%s",msg?msg:"assembly failed",msg?msg:"アセンブルできません");basic_set_error_logged(4801,text,"ASSEMBLE",current_source_path,current_line,"ASSEMBLE");b2026_log_message_grouped("__ASSEMBLE_ERROR.log",text);}
static int b2026_assemble(const char*target,const char*cpu,const char*array_name,double origin_value,const char*source,const char*mode,const char*options_name){B2026Assembler a;unsigned char*old=NULL;size_t old_n=0;int append=0,ok=0,i;HashVariable*opts=NULL;HashEntry*e;memset(&a,0,sizeof(a));basic_clear_error();b2026_asm_result_clear(target);b2026_asm_pair_n(target,"成功","ok",0);b2026_asm_pair_s(target,"状態","status","FAILED");b2026_asm_pair_s(target,"CPU","cpu",cpu?cpu:"");if(cpu==NULL||!b2026_asm_ieq(cpu,"Z80")){b2026_asm_fail(&a,"unsupported CPU; initial version supports Z80 only");goto done;}if(source==NULL||!*source||strlen(source)>B2026_ASM_MAX_SOURCE){b2026_asm_fail(&a,"source is empty or exceeds 16 MiB");goto done;}if(!isfinite(origin_value)||floor(origin_value)!=origin_value||origin_value<0||origin_value>65535){b2026_asm_fail(&a,"origin must be an integer from 0 to 65535");goto done;}append=b2026_asm_mode_append(mode);if(mode&&*mode&&!append&&!b2026_asm_ieq(mode,"OVERWRITE")&&strcmp(mode,"上書き")){b2026_asm_fail(&a,"output mode must be overwrite/append");goto done;}if(options_name){opts=find_local_hash(get_hash_frame(options_name),options_name);if(opts==NULL){b2026_asm_fail(&a,"options associative array does not exist");goto done;}e=find_hash_entry(opts,"出力方法");if(e==NULL)e=find_hash_entry(opts,"outputMode");if(e!=NULL){const char*x=hash_value_as_text(&e->value);append=b2026_asm_mode_append(x);if(!append&&!b2026_asm_ieq(x,"OVERWRITE")&&strcmp(x,"上書き")){b2026_asm_fail(&a,"invalid outputMode option");goto done;}}e=find_hash_entry(opts,"充填値");if(e==NULL)e=find_hash_entry(opts,"fillValue");if(e!=NULL){double d=hash_value_as_number(&e->value);if(!isfinite(d)||floor(d)!=d||d<0||d>255){b2026_asm_fail(&a,"fill value must be 0..255");goto done;}a.fill=(unsigned int)d;}}
    if(append&&!b2026_asm_existing(array_name,&old,&old_n)){b2026_asm_fail(&a,"existing output array must contain only byte values");goto done;}a.base=(unsigned int)origin_value;a.segment_start=a.base+(unsigned int)old_n;if(a.segment_start>65535U){b2026_asm_fail(&a,"append start exceeds 65535");goto done;}a.execution=a.base;
    if(opts){for(e=opts->entries;e;e=e->next){const char*key=e->key;const char*name=NULL;if(!strncmp(key,"定義,",7))name=key+7;else if(!b2026_strncasecmp(key,"DEFINE,",7))name=key+7;if(name&&*name){double d=hash_value_as_number(&e->value);if(!isfinite(d)||floor(d)!=d||d<-2147483647.0||d>2147483647.0||!b2026_asm_define(&a,name,(long)d,0)){b2026_asm_fail(&a,"invalid predefined symbol");goto done;}}}}
    if(!b2026_asm_pass(&a,source,1))goto done;if(!b2026_asm_pass(&a,source,0))goto done;if(!b2026_asm_pass(&a,source,2))goto done;if(a.byte_count==0){b2026_asm_fail(&a,"no machine-code bytes were generated");goto done;}if(old_n+a.byte_count>65536U){b2026_asm_fail(&a,"combined output exceeds 64KiB");goto done;}if(!b2026_asm_replace_array(array_name,append?old:NULL,append?old_n:0,a.bytes,a.byte_count)){b2026_asm_fail(&a,"out of memory while committing output array");goto done;}ok=1;b2026_asm_result_clear(target);b2026_asm_pair_n(target,"成功","ok",1);b2026_asm_pair_s(target,"状態","status","OK");b2026_asm_pair_s(target,"CPU","cpu","Z80");b2026_asm_pair_n(target,"生成バイト数","generatedBytes",(double)a.byte_count);b2026_asm_pair_n(target,"合計バイト数","totalBytes",(double)(old_n+a.byte_count));b2026_asm_pair_n(target,"開始アドレス","startAddress",(double)a.base);b2026_asm_pair_n(target,"終了アドレス","endAddress",(double)(a.base+old_n+a.byte_count-(old_n+a.byte_count?1U:0U)));b2026_asm_pair_n(target,"セグメント開始アドレス","segmentStart",(double)a.segment_start);b2026_asm_pair_n(target,"セグメント終了アドレス","segmentEnd",(double)a.segment_end);b2026_asm_pair_n(target,"実行アドレス","executionAddress",(double)a.execution);b2026_asm_pair_n(target,"ラベル数","labelCount",(double)a.symbol_count);b2026_asm_pair_n(target,"エラー数","errorCount",0);b2026_asm_pair_n(target,"警告数","warningCount",(double)a.warnings);b2026_asm_pair_s(target,"メッセージ","message","assembled successfully / アセンブルしました");for(i=0;i<a.symbol_count;++i){char key[256];snprintf(key,sizeof(key),"ラベル,%s",a.symbols[i].name);hash_set_number(target,key,(double)a.symbols[i].value);snprintf(key,sizeof(key),"label,%s",a.symbols[i].name);hash_set_number(target,key,(double)a.symbols[i].value);}
done:if(!ok){b2026_asm_pair_n(target,"エラー数","errorCount",a.errors?a.errors:1);b2026_asm_pair_n(target,"警告数","warningCount",a.warnings);b2026_asm_pair_s(target,"メッセージ","message",a.message[0]?a.message:"assembly failed");if(!append)b2026_asm_replace_array(array_name,NULL,0,NULL,0);b2026_asm_log(a.message[0]?a.message:"assembly failed");}for(i=0;i<a.symbol_count;++i)free(a.symbols[i].name);free(a.symbols);free(a.bytes);free(old);return ok;}
