]> git.xonotic.org Git - xonotic/gmqcc.git/commitdiff
Implemented length operator. This closes #130
authorDale Weiler <weilercdale@gmail.com>
Sat, 27 Sep 2014 08:15:32 +0000 (04:15 -0400)
committerDale Weiler <weilercdale@gmail.com>
Sat, 27 Sep 2014 08:15:32 +0000 (04:15 -0400)
fold.c
lexer.c
lexer.h
parser.c
tests/length.qc [new file with mode: 0644]
tests/length.tmpl [new file with mode: 0644]

diff --git a/fold.c b/fold.c
index db17601a0ae4c63d0de3315489896debb3027425..7b87dc280ad55378cb82a38e6c2cd0acf300dbe4 100644 (file)
--- a/fold.c
+++ b/fold.c
@@ -511,6 +511,7 @@ static GMQCC_INLINE void sfloat_init(sfloat_state_t *state) {
 #define isfloat(X)      (((ast_expression*)(X))->vtype == TYPE_FLOAT)
 #define isvector(X)     (((ast_expression*)(X))->vtype == TYPE_VECTOR)
 #define isstring(X)     (((ast_expression*)(X))->vtype == TYPE_STRING)
 #define isfloat(X)      (((ast_expression*)(X))->vtype == TYPE_FLOAT)
 #define isvector(X)     (((ast_expression*)(X))->vtype == TYPE_VECTOR)
 #define isstring(X)     (((ast_expression*)(X))->vtype == TYPE_STRING)
+#define isarray(X)      (((ast_expression*)(X))->vtype == TYPE_ARRAY)
 #define isfloats(X,Y)   (isfloat  (X) && isfloat (Y))
 
 /*
 #define isfloats(X,Y)   (isfloat  (X) && isfloat (Y))
 
 /*
@@ -1337,6 +1338,14 @@ static GMQCC_INLINE ast_expression *fold_op_cross(fold_t *fold, ast_value *a, as
     return NULL;
 }
 
     return NULL;
 }
 
+static GMQCC_INLINE ast_expression *fold_op_length(fold_t *fold, ast_value *a) {
+    if (fold_can_1(a) && isstring(a))
+        return fold_constgen_float(fold, strlen(fold_immvalue_string(a)), false);
+    if (isarray(a))
+        return fold_constgen_float(fold, vec_size(a->initlist), false);
+    return NULL;
+}
+
 ast_expression *fold_op(fold_t *fold, const oper_info *info, ast_expression **opexprs) {
     ast_value      *a = (ast_value*)opexprs[0];
     ast_value      *b = (ast_value*)opexprs[1];
 ast_expression *fold_op(fold_t *fold, const oper_info *info, ast_expression **opexprs) {
     ast_value      *a = (ast_value*)opexprs[0];
     ast_value      *b = (ast_value*)opexprs[1];
@@ -1374,29 +1383,30 @@ ast_expression *fold_op(fold_t *fold, const oper_info *info, ast_expression **op
             return e
 
     switch(info->id) {
             return e
 
     switch(info->id) {
-        fold_op_case(2, ('-', 'P'),    neg,    (fold, a));
-        fold_op_case(2, ('!', 'P'),    not,    (fold, a));
-        fold_op_case(1, ('+'),         add,    (fold, a, b));
-        fold_op_case(1, ('-'),         sub,    (fold, a, b));
-        fold_op_case(1, ('*'),         mul,    (fold, a, b));
-        fold_op_case(1, ('/'),         div,    (fold, a, b));
-        fold_op_case(1, ('%'),         mod,    (fold, a, b));
-        fold_op_case(1, ('|'),         bor,    (fold, a, b));
-        fold_op_case(1, ('&'),         band,   (fold, a, b));
-        fold_op_case(1, ('^'),         xor,    (fold, a, b));
-        fold_op_case(1, ('<'),         ltgt,   (fold, a, b, true));
-        fold_op_case(1, ('>'),         ltgt,   (fold, a, b, false));
-        fold_op_case(2, ('<', '<'),    lshift, (fold, a, b));
-        fold_op_case(2, ('>', '>'),    rshift, (fold, a, b));
-        fold_op_case(2, ('|', '|'),    andor,  (fold, a, b, true));
-        fold_op_case(2, ('&', '&'),    andor,  (fold, a, b, false));
-        fold_op_case(2, ('?', ':'),    tern,   (fold, a, b, c));
-        fold_op_case(2, ('*', '*'),    exp,    (fold, a, b));
-        fold_op_case(3, ('<','=','>'), lteqgt, (fold, a, b));
-        fold_op_case(2, ('!', '='),    cmp,    (fold, a, b, true));
-        fold_op_case(2, ('=', '='),    cmp,    (fold, a, b, false));
-        fold_op_case(2, ('~', 'P'),    bnot,   (fold, a));
-        fold_op_case(2, ('>', '<'),    cross,  (fold, a, b));
+        fold_op_case(2, ('-', 'P'),      neg,    (fold, a));
+        fold_op_case(2, ('!', 'P'),      not,    (fold, a));
+        fold_op_case(1, ('+'),           add,    (fold, a, b));
+        fold_op_case(1, ('-'),           sub,    (fold, a, b));
+        fold_op_case(1, ('*'),           mul,    (fold, a, b));
+        fold_op_case(1, ('/'),           div,    (fold, a, b));
+        fold_op_case(1, ('%'),           mod,    (fold, a, b));
+        fold_op_case(1, ('|'),           bor,    (fold, a, b));
+        fold_op_case(1, ('&'),           band,   (fold, a, b));
+        fold_op_case(1, ('^'),           xor,    (fold, a, b));
+        fold_op_case(1, ('<'),           ltgt,   (fold, a, b, true));
+        fold_op_case(1, ('>'),           ltgt,   (fold, a, b, false));
+        fold_op_case(2, ('<', '<'),      lshift, (fold, a, b));
+        fold_op_case(2, ('>', '>'),      rshift, (fold, a, b));
+        fold_op_case(2, ('|', '|'),      andor,  (fold, a, b, true));
+        fold_op_case(2, ('&', '&'),      andor,  (fold, a, b, false));
+        fold_op_case(2, ('?', ':'),      tern,   (fold, a, b, c));
+        fold_op_case(2, ('*', '*'),      exp,    (fold, a, b));
+        fold_op_case(3, ('<','=','>'),   lteqgt, (fold, a, b));
+        fold_op_case(2, ('!', '='),      cmp,    (fold, a, b, true));
+        fold_op_case(2, ('=', '='),      cmp,    (fold, a, b, false));
+        fold_op_case(2, ('~', 'P'),      bnot,   (fold, a));
+        fold_op_case(2, ('>', '<'),      cross,  (fold, a, b));
+        fold_op_case(3, ('l', 'e', 'n'), length, (fold, a));
     }
     #undef fold_op_case
     compile_error(fold_ctx(fold), "internal error: attempted to constant-fold for unsupported operator");
     }
     #undef fold_op_case
     compile_error(fold_ctx(fold), "internal error: attempted to constant-fold for unsupported operator");
diff --git a/lexer.c b/lexer.c
index 5ad0a90a537f558e2ba0512e79a771f8181426a6..1a8c17a1f3e414928737277fb7a5a59b76e1e8da 100644 (file)
--- a/lexer.c
+++ b/lexer.c
@@ -1301,6 +1301,28 @@ int lex_do(lex_file *lex)
         return (lex->tok.ttype = TOKEN_OPERATOR);
     }
 
         return (lex->tok.ttype = TOKEN_OPERATOR);
     }
 
+    /* length operator */
+    if (ch == 'l') {
+        if ((nextch = lex_getch(lex)) == 'e') {
+        if ((nextch = lex_getch(lex)) == 'n') {
+        if ((nextch = lex_getch(lex)) == 'g') {
+        if ((nextch = lex_getch(lex)) == 't') {
+        if ((nextch = lex_getch(lex)) == 'h') {
+            lex_tokench(lex, 'l');
+            lex_tokench(lex, 'e');
+            lex_tokench(lex, 'n');
+            lex_tokench(lex, 'g');
+            lex_tokench(lex, 't');
+            lex_tokench(lex, 'h');
+            lex_endtoken(lex);
+            return (lex->tok.ttype = TOKEN_OPERATOR);
+        } else lex_ungetch(lex, nextch);
+        } else lex_ungetch(lex, nextch);
+        } else lex_ungetch(lex, nextch);
+        } else lex_ungetch(lex, nextch);
+        } else lex_ungetch(lex, nextch);
+    }
+
     if (isident_start(ch))
     {
         const char *v;
     if (isident_start(ch))
     {
         const char *v;
diff --git a/lexer.h b/lexer.h
index 41a135549f5cce35b22aeb20203ab6ecfd4e8a2e..c073c8e76ca9ec4629154434aa7bbc51cb80e01d 100644 (file)
--- a/lexer.h
+++ b/lexer.h
@@ -176,71 +176,72 @@ typedef struct {
 #define opid3(a,b,c) (((uint8_t)a<<16)|((uint8_t)b<<8)|(uint8_t)c)
 
 static const oper_info c_operators[] = {
 #define opid3(a,b,c) (((uint8_t)a<<16)|((uint8_t)b<<8)|(uint8_t)c)
 
 static const oper_info c_operators[] = {
-    { "(",   0, opid1('('),         ASSOC_LEFT,  99, OP_PREFIX, false}, /* paren expression - non function call */
+    { "(",      0, opid1('('),         ASSOC_LEFT,  99, OP_PREFIX, false}, /* paren expression - non function call */
+    { "length", 1, opid3('l','e','n'), ASSOC_RIGHT, 98, OP_PREFIX, true},
 
 
-    { "++",  1, opid3('S','+','+'), ASSOC_LEFT,  17, OP_SUFFIX, false},
-    { "--",  1, opid3('S','-','-'), ASSOC_LEFT,  17, OP_SUFFIX, false},
-    { ".",   2, opid1('.'),         ASSOC_LEFT,  17, 0,         false},
-    { "(",   0, opid1('('),         ASSOC_LEFT,  17, 0,         false}, /* function call */
-    { "[",   2, opid1('['),         ASSOC_LEFT,  17, 0,         false}, /* array subscript */
+    { "++",     1, opid3('S','+','+'), ASSOC_LEFT,  17, OP_SUFFIX, false},
+    { "--",     1, opid3('S','-','-'), ASSOC_LEFT,  17, OP_SUFFIX, false},
+    { ".",      2, opid1('.'),         ASSOC_LEFT,  17, 0,         false},
+    { "(",      0, opid1('('),         ASSOC_LEFT,  17, 0,         false}, /* function call */
+    { "[",      2, opid1('['),         ASSOC_LEFT,  17, 0,         false}, /* array subscript */
 
 
-    { "++",  1, opid3('+','+','P'), ASSOC_RIGHT, 16, OP_PREFIX, false},
-    { "--",  1, opid3('-','-','P'), ASSOC_RIGHT, 16, OP_PREFIX, false},
+    { "++",     1, opid3('+','+','P'), ASSOC_RIGHT, 16, OP_PREFIX, false},
+    { "--",     1, opid3('-','-','P'), ASSOC_RIGHT, 16, OP_PREFIX, false},
 
 
-    { "**",  2, opid2('*','*'),     ASSOC_RIGHT, 14, 0,         true},
-    { "!",   1, opid2('!','P'),     ASSOC_RIGHT, 14, OP_PREFIX, true},
-    { "~",   1, opid2('~','P'),     ASSOC_RIGHT, 14, OP_PREFIX, true},
-    { "+",   1, opid2('+','P'),     ASSOC_RIGHT, 14, OP_PREFIX, false},
-    { "-",   1, opid2('-','P'),     ASSOC_RIGHT, 14, OP_PREFIX, true},
-/*  { "&",   1, opid2('&','P'),     ASSOC_RIGHT, 14, OP_PREFIX, false}, */
+    { "**",     2, opid2('*','*'),     ASSOC_RIGHT, 14, 0,         true},
+    { "!",      1, opid2('!','P'),     ASSOC_RIGHT, 14, OP_PREFIX, true},
+    { "~",      1, opid2('~','P'),     ASSOC_RIGHT, 14, OP_PREFIX, true},
+    { "+",      1, opid2('+','P'),     ASSOC_RIGHT, 14, OP_PREFIX, false},
+    { "-",      1, opid2('-','P'),     ASSOC_RIGHT, 14, OP_PREFIX, true},
+/*  { "&",      1, opid2('&','P'),     ASSOC_RIGHT, 14, OP_PREFIX, false}, */
 
 
-    { "*",   2, opid1('*'),         ASSOC_LEFT,  13, 0,         true},
-    { "/",   2, opid1('/'),         ASSOC_LEFT,  13, 0,         true},
-    { "%",   2, opid1('%'),         ASSOC_LEFT,  13, 0,         true},
-    { "><",  2, opid2('>','<'),     ASSOC_LEFT,  13, 0,         true},
+    { "*",      2, opid1('*'),         ASSOC_LEFT,  13, 0,         true},
+    { "/",      2, opid1('/'),         ASSOC_LEFT,  13, 0,         true},
+    { "%",      2, opid1('%'),         ASSOC_LEFT,  13, 0,         true},
+    { "><",     2, opid2('>','<'),     ASSOC_LEFT,  13, 0,         true},
 
 
-    { "+",   2, opid1('+'),         ASSOC_LEFT,  12, 0,         true},
-    { "-",   2, opid1('-'),         ASSOC_LEFT,  12, 0,         true},
+    { "+",      2, opid1('+'),         ASSOC_LEFT,  12, 0,         true},
+    { "-",      2, opid1('-'),         ASSOC_LEFT,  12, 0,         true},
 
 
-    { "<<",  2, opid2('<','<'),     ASSOC_LEFT,  11, 0,         true},
-    { ">>",  2, opid2('>','>'),     ASSOC_LEFT,  11, 0,         true},
+    { "<<",     2, opid2('<','<'),     ASSOC_LEFT,  11, 0,         true},
+    { ">>",     2, opid2('>','>'),     ASSOC_LEFT,  11, 0,         true},
 
 
-    { "<",   2, opid1('<'),         ASSOC_LEFT,  10, 0,         false},
-    { ">",   2, opid1('>'),         ASSOC_LEFT,  10, 0,         false},
-    { "<=>", 2, opid3('<','=','>'), ASSOC_LEFT,  10, 0,         true},
-    { "<=",  2, opid2('<','='),     ASSOC_LEFT,  10, 0,         false},
-    { ">=",  2, opid2('>','='),     ASSOC_LEFT,  10, 0,         false},
+    { "<",      2, opid1('<'),         ASSOC_LEFT,  10, 0,         false},
+    { ">",      2, opid1('>'),         ASSOC_LEFT,  10, 0,         false},
+    { "<=>",    2, opid3('<','=','>'), ASSOC_LEFT,  10, 0,         true},
+    { "<=",     2, opid2('<','='),     ASSOC_LEFT,  10, 0,         false},
+    { ">=",     2, opid2('>','='),     ASSOC_LEFT,  10, 0,         false},
 
 
-    { "==",  2, opid2('=','='),     ASSOC_LEFT,  9,  0,         true},
-    { "!=",  2, opid2('!','='),     ASSOC_LEFT,  9,  0,         true},
+    { "==",     2, opid2('=','='),     ASSOC_LEFT,  9,  0,         true},
+    { "!=",     2, opid2('!','='),     ASSOC_LEFT,  9,  0,         true},
 
 
-    { "&",   2, opid1('&'),         ASSOC_LEFT,  8,  0,         true},
+    { "&",      2, opid1('&'),         ASSOC_LEFT,  8,  0,         true},
 
 
-    { "^",   2, opid1('^'),         ASSOC_LEFT,  7,  0,         true},
+    { "^",      2, opid1('^'),         ASSOC_LEFT,  7,  0,         true},
 
 
-    { "|",   2, opid1('|'),         ASSOC_LEFT,  6,  0,         true},
+    { "|",      2, opid1('|'),         ASSOC_LEFT,  6,  0,         true},
 
 
-    { "&&",  2, opid2('&','&'),     ASSOC_LEFT,  5,  0,         true},
+    { "&&",     2, opid2('&','&'),     ASSOC_LEFT,  5,  0,         true},
 
 
-    { "||",  2, opid2('|','|'),     ASSOC_LEFT,  4,  0,         true},
+    { "||",     2, opid2('|','|'),     ASSOC_LEFT,  4,  0,         true},
 
 
-    { "?",   3, opid2('?',':'),     ASSOC_RIGHT, 3,  0,         true},
+    { "?",      3, opid2('?',':'),     ASSOC_RIGHT, 3,  0,         true},
 
 
-    { "=",   2, opid1('='),         ASSOC_RIGHT, 2,  0,         false},
-    { "+=",  2, opid2('+','='),     ASSOC_RIGHT, 2,  0,         false},
-    { "-=",  2, opid2('-','='),     ASSOC_RIGHT, 2,  0,         false},
-    { "*=",  2, opid2('*','='),     ASSOC_RIGHT, 2,  0,         false},
-    { "/=",  2, opid2('/','='),     ASSOC_RIGHT, 2,  0,         false},
-    { "%=",  2, opid2('%','='),     ASSOC_RIGHT, 2,  0,         false},
-    { ">>=", 2, opid3('>','>','='), ASSOC_RIGHT, 2,  0,         false},
-    { "<<=", 2, opid3('<','<','='), ASSOC_RIGHT, 2,  0,         false},
-    { "&=",  2, opid2('&','='),     ASSOC_RIGHT, 2,  0,         false},
-    { "^=",  2, opid2('^','='),     ASSOC_RIGHT, 2,  0,         false},
-    { "|=",  2, opid2('|','='),     ASSOC_RIGHT, 2,  0,         false},
+    { "=",      2, opid1('='),         ASSOC_RIGHT, 2,  0,         false},
+    { "+=",     2, opid2('+','='),     ASSOC_RIGHT, 2,  0,         false},
+    { "-=",     2, opid2('-','='),     ASSOC_RIGHT, 2,  0,         false},
+    { "*=",     2, opid2('*','='),     ASSOC_RIGHT, 2,  0,         false},
+    { "/=",     2, opid2('/','='),     ASSOC_RIGHT, 2,  0,         false},
+    { "%=",     2, opid2('%','='),     ASSOC_RIGHT, 2,  0,         false},
+    { ">>=",    2, opid3('>','>','='), ASSOC_RIGHT, 2,  0,         false},
+    { "<<=",    2, opid3('<','<','='), ASSOC_RIGHT, 2,  0,         false},
+    { "&=",     2, opid2('&','='),     ASSOC_RIGHT, 2,  0,         false},
+    { "^=",     2, opid2('^','='),     ASSOC_RIGHT, 2,  0,         false},
+    { "|=",     2, opid2('|','='),     ASSOC_RIGHT, 2,  0,         false},
 
 
-    { ":",   0, opid2(':','?'),     ASSOC_RIGHT, 1,  0,         false},
+    { ":",      0, opid2(':','?'),     ASSOC_RIGHT, 1,  0,         false},
 
 
-    { ",",   2, opid1(','),         ASSOC_LEFT,  0,  0,         false}
+    { ",",      2, opid1(','),         ASSOC_LEFT,  0,  0,         false}
 };
 
 static const oper_info fte_operators[] = {
 };
 
 static const oper_info fte_operators[] = {
index 4454085008f5471c0cbf18c72ce2294ae8681cd7..e1779da37667f157085c3e40322378c01fd657fd 100644 (file)
--- a/parser.c
+++ b/parser.c
@@ -1145,6 +1145,22 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy)
             out = (ast_expression*)asbinstore;
             break;
 
             out = (ast_expression*)asbinstore;
             break;
 
+        case opid3('l', 'e', 'n'):
+            if (exprs[0]->vtype != TYPE_STRING && exprs[0]->vtype != TYPE_ARRAY) {
+                ast_type_to_string(exprs[0], ty1, sizeof(ty1));
+                compile_error(ast_ctx(exprs[0]), "invalid type for length operator: %s", ty1);
+                return false;
+            }
+            /* strings must be const, arrays are statically sized */
+            if (exprs[0]->vtype == TYPE_STRING &&
+                !(((ast_value*)exprs[0])->hasvalue && ((ast_value*)exprs[0])->cvq == CV_CONST))
+            {
+                compile_error(ast_ctx(exprs[0]), "operand of length operator not a valid constant expression");
+                return false;
+            }
+            out = fold_op(parser->fold, op, exprs);
+            break;
+
         case opid2('~', 'P'):
             if (exprs[0]->vtype != TYPE_FLOAT && exprs[0]->vtype != TYPE_VECTOR) {
                 ast_type_to_string(exprs[0], ty1, sizeof(ty1));
         case opid2('~', 'P'):
             if (exprs[0]->vtype != TYPE_FLOAT && exprs[0]->vtype != TYPE_VECTOR) {
                 ast_type_to_string(exprs[0], ty1, sizeof(ty1));
diff --git a/tests/length.qc b/tests/length.qc
new file mode 100644 (file)
index 0000000..5abe769
--- /dev/null
@@ -0,0 +1,23 @@
+const string a = "hello world"; // 11
+float b[] = { 1, 2, 3 };        // 3
+float c[5] = { 5, 4, 3, 2, 1 }; // 5
+const float d[] = { 1 };        // 1
+
+void main() {
+    print(ftos(length a), "\n"); // 11
+    print(ftos(length b), "\n"); // 3
+    print(ftos(length c), "\n"); // 5
+    print(ftos(length d), "\n"); // 1
+
+    static float al = length(a);
+    static float bl = length(b);
+    static float cl = length(c);
+    static float dl = length(d);
+
+    print(ftos(al), "\n"); // 11
+    print(ftos(bl), "\n"); // 3
+    print(ftos(cl), "\n"); // 5
+    print(ftos(dl), "\n"); // 1
+
+    print(ftos(length "hello world"), "\n"); // 11
+}
diff --git a/tests/length.tmpl b/tests/length.tmpl
new file mode 100644 (file)
index 0000000..292b6ad
--- /dev/null
@@ -0,0 +1,13 @@
+I: length.qc
+D: length operator
+T: -execute
+C: -std=gmqcc
+M: 11
+M: 3
+M: 5
+M: 1
+M: 11
+M: 3
+M: 5
+M: 1
+M: 11