/* grammar.h — draft grammaticale (#48): GBNF (sottoinsieme) valutata a livello di BYTE. * * Idea: nei workload a output vincolato (JSON/NDJSON, function calling, estrazione * strutturata) una frazione dei token e' DETERMINISTICA data la grammatica: parentesi, * virgolette, nomi delle chiavi, separatori, valori enum. Quegli span sono draft * gratuiti ad acceptance ~1: nessuna testa, nessuna lookup table — la verifica * batch-union li conferma e paga UN forward per piu' token. E si aggancia anche dove * la testa MTP int4 non parte (#8). * * La grammatica non vincola MAI il campionamento: propone solo draft, che la verifica * accetta o rifiuta come qualunque altro draft. Grammatica sbagliata o fuori sync => * draft rifiutati, output IDENTICO. E' un acceleratore puro, mai un filtro. * * Sottoinsieme GBNF (stile llama.cpp), valutato sui BYTE: * root ::= obj+ # la regola di partenza si chiama "root" * obj ::= "{" pair ("," pair)* "}" "\n" * str ::= "\"" [^"\\]* "\"" * Supportato: letterali "..." (escape \" \\ \n \r \t \xHH), classi [a-z0-9-] anche * negate [^...], riferimenti a regole, gruppi (...), postfissi ? * +, commenti #, * alternate con |, epsilon come "". Le regole possono estendersi su piu' righe: una * nuova regola inizia dove un identificatore e' seguito da "::=". * NON supportato: ripetizioni {m,n}, range unicode nelle classi (le classi lavorano * sui byte; per l'UTF-8 multibyte usare i letterali, che passano i byte grezzi). * Ricorsione sinistra: intercettata dal tetto di profondita' -> il walker si spegne * (alive=0) e la generazione prosegue senza draft. Mai un blocco, mai un crash. * * Il walker e' un PDA con INSIEME di stack (come llama.cpp): ogni stack in forma * normale ha in cima un simbolo terminale (classe di byte) oppure e' vuoto (parse * completabile qui). gr_forced() estende il prefisso finche' esiste UN SOLO byte * legale e il parse non e' terminabile: quel prefisso e' il draft forzato. */ #ifndef COLI_GRAMMAR_H #define COLI_GRAMMAR_H #include #include #include #include #define GR_MAX_RULES 1024 #define GR_MAX_STACKS 64 /* ambiguita' massima seguita in parallelo */ #define GR_MAX_DEPTH 64 /* profondita' massima di uno stack del PDA */ typedef struct { uint8_t bits[32]; } GrCls; /* insieme di byte ammessi */ enum { GR_CLS = 0, GR_REF = 1 }; typedef struct { uint8_t t; int16_t ref; GrCls c; } GrSym; typedef struct { GrSym *s; int n, cap; } GrAlt; /* una sequenza di simboli */ typedef struct { GrAlt *a; int n, cap; char name[64]; } GrRule; typedef struct { GrRule r[GR_MAX_RULES]; int n; int root; char err[160]; } Grammar; /* frame = posizione dentro un alternate: (regola, alternate, simbolo) */ typedef struct { int16_t r, a, s; } GrFrame; typedef struct { GrFrame f[GR_MAX_DEPTH]; int16_t n; } GrStack; typedef struct { Grammar *G; GrStack st[GR_MAX_STACKS]; int n; int alive; } GrState; /* ---------- costruzione ---------- */ static int gr__alt_new(Grammar *G, int ri){ GrRule *R=&G->r[ri]; if(R->n==R->cap){ int nc=R->cap?R->cap*2:4; GrAlt *na=(GrAlt*)realloc(R->a,(size_t)nc*sizeof(GrAlt)); if(!na) return -1; R->a=na; R->cap=nc; } memset(&R->a[R->n],0,sizeof(GrAlt)); return R->n++; } static int gr__push(Grammar *G, int ri, int ai, const GrSym *sy){ GrAlt *A=&G->r[ri].a[ai]; if(A->n==A->cap){ int nc=A->cap?A->cap*2:8; GrSym *ns=(GrSym*)realloc(A->s,(size_t)nc*sizeof(GrSym)); if(!ns) return -1; A->s=ns; A->cap=nc; } A->s[A->n++]=*sy; return 0; } static int gr__rule(Grammar *G, const char *name, int len){ if(len>63) len=63; for(int i=0;in;i++) if((int)strlen(G->r[i].name)==len && !memcmp(G->r[i].name,name,(size_t)len)) return i; if(G->n>=GR_MAX_RULES) return -1; GrRule *R=&G->r[G->n]; memset(R,0,sizeof *R); memcpy(R->name,name,(size_t)len); return G->n++; } static int gr__anon(Grammar *G){ /* regola sintetica ($n non collide: '$' non e' un identificatore */ if(G->n>=GR_MAX_RULES) return -1; GrRule *R=&G->r[G->n]; memset(R,0,sizeof *R); snprintf(R->name,sizeof R->name,"$%d",G->n); return G->n++; } /* ---------- parser GBNF ---------- */ static const char* gr__ws(const char *p){ for(;;){ while(*p==' '||*p=='\t'||*p=='\r'||*p=='\n') p++; if(*p=='#'){ while(*p && *p!='\n') p++; continue; } return p; } } static int gr__idch(char c){ return (c>='a'&&c<='z')||(c>='A'&&c<='Z')||(c>='0'&&c<='9')||c=='_'||c=='-'; } static int gr__idlen(const char *p){ int n=0; while(gr__idch(p[n])) n++; return n; } static int gr__hex(char c){ if(c>='0'&&c<='9') return c-'0'; if(c>='a'&&c<='f') return c-'a'+10; if(c>='A'&&c<='F') return c-'A'+10; return -1; } static int gr__esc(const char **pp){ /* dopo la barra: byte 0-255 o -1 */ const char *p=*pp; int c=-1; switch(*p){ case 'n': c='\n'; break; case 'r': c='\r'; break; case 't': c='\t'; break; case '"': c='"'; break; case '\\':c='\\'; break; case '[': c='['; break; case ']': c=']'; break; case '-': c='-'; break; case '^': c='^'; break; case 'x': { int h=gr__hex(p[1]), l=gr__hex(p[2]); if(h>=0&&l>=0){ c=h*16+l; p+=2; } break; } default: return -1; } if(c<0) return -1; *pp=p+1; return c; } static int gr__lit(Grammar *G, int ri, int ai, const char **pp){ const char *p=*pp+1; while(*p && *p!='"'){ int b; if(*p=='\\'){ p++; b=gr__esc(&p); if(b<0){ snprintf(G->err,sizeof G->err,"escape non valido nel letterale"); return -1; } } else b=(unsigned char)*p++; GrSym s; memset(&s,0,sizeof s); s.t=GR_CLS; s.c.bits[b>>3]|=(uint8_t)(1u<<(b&7)); if(gr__push(G,ri,ai,&s)){ snprintf(G->err,sizeof G->err,"memoria esaurita"); return -1; } } if(*p!='"'){ snprintf(G->err,sizeof G->err,"letterale non chiuso"); return -1; } *pp=p+1; return 0; } static int gr__cls(Grammar *G, int ri, int ai, const char **pp){ const char *p=*pp+1; int neg=0; GrSym s; memset(&s,0,sizeof s); s.t=GR_CLS; if(*p=='^'){ neg=1; p++; } while(*p && *p!=']'){ int lo, hi; if(*p=='\\'){ p++; lo=gr__esc(&p); if(lo<0){ snprintf(G->err,sizeof G->err,"escape non valido nella classe"); return -1; } } else lo=(unsigned char)*p++; hi=lo; if(*p=='-' && p[1] && p[1]!=']'){ p++; if(*p=='\\'){ p++; hi=gr__esc(&p); if(hi<0){ snprintf(G->err,sizeof G->err,"escape non valido nella classe"); return -1; } } else hi=(unsigned char)*p++; } if(hi>3]|=(uint8_t)(1u<<(b&7)); } if(*p!=']'){ snprintf(G->err,sizeof G->err,"classe non chiusa"); return -1; } if(neg) for(int i=0;i<32;i++) s.c.bits[i]=(uint8_t)~s.c.bits[i]; *pp=p+1; if(gr__push(G,ri,ai,&s)){ snprintf(G->err,sizeof G->err,"memoria esaurita"); return -1; } return 0; } /* postfisso ? * + sull'ITEM appena letto (simboli [n0, n) dell'alternate corrente). * L'item diventa una regola anonima I; poi: ? -> R ::= I | "" * * -> R ::= I R | "" * + -> R ::= I R | I */ static int gr__postfix(Grammar *G, int ri, int ai, int n0, char op){ int k=G->r[ri].a[ai].n-n0; if(k<=0) return 0; /* postfisso su "" : no-op */ int ii=gr__anon(G); if(ii<0) goto full; int ia=gr__alt_new(G,ii); if(ia<0) goto full; for(int j=0;jr[ri].a[ai].s[n0+j])) goto full; G->r[ri].a[ai].n=n0; int rr=gr__anon(G); if(rr<0) goto full; GrSym I; memset(&I,0,sizeof I); I.t=GR_REF; I.ref=(int16_t)ii; GrSym R; memset(&R,0,sizeof R); R.t=GR_REF; R.ref=(int16_t)rr; int a0=gr__alt_new(G,rr); if(a0<0) goto full; if(gr__push(G,rr,a0,&I)) goto full; if(op=='*'||op=='+') if(gr__push(G,rr,a0,&R)) goto full; int a1=gr__alt_new(G,rr); if(a1<0) goto full; /* "" per ? e *, I per + */ if(op=='+') if(gr__push(G,rr,a1,&I)) goto full; if(gr__push(G,ri,ai,&R)) goto full; /* l'item nell'alternate diventa R */ return 0; full: snprintf(G->err,sizeof G->err,"grammatica troppo grande"); return -1; } static int gr__alts(Grammar *G, int ri, const char **pp, int depth, int in_group){ if(depth>32){ snprintf(G->err,sizeof G->err,"gruppi troppo annidati"); return -1; } const char *p=*pp; int ai=gr__alt_new(G,ri); if(ai<0){ snprintf(G->err,sizeof G->err,"memoria esaurita"); return -1; } for(;;){ p=gr__ws(p); if(!*p){ if(in_group){ snprintf(G->err,sizeof G->err,"manca ')'"); return -1; } break; } if(*p==')'){ if(!in_group){ snprintf(G->err,sizeof G->err,"')' inatteso"); return -1; } break; } if(*p=='|'){ p++; ai=gr__alt_new(G,ri); if(ai<0){ snprintf(G->err,sizeof G->err,"memoria esaurita"); return -1; } continue; } int n0=G->r[ri].a[ai].n; if(*p=='"'){ if(gr__lit(G,ri,ai,&p)) return -1; } else if(*p=='['){ if(gr__cls(G,ri,ai,&p)) return -1; } else if(*p=='('){ p++; int gi=gr__anon(G); if(gi<0){ snprintf(G->err,sizeof G->err,"grammatica troppo grande"); return -1; } if(gr__alts(G,gi,&p,depth+1,1)) return -1; p=gr__ws(p); if(*p!=')'){ snprintf(G->err,sizeof G->err,"manca ')'"); return -1; } p++; GrSym s; memset(&s,0,sizeof s); s.t=GR_REF; s.ref=(int16_t)gi; if(gr__push(G,ri,ai,&s)){ snprintf(G->err,sizeof G->err,"memoria esaurita"); return -1; } } else if(gr__idch(*p)){ int nl=gr__idlen(p); const char *after=gr__ws(p+nl); if(!in_group && !strncmp(after,"::=",3)) break; /* inizia la prossima regola */ int ref=gr__rule(G,p,nl); if(ref<0){ snprintf(G->err,sizeof G->err,"troppe regole"); return -1; } p+=nl; GrSym s; memset(&s,0,sizeof s); s.t=GR_REF; s.ref=(int16_t)ref; if(gr__push(G,ri,ai,&s)){ snprintf(G->err,sizeof G->err,"memoria esaurita"); return -1; } } else { snprintf(G->err,sizeof G->err,"carattere inatteso '%c'",*p); return -1; } p=gr__ws(p); if(*p=='?'||*p=='*'||*p=='+'){ if(gr__postfix(G,ri,ai,n0,*p)) return -1; p++; } } *pp=p; return 0; } /* parse del testo GBNF. 0 = ok; -1 = errore (messaggio in G->err). */ static int gr_parse(Grammar *G, const char *src){ memset(G,0,sizeof *G); G->root=-1; const char *p=src; for(;;){ p=gr__ws(p); if(!*p) break; int nl=gr__idlen(p); if(nl<=0){ snprintf(G->err,sizeof G->err,"attesa una regola, trovato '%c'",*p); return -1; } const char *name=p; const char *q=gr__ws(p+nl); if(strncmp(q,"::=",3)){ snprintf(G->err,sizeof G->err,"atteso '::=' dopo '%.*s'",nl,name); return -1; } p=q+3; int ri=gr__rule(G,name,nl); if(ri<0){ snprintf(G->err,sizeof G->err,"troppe regole"); return -1; } if(G->r[ri].n>0){ snprintf(G->err,sizeof G->err,"regola '%.*s' duplicata",nl,name); return -1; } if(gr__alts(G,ri,&p,0,0)) return -1; } for(int i=0;in;i++){ if(!strcmp(G->r[i].name,"root")) G->root=i; if(G->r[i].n==0){ snprintf(G->err,sizeof G->err,"regola '%s' usata ma mai definita",G->r[i].name); return -1; } } if(G->root<0){ snprintf(G->err,sizeof G->err,"manca la regola 'root'"); return -1; } return 0; } static void gr_free(Grammar *G){ for(int i=0;in;i++){ for(int a=0;ar[i].n;a++) free(G->r[i].a[a].s); free(G->r[i].a); } G->n=0; } /* ---------- walker (PDA a insieme di stack) ---------- */ static int gr__set_add(GrState *S, const GrStack *k){ for(int i=0;in;i++) if(S->st[i].n==k->n && !memcmp(S->st[i].f,k->f,(size_t)k->n*sizeof(GrFrame))) return 1; if(S->n>=GR_MAX_STACKS) return 0; /* troppa ambiguita': fail-safe */ S->st[S->n++]=*k; return 1; } /* porta lo stack in forma normale (cima = terminale, o stack vuoto = parse completo), * diramando sugli alternate delle regole referenziate. 0 = overflow (fail-safe). */ static int gr__normalize(Grammar *G, GrStack *k, GrState *out, int depth){ for(;;){ if(k->n==0) return gr__set_add(out,k); GrFrame *t=&k->f[k->n-1]; GrAlt *A=&G->r[t->r].a[t->a]; if(t->s>=A->n){ k->n--; continue; } /* alternate esaurito: pop */ GrSym *sy=&A->s[t->s]; if(sy->t==GR_CLS) return gr__set_add(out,k); if(depth>=GR_MAX_DEPTH) return 0; /* ricorsione sinistra / epsilon-ciclo */ t->s++; /* il chiamante riprende OLTRE il ref */ GrRule *C=&G->r[sy->ref]; for(int a=0;an;a++){ if(k->n>=GR_MAX_DEPTH) return 0; GrStack cp=*k; cp.f[cp.n].r=sy->ref; cp.f[cp.n].a=(int16_t)a; cp.f[cp.n].s=0; cp.n++; if(!gr__normalize(G,&cp,out,depth+1)) return 0; } return 1; } } static void gr_state_init(GrState *S, Grammar *G){ S->G=G; S->n=0; S->alive=1; GrRule *R=&G->r[G->root]; for(int a=0;an;a++){ GrStack k; k.n=1; k.f[0].r=(int16_t)G->root; k.f[0].a=(int16_t)a; k.f[0].s=0; if(!gr__normalize(G,&k,S,0)){ S->alive=0; return; } } if(S->n==0) S->alive=0; } /* avanza di un byte. 1 = consumato; 0 = byte non ammesso (stato INVARIATO); * -1 = walker spento (overflow: da qui in poi niente piu' draft). */ static int gr_accept(GrState *S, unsigned char b){ if(!S->alive) return -1; GrState out; out.G=S->G; out.n=0; out.alive=1; for(int i=0;in;i++){ GrStack *k=&S->st[i]; if(k->n==0) continue; /* parse gia' completo: non consuma */ GrFrame *t=&k->f[k->n-1]; GrSym *sy=&S->G->r[t->r].a[t->a].s[t->s]; if(!(sy->c.bits[b>>3]&(1u<<(b&7)))) continue; GrStack cp=*k; cp.f[cp.n-1].s++; if(!gr__normalize(S->G,&cp,&out,0)){ S->alive=0; return -1; } } if(out.n==0) return 0; S->n=out.n; memcpy(S->st,out.st,(size_t)out.n*sizeof(GrStack)); return 1; } /* insieme dei byte ammessi adesso (bitmap 256). Ritorna il conteggio; * *can_end = 1 se il parse puo' terminare qui (quindi il modello puo' emettere EOS). */ static int gr_admissible(const GrState *S, unsigned char mask[32], int *can_end){ memset(mask,0,32); int end=0; for(int i=0;in;i++){ const GrStack *k=&S->st[i]; if(k->n==0){ end=1; continue; } const GrFrame *t=&k->f[k->n-1]; const GrSym *sy=&S->G->r[t->r].a[t->a].s[t->s]; for(int j=0;j<32;j++) mask[j]|=sy->c.bits[j]; } int cnt=0; for(int j=0;j<32;j++){ unsigned v=mask[j]; while(v){ cnt+=v&1; v>>=1; } } if(can_end)*can_end=end; return cnt; } /* prefisso FORZATO: si estende finche' c'e' UN SOLO byte legale e il parse non e' * terminabile (li' il modello potrebbe fermarsi). Non muta S. Ritorna i byte scritti. */ static int gr_forced(const GrState *S, char *out, int max){ if(!S->alive||S->n==0) return 0; GrState cp=*S; int n=0; while(n>3]&(1u<<(b&7)))) b++; if(b>=256 || gr_accept(&cp,(unsigned char)b)!=1) break; out[n++]=(char)b; } return n; } #endif /* COLI_GRAMMAR_H */