// Jass2 parser for bison/yacc
// by Rudi Cilibrasi
// Sun Jun 8 00:51:53 CEST 2003
// thanks to Jeff Pang for the handy documentation that this was based
// on at http://jass.sourceforge.net
// Released under the BSD license
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include <stdint.h>
#include "misc.h"
int pjass_flags;
int fno;
int lineno;
int haderrors;
int ignorederrors;
int totlines;
int islinebreak;
bool isconstant;
bool inconstant;
bool infunction;
bool inblock;
int fnannotations;
int annotations;
int didparse;
int inloop;
bool afterendglobals;
bool inglobals;
bool encoutered_first_function;
int *showerrorlevel;
struct hashtable functions;
struct hashtable globals;
struct hashtable locals;
struct hashtable params;
struct hashtable types;
struct hashtable initialized;
struct hashtable bad_natives_in_globals;
struct hashtable shadowed_variables;
struct hashtable uninitialized_globals;
struct hashtable string_literals;
size_t stringlit_buffsize = 2048;
char stringlit_buff[2048] = {0};
size_t stringlit_length = 0;
struct tree stringlit_hashes;
struct hashtable *curtab;
const struct typenode *retval;
const char *curfile;
struct typenode *gInteger, *gReal, *gBoolean, *gString, *gCode, *gHandle, *gNothing, *gNull, *gAny, *gNone, *gEmpty;
struct typenode *gCodeReturnsNoBoolean, *gCodeReturnsBoolean;
struct typenode *gEmpty;
struct funcdecl *fCurrent;
struct funcdecl *fFilter, *fCondition, *fStringHash;
struct hashtable available_flags;
void yyerrorline (enum errortype type, int line, const char *s)
{
if(flagenabled(flag_syntaxerror) && type == syntaxerror){
ignorederrors++;
return;
}
if(flagenabled(flag_semanticerror) && type == semanticerror){
ignorederrors++;
return;
}
if(flagenabled(flag_runtimeerror) && type == runtimeerror){
ignorederrors++;
return;
}
haderrors++;
printf ("%s:%d: %s\n", curfile, line, s);
}
void yyerrorex (enum errortype type, const char *s)
{
yyerrorline(type, lineno, s);
}
void yyerror (const char *s) /* Called by yyparse on error */
{
yyerrorex(syntaxerror, s);
}
void put(struct hashtable *h, const char *name, void *val){
if( !ht_put(h, name, val) ){
char ebuf[1024];
snprintf(ebuf, 1024, "Symbol %s multiply defined", name);
yyerrorline(semanticerror, islinebreak ? lineno - 1 : lineno, ebuf);
}
}
#define min(a, b) (((a) < (b)) ? (a) : (b))
int abs(int i){
if(i < 0)
return -i;
return i;
}
void str_append(char *buf, const char *str, size_t buf_size){
size_t str_len = strlen(str);
size_t buf_len = strlen(buf);
size_t buf_freespace = buf_size - (buf_len+1); // +1 for zero byte at the end
size_t to_copy;
if(buf_freespace > str_len){
to_copy = str_len;
}else{
to_copy = buf_freespace;
}
memmove(buf+buf_len, str, to_copy);
buf[buf_len + to_copy] = 0;
}
static int editdistance(const char *s, const char *t, int cutoff){
if(!strcmp(s, t)) return 0;
int a = strlen(s);
int b = strlen(t);
if(a==0) return b;
if(b==0) return a;
if(abs(a-b) > cutoff){
return cutoff + 1;
}
int *v[3];
int i;
for(i = 0; i != 3; i++) {
v[i] = malloc(sizeof(int) * (size_t)(b+1));
}
for(i = 0; i != b+1; i++){
v[0][i] = i;
}
int pcur;
int ppcur;
int cur = 1;
for(i = 0; i != a; i++){
cur = (cur+1) % 3;
pcur = cur -1;
if(pcur < 0) pcur += 3;
ppcur = pcur -1;
if(ppcur < 0) ppcur += 3;
v[cur][0] = i + 1;
int minDistance = INT_MAX;
int j;
for(j = 0; j != b; j++){
int cost = (s[i] == t[j]) ? 0 : 1;
v[cur][j+1] = min(v[cur][j] + 1, min(v[pcur][j+1] + 1, v[pcur][j] + cost));
if(i > 0 && j > 0 && s[i] == t[j-1] && s[i-1] == t[j]){
v[cur][j+1] = min(v[cur][j+1], v[ppcur][j-1] + cost);
}
if(v[cur][j+1] < minDistance){
minDistance = v[cur][j+1];
}
}
if(minDistance > cutoff){
return cutoff + 1;
}
}
pcur = cur -1;
if(pcur < 0) pcur += 3;
int d = v[pcur][b];
for(i = 0; i != 3; i++)
free(v[i]);
return d;
}
void getsuggestions(const char *name, char *buff, size_t buffsize, int nTables, ...)
{
va_list ap;
int len = strlen(name);
int cutoff = (int)((len+2)/4.0);
int count = 0;
struct {int distance; const char *name;} suggestions[3];
int i;
for(i = 0; i != 3; i++){
suggestions[i].distance = INT_MAX;
suggestions[i].name = NULL;
}
va_start(ap, nTables);
for(i = 0; i != nTables; i++){
struct hashtable *ht = va_arg(ap, struct hashtable*);
size_t x;
for(x = 0; x != ht->size; x++){
if(ht->bucket[x].name){
const struct typeandname *tan = ht->bucket[x].val;
if(typeeq(tan->ty, gAny)){
continue;
}
int dist = editdistance(ht->bucket[x].name, name, cutoff);
if(dist <= cutoff){
count++;
int j;
for(j = 0; j != 3; j++){
if(suggestions[j].distance > dist){
if(i == 0){
suggestions[2] = suggestions[1];
suggestions[1] = suggestions[0];
}else if(i == 1){
suggestions[2] = suggestions[1];
}
suggestions[j].distance = dist;
suggestions[j].name = ht->bucket[x].name;
break;
}
}
}
}
}
}
va_end(ap);
char hbuff[1024];
if(count == 1){
snprintf(hbuff, 1024, ". Maybe you meant %s", suggestions[0].name);
str_append(buff, hbuff, buffsize);
}else if(count == 2){
snprintf(hbuff, 1024, ". Maybe you meant %s or %s", suggestions[0].name, suggestions[1].name);
str_append(buff, hbuff, buffsize);
}else if(count >= 3){
snprintf(hbuff, 1024, ". Maybe you meant %s, %s or %s", suggestions[0].name, suggestions[1].name, suggestions[2].name);
str_append(buff, hbuff, buffsize);
}
}
const struct typeandname *getVariable(const char *varname)
{
char ebuf[1024];
struct typeandname *result;
result = ht_lookup(&locals, varname);
if (result) return result;
result = ht_lookup(¶ms, varname);
if (result) return result;
result = ht_lookup(&globals, varname);
if (result) return result;
snprintf(ebuf, 1024, "Undeclared variable %s", varname);
getsuggestions(varname, ebuf, 1024, 3, &locals, ¶ms, &globals);
yyerrorline(semanticerror, islinebreak ? lineno - 1 : lineno, ebuf);
// Store it as unidentified variable
const struct typeandname *newtan = newtypeandname(gAny, varname);
put(curtab, varname, (void*)newtan);
if(infunction && !ht_lookup(&initialized, varname)){
put(&initialized, varname, (void*)1);
}
return newtan;
}
void validateGlobalAssignment(const char *varname)
{
char ebuf[1024];
// check if the variable exists in global scope but not in params or locals
if( ht_lookup(&globals, varname) && !ht_lookup(&locals, varname) && !ht_lookup(¶ms, varname) ){
snprintf(ebuf, 1024, "Assignment to global variable %s in constant function", varname);
yyerrorline(semanticerror, lineno - 1, ebuf);
}
}
void checkParameters(const struct funcdecl *fd, const struct paramlist *inp, bool mustretbool)
{
const struct paramlist *func = fd->p;
const struct typeandname *fi = func->head;
const struct typeandname *pi = inp->head;
while(true) {
if (fi == NULL && pi == NULL)
return;
if (fi == NULL && pi != NULL) {
char buf[1024];
snprintf(buf, 1024, "Too many arguments passed to function %s. ", fd->name);
yyerrorex(semanticerror, buf);
return;
}
if (fi != NULL && pi == NULL) {
char buf[1024];
snprintf(buf, 1024, "Not enough arguments passed to function %s. ", fd->name);
str_append(buf, "Still missing: ", 1024);
bool addComma = false;
for(; fi; fi = fi->next){
if(addComma){
str_append(buf, ", ", 1024);
}
str_append(buf, fi->name, 1024);
addComma = true;
}
yyerrorex(semanticerror, buf);
return;
}
char buf[1024];
if(! canconvertbuf(buf, 1024, pi->ty, fi->ty )){
char pbuf[1024];
snprintf(pbuf, 1024, " in parameter %s in call to %s", fi->name, fd->name);
str_append(buf, pbuf, 1024);
yyerrorex(semanticerror, buf);
}
if(flagenabled(flag_filter) && mustretbool && typeeq(pi->ty, gCodeReturnsNoBoolean)){
yyerrorex(semanticerror, "Function passed to Filter or Condition must return a boolean");
return;
}
pi = pi->next;
fi = fi->next;
}
}
void checkarrayindex(const char *name, const struct typenode *ty, int lineno)
{
char buf[1024];
if(! canconvertbuf(buf, 1024, ty, gInteger)){
str_append(buf, " as index for array ", 1024);
str_append(buf, name, 1024);
yyerrorline(semanticerror, lineno, buf);
}
}
const struct typenode *binop(const struct typenode *a, const struct typenode *b)
{
a = getPrimitiveAncestor(a);
b = getPrimitiveAncestor(b);
if (typeeq(a, gInteger) && typeeq(b, gInteger))
return gInteger;
if (typeeq(a, gString) && typeeq(b, gString))
return gString;
if (typeeq(a, gAny))
return b;
if (typeeq(b, gAny))
return a;
if ((!typeeq(a, gInteger) && !typeeq(a, gReal)) || (!typeeq(b, gInteger) && !typeeq(b, gReal))) {
yyerrorline(semanticerror, islinebreak ? lineno - 1 : lineno, "Bad types for binary operator");
}
return gReal;
}
const struct typenode *combinetype(const struct typenode *n1, const struct typenode *n2)
{
uint8_t ret = getTypeTag(n1) & getTypeTag(n2);
if ((typeeq(n1, gNone)) || (typeeq(n2, gNone)))
return mkretty(gNone, ret);
if (typeeq(n1, n2))
return mkretty(n1, ret);
if (typeeq(n1, gNull))
return mkretty(n2, ret);
if (typeeq(n2, gNull))
return mkretty(n1, ret);
n1 = getPrimitiveAncestor(n1);
n2 = getPrimitiveAncestor(n2);
if (typeeq(n1, n2))
return mkretty(n1, ret);
if (typeeq(n1, gNull))
return mkretty(n2, ret);
if (typeeq(n2, gNull))
return mkretty(n1, ret);
if ((typeeq(n1, gInteger)) && (typeeq(n2, gReal)))
return mkretty(gReal, ret);
if ((typeeq(n1, gReal)) && (typeeq(n2, gInteger)))
return mkretty(gInteger, ret);
return mkretty(gNone, ret);
}
bool canconvertbuf(char *buf, size_t buflen, const struct typenode *ufrom, const struct typenode *uto)
{
const struct typenode *from = ufrom, *to = uto;
if (from == NULL || to == NULL)
return true;
if (typeeq(from, gAny) || typeeq(to, gAny))
return true;
if (isDerivedFrom(from, to))
return true;
if (getTypePtr(from)->typename == NULL || getTypePtr(to)->typename == NULL)
return true;
if (typeeq(from, gNone) || typeeq(to, gNone))
return true;
from = getPrimitiveAncestor(from);
to = getPrimitiveAncestor(to);
if (typeeq(from, gNull) && !typeeq(to, gInteger) && !typeeq(to, gReal) && !typeeq(to, gBoolean))
return true;
if (typeeq(from, gInteger) && (typeeq(to, gReal) || typeeq(to, gInteger)))
return true;
if (typeeq(from, to) && (typeeq(from, gBoolean) || typeeq(from, gString) || typeeq(from, gReal) || typeeq(from, gInteger) || typeeq(from, gCode)))
return true;
snprintf(buf, buflen, "Cannot convert %s to %s", ufrom->typename, uto->typename);
return false;
}
// this is used for reducing expressions in many places (if/exitwhen conditions, assignments etc.)
void canconvert(const struct typenode *ufrom, const struct typenode *uto, const int linemod)
{
char buf[1024];
if(! canconvertbuf(buf, 1024, ufrom, uto ) ){
yyerrorline(semanticerror, lineno + linemod, buf);
}
}
// this is used for return statements only
void canconvertreturn(const struct typenode *ufrom, const struct typenode *uto, const int linemod)
{
const struct typenode *from = ufrom, *to = uto;
char ebuf[1024];
if(typeeq(from, NULL) || typeeq(to, NULL))
return; // garbage
if (typeeq(from, gAny) || typeeq(to, gAny))
return; // we don't care
if (isDerivedFrom(from, to))
return; // eg. from = unit, to = handle
if (getTypePtr(from)->typename == NULL || getTypePtr(to)->typename == NULL)
return; // garbage
if (typeeq(from, gNone) || typeeq(to, gNone))
return; // garbage
from = getPrimitiveAncestor(from);
to = getPrimitiveAncestor(to);
if ((typeeq(to, gReal)) && (typeeq(from, gInteger))) {
// can't return integer when it expects a real (added 9.5.2005)
snprintf(ebuf, 1024, "Cannot convert returned value from %s to %s", getTypePtr(from)->typename, getTypePtr(to)->typename);
yyerrorline(semanticerror, lineno + linemod, ebuf);
return;
}
if ((typeeq(from, gNull)) && (!typeeq(to, gInteger)) && (!typeeq(to, gReal)) && (!typeeq(to, gBoolean)))
return; // can't return null when it expects integer, real or boolean (added 9.5.2005)
if (typeeq(ufrom, uto)){
return;
}
snprintf(ebuf, 1024, "Cannot convert returned value from %s to %s", getTypePtr(ufrom)->typename, getTypePtr(uto)->typename);
yyerrorline(semanticerror, lineno + linemod, ebuf);
return;
}
void isnumeric(const struct typenode *ty)
{
ty = getPrimitiveAncestor(ty);
if (!(ty == gInteger || ty == gReal || ty == gAny))
yyerrorline(semanticerror, islinebreak ? lineno - 1 : lineno, "Cannot be converted to numeric type");
}
void checkcomparisonsimple(const struct typenode *a)
{
const struct typenode *pa;
pa = getPrimitiveAncestor(a);
if (typeeq(pa, gString) || typeeq(pa, gHandle) || typeeq(pa, gCode) || typeeq(pa, gBoolean)) {
yyerrorex(semanticerror, "Comparing the order/size of 2 variables only works on reals and integers");
return;
}
if (typeeq(pa, gNull))
yyerrorex(semanticerror, "Comparing null is not allowed");
}
void checkcomparison(const struct typenode *a, const struct typenode *b)
{
const struct typenode *pa, *pb;
pa = getPrimitiveAncestor(a);
pb = getPrimitiveAncestor(b);
if (typeeq(pa, gString) || typeeq(pa, gHandle) || typeeq(pa, gCode) || typeeq(pa, gBoolean) || typeeq(pb, gString) || typeeq(pb, gCode) || typeeq(pb, gHandle) || typeeq(pb, gBoolean)) {
yyerrorex(semanticerror, "Comparing the order/size of 2 variables only works on reals and integers");
return;
}
if (typeeq(pa, gNull) && typeeq(pb, gNull))
yyerrorex(semanticerror, "Comparing null is not allowed");
}
void checkmodulo(const struct typenode *a, const struct typenode *b)
{
const struct typenode *pa, *pb;
pa = getPrimitiveAncestor(a);
pb = getPrimitiveAncestor(b);
bool fst = typeeq(pa, gInteger);
bool snd = typeeq(pb, gInteger);
if(! fst && ! snd){
yyerrorex(semanticerror, "Both operands of the modulo-operator must be integers");
}else if(! fst){
yyerrorex(semanticerror, "First operand of the modulo-operator must be an integer");
}else if(! snd){
yyerrorex(semanticerror, "Second operand of the modulo-operator must be an integer");
}
}
void checkeqtest(const struct typenode *a, const struct typenode *b)
{
const struct typenode *pa, *pb;
pa = getPrimitiveAncestor(a);
pb = getPrimitiveAncestor(b);
if ((typeeq(pa, gInteger) || typeeq(pa, gReal)) && (typeeq(pb, gInteger) || typeeq(pb, gReal)))
return;
if (typeeq(pa, gNull) || typeeq(pb, gNull))
return;
if (!typeeq(pa, pb)) {
yyerrorex(semanticerror, "Comparing two variables of different primitive types (except real and integer) is not allowed");
return;
}
}
int isflag(char *txt, struct hashtable *flags){
txt++; // ignore +/- at the start
void *flag = ht_lookup(flags, txt);
return (int)flag;
}
int updateflag(int cur, char *txt, struct hashtable *flags){
char sgn = txt[0];
int flag = isflag(txt, flags);
if( flag){
if(sgn == '+') {
cur |= flag;
} else if(sgn == '-') {
cur &= ~flag;
}
}
return cur;
}
int updateannotation(int cur, char *txt, struct hashtable *flags){
char sep[] = " \t\r\n";
char *ann;
for(ann = strtok(txt, sep); ann; ann = strtok(NULL, sep)){
cur = updateflag(cur, ann, flags);
}
return cur;
}
bool flagenabled(int flag)
{
if(infunction){
return (fnannotations & flag);
}else{
return (pjass_flags & flag);
}
}
union node checkfunctionheader(const char *fnname, struct paramlist *pl, const struct typenode *retty)
{
union node ret;
if (ht_lookup(&locals, fnname) || ht_lookup(¶ms, fnname) || ht_lookup(&globals, fnname)) {
char buf[1024];
snprintf(buf, 1024, "%s already defined as variable", fnname);
yyerrorex(semanticerror, buf);
} else if (ht_lookup(&types, fnname)) {
char buf[1024];
snprintf(buf, 1024, "%s already defined as type", fnname);
yyerrorex(semanticerror, buf);
}
curtab = &locals;
ret.fd = newfuncdecl();
ret.fd->name = strdup(fnname);
ret.fd->p = pl;
ret.fd->ret = retty;
put(&functions, ret.fd->name, ret.fd);
fCurrent = ht_lookup(&functions, fnname);
fnannotations = annotations;
struct typeandname *tan = pl->head;
for (;tan; tan=tan->next) {
tan->lineno = lineno;
tan->fn = fno;
put(¶ms, strdup(tan->name), newtypeandname(tan->ty, tan->name));
if (ht_lookup(&functions, tan->name)) {
char buf[1024];
snprintf(buf, 1024, "%s already defined as function", tan->name);
yyerrorex(semanticerror, buf);
} else if (ht_lookup(&types, tan->name)) {
char buf[1024];
snprintf(buf, 1024, "%s already defined as type", tan->name);
yyerrorex(semanticerror, buf);
}
if( flagenabled(flag_shadowing) ){
if( ht_lookup(&globals, tan->name) ){
char buf[1024];
snprintf(buf, 1024, "Parmeter %s shadows global variable", tan->name);
yyerrorex(semanticerror, buf);
}
}
}
retval = ret.fd->ret;
inblock = 1;
inloop = 0;
return ret;
}
union node checkfunccall(const char *fnname, struct paramlist *pl)
{
union node ret;
struct funcdecl *fd = ht_lookup(&functions, fnname);
if (fd == NULL) {
char ebuf[1024];
snprintf(ebuf, 1024, "Undeclared function %s", fnname);
getsuggestions(fnname, ebuf, 1024, 1, &functions);
yyerrorex(semanticerror, ebuf);
ret.ty = gAny;
} else {
if (inconstant && !(fd->isconst)) {
char ebuf[1024];
snprintf(ebuf, 1024, "Call to non-constant function %s in constant function", fnname);
yyerrorex(semanticerror, ebuf);
}
if (fd == fCurrent && fCurrent)
yyerrorex(semanticerror, "Recursive function calls are not permitted in local declarations");
if( inglobals){
char ebuf[1024];
int err = (int)ht_lookup(&bad_natives_in_globals, fd->name);
if(err == CrashInGlobals){
snprintf(ebuf, 1024, "Call to %s in a globals block crashes the game", fd->name);
yyerrorex(runtimeerror, ebuf);
}else if(err == NullInGlobals){
snprintf(ebuf, 1024, "Call to %s in a globals block always returns null", fd->name);
yyerrorex(runtimeerror, ebuf);
}
}
if( fd == fStringHash && pl->head && flagenabled(flag_checkstringhash) ){
const struct typenode *a1 = pl->head->ty;
if( ! typeeq(a1, gString) && isDerivedFrom(a1, gString) ){
//printf("Got call to StringHash with argument %s\n", a1->typename);
uint32_t strhash = SStrHash2(a1->typename);
char *name = tree_lookup(&stringlit_hashes, strhash);
if( name == NULL ){
tree_put(&stringlit_hashes, strhash, a1->typename);
}else if( strcmp(name, a1->typename)){
char ebuf[1024];
snprintf(ebuf, 1024, "String %s produces the same hash as %s", name, a1->typename);
yyerrorex(semanticerror, ebuf);
}
}
}
checkParameters(fd, pl, fd == fFilter || fd == fCondition);
ret.ty = fd->ret;
}
return ret;
}
static void checkvarname(struct typeandname *tan, bool isarray)
{
const char *name = tan->name;
if (ht_lookup(&functions, name)) {
char buf[1024];
snprintf(buf, 1024, "Symbol %s already defined as function", name);
yyerrorex(semanticerror, buf);
} else if (ht_lookup(&types, name)) {
char buf[1024];
snprintf(buf, 1024, "Symbol %s already defined as type", name);
yyerrorex(semanticerror, buf);
}
struct typeandname *existing = ht_lookup(&locals, name);
if (!existing) {
char buf[1024];
existing = ht_lookup(¶ms, name);
if ( isarray && infunction && existing) {
snprintf(buf, 1024, "Symbol %s already defined as function parameter", name);
yyerrorex(semanticerror, buf);
}
if (!existing) {
existing = ht_lookup(&globals, name);
if ( isarray && infunction && existing) {
snprintf(buf, 1024, "Symbol %s already defined as global variable", name);
yyerrorex(semanticerror, buf);
}
}
}
if (existing) {
tan->lineno = existing->lineno;
tan->fn = existing->fn;
} else {
tan->lineno = lineno;
tan->fn = fno;
}
}
void checkallshadowing(struct typeandname *tan){
struct typeandname *global = ht_lookup(&globals, tan->name);
char buf[1024];
if( global ){
// once a variable is shadowed with an incompatible type every usage
// of the shadowed variable in the script file (sic) cannot be used
// safely anymore. Usages of the shadowed variable in the script
// above the shadowing still work fine.
tan->lineno = lineno;
ht_put(&shadowed_variables, tan->name, tan);
if(flagenabled(flag_shadowing)){
snprintf(buf, 1024, "Local variable %s shadows global variable", tan->name);
yyerrorline(semanticerror, lineno, buf);
}
} else if( flagenabled(flag_shadowing) && ht_lookup(¶ms, tan->name)){
snprintf(buf, 1024, "Local variable %s shadows parameter", tan->name);
yyerrorline(semanticerror, lineno, buf);
}
}
union node checkvartypedecl(struct typeandname *tan)
{
const char *name = tan->name;
union node ret;
checkvarname(tan, false);
ret.str = name;
put(curtab, name, tan);
if(infunction ){
// always an error
checkwrongshadowing(tan, 0);
// flag driven
checkallshadowing(tan);
}
return ret;
}
union node checkarraydecl(struct typeandname *tan)
{
const char *name = tan->name;
union node ret;
if (getPrimitiveAncestor(tan->ty) == gCode)
yyerrorex(semanticerror, "Code arrays are not allowed");
checkvarname(tan, true);
ret.str = name;
put(curtab, name, tan);
return ret;
}
void checkwrongshadowing(const struct typeandname *tan, int linemod){
struct typeandname *global;
char buf[1024];
if( (global = ht_lookup(&shadowed_variables, tan->name)) ){
if(! typeeq(getPrimitiveAncestor(global->ty), getPrimitiveAncestor(tan->ty))){
snprintf(buf, 1024, "Global variable %s is used after it was shadowed with an incompatible type in line %d", tan->name, global->lineno);
yyerrorline(semanticerror, lineno - linemod, buf);
}
}
}