├── .gitattributes ├── .gitignore ├── AUTHORS ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── README ├── algo.h ├── ast.cpp ├── ast.h ├── code.cpp ├── conout.cpp ├── doc ├── gmqcc.1 └── qcvm.1 ├── exec.cpp ├── fold.cpp ├── fold.h ├── ftepp.cpp ├── gmqcc.h ├── gmqcc.ini.example ├── intrin.cpp ├── intrin.h ├── ir.cpp ├── ir.h ├── lexer.cpp ├── lexer.h ├── main.cpp ├── misc └── check-doc.sh ├── opts.cpp ├── opts.def ├── parser.cpp ├── parser.h ├── stat.cpp ├── test.cpp ├── tests ├── accumulate.qc ├── accumulate.tmpl ├── aliases.qc ├── aliases.tmpl ├── arithexcept.qc ├── arithexcept.tmpl ├── arithexcept_of.tmpl ├── arithexcept_uf.tmpl ├── arrays.qc ├── arrays.tmpl ├── arrays2.qc ├── arrays2.tmpl ├── bitnot.qc ├── bitnot.tmpl ├── bitnotgmqcc.tmpl ├── break.qc ├── break.tmpl ├── builtin.qc ├── builtin.tmpl ├── calls.qc ├── calls.tmpl ├── correct-logic-1-s.tmpl ├── correct-logic-1.tmpl ├── correct-logic-2-s.tmpl ├── correct-logic-2.tmpl ├── correct-logic.qc ├── correct-vs-short-1.tmpl ├── correct-vs-short-2.tmpl ├── correct-vs-short-3.tmpl ├── correct-vs-short.qc ├── defs.qh ├── dots.qc ├── dots.tmpl ├── enum.qc ├── enum.tmpl ├── equality.qc ├── equality.tmpl ├── exponentiation.qc ├── exponentiation.tmpl ├── exprforbuiltins.qc ├── exprforbuiltins.tmpl ├── fieldfuncs.qc ├── fieldfuncs.tmpl ├── fieldparams.qc ├── fieldparams.tmpl ├── forloop.qc ├── forloop.tmpl ├── framemacro.qc ├── framemacro.tmpl ├── functions-as-params.qc ├── functions-as-params.tmpl ├── goto.qc ├── goto.tmpl ├── ifs.qc ├── ifs.tmpl ├── inexact-local.qc ├── inexact-local.tmpl ├── inexact.qc ├── inexact.tmpl ├── last.qc ├── last.tmpl ├── last2.tmpl ├── length.qc ├── length.tmpl ├── memberbinop.qc ├── memberbinop.tmpl ├── mul_vf.qc ├── mul_vf.tmpl ├── ngraphs.qc ├── ngraphs.tmpl ├── noref.qc ├── noref.tmpl ├── noreturn.qc ├── noreturn1.tmpl ├── noreturn2.tmpl ├── noreturn3.tmpl ├── noreturn4.tmpl ├── octal.qc ├── octal.tmpl ├── operators.qc ├── operators.tmpl ├── order.qc ├── order.tmpl ├── param8.qc ├── param8.tmpl ├── paramomit.qc ├── paramomit.tmpl ├── parens.qc ├── parens.tmpl ├── parent_block_scope_for_locals.qc ├── parent_block_scope_for_locals.tmpl ├── perl-logic.qc ├── perl-logic.tmpl ├── pmacros.qc ├── pmacros.tmpl ├── pointlife.qc ├── pointlife.tmpl ├── pops.qc ├── pops.tmpl ├── pp_va_args.qc ├── pp_va_args.tmpl ├── ppcat.qc ├── ppcat.tmpl ├── pperror.qc ├── pperror.tmpl ├── ppindirectexpand.qc ├── ppindirectexpand.tmpl ├── predef_func.qc ├── predef_func.tmpl ├── predef_func_concat.tmpl ├── rassign-fail.tmpl ├── rassign.qc ├── rassign.tmpl ├── short-logic.qc ├── short-logic.tmpl ├── split-vectors.qc ├── split-vectors.tmpl ├── state-emu.tmpl ├── state.qc ├── state.tmpl ├── switch.qc ├── switch.tmpl ├── ternary-fte.tmpl ├── ternary.qc ├── ternary.tmpl ├── truth-flags-1-s.tmpl ├── truth-flags-1.tmpl ├── truth-flags-2-s.tmpl ├── truth-flags-2.qc ├── truth-flags-2.tmpl ├── truth-flags-3-s.tmpl ├── truth-flags-3.tmpl ├── truth.qc ├── truth.tmpl ├── truth2.tmpl ├── truth3.tmpl ├── typedefs.qc ├── typedefs.tmpl ├── uninit.qc ├── uninit.tmpl ├── uninit2.tmpl ├── utf8-2.tmpl ├── utf8.qc ├── utf8.tmpl ├── var-search-order.qc ├── var-search-order.tmpl ├── varargs.qc ├── varargs.tmpl ├── varargs2.qc ├── varargs2.tmpl ├── variadic.qc ├── variadic.tmpl ├── vec_ops.qc ├── vec_ops.tmpl ├── vecfields-broken.tmpl ├── vecfields.qc ├── vecfields.tmpl ├── vecmath.qc ├── vecmath.tmpl ├── vector-init.qc ├── vector-init.tmpl ├── vector-init2.tmpl ├── vector-init3.tmpl ├── xor-optimized.tmpl ├── xor.qc └── xor.tmpl ├── utf8.cpp └── util.cpp /.gitattributes: -------------------------------------------------------------------------------- 1 | #dissalow trailing whitespace 2 | *.c whitespace=-trailing-space 3 | *.h whitespace=-trailing-space 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.d 3 | *.orig 4 | *.rej 5 | *.patch 6 | *.diff 7 | *.exe 8 | 9 | gmqcc 10 | gmqpak 11 | qcvm 12 | testsuite 13 | 14 | build/ 15 | .idea/ 16 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Authors: 2 | Dale `graphitemaster` Weiler - Charismatic Visionary / Programmer 3 | Wolfgang `Blub\w` Bumiller - Main Programmer 4 | 5 | Thanks to: 6 | Forest `LordHavoc` Hale - Technical support and assistance 7 | Rudolf `divVerent` Polzer - Technical support and assistance 8 | Matthias `matthiaskrgr` Krüger - Miscellaneous assistance 9 | Samual `Samual` Lenks - Preprocessor assistance 10 | Igor `ignatenkobrain` Gnatenko - Fedora packages 11 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | project(gmqcc) 3 | 4 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 5 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") 6 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions") 7 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti") 8 | 9 | set(SOURCE_FILES 10 | algo.h 11 | ast.cpp ast.h 12 | code.cpp 13 | conout.cpp 14 | fold.cpp fold.h 15 | ftepp.cpp 16 | gmqcc.h 17 | intrin.cpp intrin.h 18 | ir.cpp 19 | ir.h 20 | lexer.cpp lexer.h 21 | opts.cpp 22 | parser.cpp parser.h 23 | stat.cpp 24 | utf8.cpp 25 | util.cpp) 26 | add_library(gmqcclib ${SOURCE_FILES}) 27 | 28 | add_executable(gmqcc main.cpp) 29 | target_link_libraries(gmqcc gmqcclib) 30 | 31 | add_executable(testsuite test.cpp) 32 | target_link_libraries(testsuite gmqcclib) 33 | 34 | add_executable(qcvm exec.cpp) 35 | target_link_libraries(qcvm gmqcclib) 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012, 2013, 2014, 2015 2 | Dale Weiler 3 | Wolfgang Bumiller 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Compilation options: 2 | # * LTO - Link time optimization [default=0] 3 | # * ASAN - Address sanitizer [default=0] 4 | # * UBSAN - Undefined behavior sanitizer [default=0] 5 | # * DEBUG - Debug build [default=0] 6 | # * UNUSED - Remove unused references [default=1] 7 | # * SRCDIR - Out of tree builds [default=./] 8 | LTO ?= 0 9 | ASAN ?= 0 10 | UBSAN ?= 0 11 | DEBUG ?= 0 12 | UNUSED ?= 1 13 | SRCDIR ?= ./ 14 | 15 | # Determine if we're building for Windows or not so we can set the right file 16 | # extensions for the binaries and exclude the testsuite because it doesn't build 17 | # for that platform. 18 | ifeq ($(OS),Windows_NT) 19 | GMQCC := gmqcc.exe 20 | QCVM := qcvm.exe 21 | else 22 | GMQCC := gmqcc 23 | QCVM := qcvm 24 | TESTSUITE := testsuite 25 | endif 26 | 27 | # C++ compiler 28 | CXX ?= clang++ 29 | 30 | # Build artifact directories 31 | OBJDIR := .build/objs 32 | DEPDIR := .build/deps 33 | 34 | # Collect all the source files for GMQCC. 35 | GSRCS := ast.cpp 36 | GSRCS += code.cpp 37 | GSRCS += conout.cpp 38 | GSRCS += fold.cpp 39 | GSRCS += ftepp.cpp 40 | GSRCS += intrin.cpp 41 | GSRCS += ir.cpp 42 | GSRCS += lexer.cpp 43 | GSRCS += main.cpp 44 | GSRCS += opts.cpp 45 | GSRCS += parser.cpp 46 | GSRCS += stat.cpp 47 | GSRCS += utf8.cpp 48 | GSRCS += util.cpp 49 | 50 | # Collect all the source files for QCVM. 51 | QSRCS := exec.cpp 52 | QSRCS += stat.cpp 53 | QSRCS += util.cpp 54 | 55 | # Collect all the source files for TESTSUITE. 56 | TSRCS := conout.cpp 57 | TSRCS += opts.cpp 58 | TSRCS += stat.cpp 59 | TSRCS += test.cpp 60 | TSRCS += util.cpp 61 | 62 | # 63 | # Compilation flags 64 | # 65 | CXXFLAGS := -Wall 66 | CXXFLAGS += -Wextra 67 | CXXFLAGS += -Wno-parentheses 68 | CXXFLAGS += -Wno-class-memaccess 69 | CXXFLAGS += -Wno-implicit-fallthrough 70 | CXXFLAGS += -std=c++11 71 | 72 | # Disable some unneeded features. 73 | CXXFLAGS += -fno-exceptions 74 | CXXFLAGS += -fno-rtti 75 | CXXFLAGS += -fno-asynchronous-unwind-tables 76 | 77 | # Give each function and data it's own section so the linker can remove unused 78 | # references to each, producing smaller, tighter binaries. 79 | ifeq ($(UNUSED),1) 80 | CXXFLAGS += -ffunction-sections 81 | CXXFLAGS += -fdata-sections 82 | endif 83 | 84 | # Enable link-time optimizations if requested. 85 | ifeq ($(LTO),1) 86 | CXXFLAGS += -flto 87 | endif 88 | 89 | ifeq ($(DEBUG),1) 90 | # Ensure there is a frame-pointer in debug builds. 91 | CXXFLAGS += -fno-omit-frame-pointer 92 | 93 | # Disable all optimizations in debug builds. 94 | CXXFLAGS += -O0 95 | 96 | # Enable debug symbols. 97 | CXXFLAGS += -g 98 | else 99 | # Disable all the stack protection features in release builds. 100 | CXXFLAGS += -fno-stack-protector 101 | CXXFLAGS += -fno-stack-check 102 | 103 | # Disable frame pointer in release builds when AddressSanitizer isn't present. 104 | ifeq ($(ASAN),1) 105 | CXXFLAGS += -fno-omit-frame-pointer 106 | else 107 | CXXFLAGS += -fomit-frame-pointer 108 | endif 109 | 110 | # Highest optimization flag in release builds. 111 | CXXFLAGS += -O3 112 | endif 113 | 114 | # Sanitizer selection 115 | ifeq ($(ASAN),1) 116 | CXXFLAGS += -fsanitize=address 117 | endif 118 | ifeq ($(UBSAN),1) 119 | CXXFLAGS += -fsanitize=undefined 120 | endif 121 | 122 | # 123 | # Dependency flags 124 | # 125 | DEPFLAGS := -MMD 126 | DEPFLAGS += -MP 127 | 128 | # 129 | # Linker flags 130 | # 131 | LDFLAGS := 132 | 133 | # Remove unreferenced sections 134 | ifeq ($(UNUSED),1) 135 | LDFLAGS += -Wl,--gc-sections 136 | endif 137 | 138 | # Enable link-time optimizations if request. 139 | ifeq ($(LTO),1) 140 | LDFLAGS += -flto 141 | endif 142 | 143 | # Sanitizer selection 144 | ifeq ($(ASAN),1) 145 | LDFLAGS += -fsanitize=address 146 | endif 147 | ifeq ($(UBSAN),1) 148 | LDFLAGS += -fsanitize=undefined 149 | endif 150 | 151 | # Strip the binaries when not a debug build 152 | ifneq (,$(findstring -g,$(CXXFLAGS))) 153 | STRIP := true 154 | else 155 | STRIP := strip 156 | endif 157 | 158 | all: $(GMQCC) $(QCVM) $(TESTSUITE) 159 | 160 | # Build artifact directories. 161 | $(DEPDIR): 162 | @mkdir -p $(DEPDIR) 163 | $(OBJDIR): 164 | @mkdir -p $(OBJDIR) 165 | 166 | $(OBJDIR)/%.o: %.cpp $(DEPDIR)/%.d | $(OBJDIR) $(DEPDIR) 167 | $(CXX) -MT $@ $(DEPFLAGS) -MF $(DEPDIR)/$*.Td $(CXXFLAGS) -c -o $@ $< 168 | @mv -f $(DEPDIR)/$*.Td $(DEPDIR)/$*.d 169 | 170 | $(GMQCC): $(filter %.o,$(GSRCS:%.cpp=$(OBJDIR)/%.o)) 171 | $(CXX) $^ $(LDFLAGS) -o $@ 172 | $(STRIP) $@ 173 | 174 | $(QCVM): $(filter %.o,$(QSRCS:%.cpp=$(OBJDIR)/%.o)) 175 | $(CXX) $^ $(LDFLAGS) -o $@ 176 | $(STRIP) $@ 177 | 178 | $(TESTSUITE): $(filter %.o,$(TSRCS:%.cpp=$(OBJDIR)/%.o)) 179 | $(CXX) $^ $(LDFLAGS) -o $@ 180 | $(STRIP) $@ 181 | 182 | # Determine if the tests should be run. 183 | RUNTESTS := true 184 | ifdef TESTSUITE 185 | RUNTESTS := ./$(TESTSUITE) 186 | endif 187 | 188 | test: $(QCVM) $(TESTSUITE) 189 | @$(RUNTESTS) 190 | 191 | clean: 192 | rm -rf $(DEPDIR) $(OBJDIR) 193 | 194 | .PHONY: test clean $(DEPDIR) $(OBJDIR) 195 | 196 | # Dependencies 197 | $(filter %.d,$(GSRCS:%.cpp=$(DEPDIR)/%.d)): 198 | include $(wildcard $@) 199 | 200 | $(filter %.d,$(QSRCS:%.cpp=$(DEPDIR)/%.d)): 201 | include $(wildcard $@) 202 | 203 | $(filter %.d,$(TSRCS:%.cpp=$(DEPDIR)/%.d)): 204 | include $(wildcard $@) 205 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | An improved QuakeC compiler 2 | -------------------------------------------------------------------------------- /algo.h: -------------------------------------------------------------------------------- 1 | #ifndef GMQCC_ALGO_HDR 2 | #define GMQCC_ALGO_HDR 3 | 4 | namespace algo { 5 | 6 | template 7 | void shiftback(ITER element, ITER end) { 8 | //typename ITER::value_type backup(move(*element)); // hold the element 9 | typename std::remove_reference::type backup(move(*element)); // hold the element 10 | ITER p = element++; 11 | for (; element != end; p = element++) 12 | *p = move(*element); 13 | *p = move(backup); 14 | } 15 | 16 | } // ::algo 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /ast.h: -------------------------------------------------------------------------------- 1 | #ifndef GMQCC_AST_HDR 2 | #define GMQCC_AST_HDR 3 | #include 4 | #include "ir.h" 5 | 6 | typedef uint16_t ast_flag_t; 7 | 8 | /* Note: I will not be using a _t suffix for the 9 | * "main" ast node types for now. 10 | */ 11 | 12 | struct ast_node; 13 | struct ast_expression; 14 | struct ast_value; 15 | struct ast_function; 16 | struct ast_block; 17 | struct ast_binary; 18 | struct ast_store; 19 | struct ast_binstore; 20 | struct ast_entfield; 21 | struct ast_ifthen; 22 | struct ast_ternary; 23 | struct ast_loop; 24 | struct ast_call; 25 | struct ast_unary; 26 | struct ast_return; 27 | struct ast_member; 28 | struct ast_array_index; 29 | struct ast_breakcont; 30 | struct ast_switch; 31 | struct ast_label; 32 | struct ast_goto; 33 | struct ast_argpipe; 34 | struct ast_state; 35 | 36 | enum { 37 | AST_FLAG_VARIADIC = 1 << 0, 38 | AST_FLAG_NORETURN = 1 << 1, 39 | AST_FLAG_INLINE = 1 << 2, 40 | AST_FLAG_INITIALIZED = 1 << 3, 41 | AST_FLAG_DEPRECATED = 1 << 4, 42 | AST_FLAG_INCLUDE_DEF = 1 << 5, 43 | AST_FLAG_IS_VARARG = 1 << 6, 44 | AST_FLAG_ALIAS = 1 << 7, 45 | AST_FLAG_ERASEABLE = 1 << 8, 46 | AST_FLAG_NOERASE = 1 << 9, /* Never allow it to be erased, even if ERASEABLE is present */ 47 | AST_FLAG_ACCUMULATE = 1 << 10, 48 | 49 | /* An array declared as [] 50 | * so that the size is taken from the initializer 51 | */ 52 | AST_FLAG_ARRAY_INIT = 1 << 11, 53 | 54 | AST_FLAG_FINAL_DECL = 1 << 12, 55 | 56 | /* Several coverage options 57 | * AST_FLAG_COVERAGE means there was an explicit [[coverage]] attribute, 58 | * which will overwrite the default set via the commandline switches. 59 | * BLOCK_COVERAGE inserts coverage() calls into every basic block. 60 | * In the future there might be more options like tracking variable access 61 | * by creating get/set wrapper functions. 62 | */ 63 | AST_FLAG_COVERAGE = 1 << 13, 64 | AST_FLAG_BLOCK_COVERAGE = 1 << 14, 65 | 66 | /* 67 | * Propagates norefness to the IR so the unused (read/write) check can be 68 | * more intelligently done. 69 | */ 70 | AST_FLAG_NOREF = 1 << 15, 71 | 72 | AST_FLAG_LAST, 73 | AST_FLAG_TYPE_MASK = (AST_FLAG_VARIADIC | AST_FLAG_NORETURN), 74 | AST_FLAG_COVERAGE_MASK = (AST_FLAG_BLOCK_COVERAGE) 75 | }; 76 | 77 | enum { 78 | TYPE_ast_node, /* 0 */ 79 | TYPE_ast_expression, /* 1 */ 80 | TYPE_ast_value, /* 2 */ 81 | TYPE_ast_function, /* 3 */ 82 | TYPE_ast_block, /* 4 */ 83 | TYPE_ast_binary, /* 5 */ 84 | TYPE_ast_store, /* 6 */ 85 | TYPE_ast_binstore, /* 7 */ 86 | TYPE_ast_entfield, /* 8 */ 87 | TYPE_ast_ifthen, /* 9 */ 88 | TYPE_ast_ternary, /* 10 */ 89 | TYPE_ast_loop, /* 11 */ 90 | TYPE_ast_call, /* 12 */ 91 | TYPE_ast_unary, /* 13 */ 92 | TYPE_ast_return, /* 14 */ 93 | TYPE_ast_member, /* 15 */ 94 | TYPE_ast_array_index, /* 16 */ 95 | TYPE_ast_breakcont, /* 17 */ 96 | TYPE_ast_switch, /* 18 */ 97 | TYPE_ast_label, /* 19 */ 98 | TYPE_ast_goto, /* 20 */ 99 | TYPE_ast_argpipe, /* 21 */ 100 | TYPE_ast_state /* 22 */ 101 | }; 102 | 103 | #define ast_istype(x, t) ( (x)->m_node_type == (TYPE_##t) ) 104 | 105 | struct ast_node 106 | { 107 | ast_node() = delete; 108 | ast_node(lex_ctx_t, int nodetype); 109 | virtual ~ast_node(); 110 | 111 | lex_ctx_t m_context; 112 | /* I don't feel comfortable using keywords like 'delete' as names... */ 113 | int m_node_type; 114 | /* keep_node: if a node contains this node, 'keep_node' 115 | * prevents its dtor from destroying this node as well. 116 | */ 117 | bool m_keep_node; 118 | bool m_side_effects; 119 | 120 | void propagateSideEffects(const ast_node *other); 121 | }; 122 | 123 | #define ast_unref(x) do \ 124 | { \ 125 | if (! (x)->m_keep_node ) { \ 126 | delete (x); \ 127 | } \ 128 | } while(0) 129 | 130 | enum class ast_copy_type_t { value }; 131 | static const ast_copy_type_t ast_copy_type = ast_copy_type_t::value; 132 | 133 | /* TODO: the codegen function should take an output-type parameter 134 | * indicating whether a variable, type, label etc. is expected, and 135 | * an environment! 136 | * Then later an ast_ident could have a codegen using this to figure 137 | * out what to look for. 138 | * eg. in code which uses a not-yet defined variable, the expression 139 | * would take an ast_ident, and the codegen would be called with 140 | * type `expression`, so the ast_ident's codegen would search for 141 | * variables through the environment (or functions, constants...). 142 | */ 143 | struct ast_expression : ast_node { 144 | ast_expression() = delete; 145 | ast_expression(lex_ctx_t ctx, int nodetype, qc_type vtype); 146 | ast_expression(lex_ctx_t ctx, int nodetype); 147 | ~ast_expression(); 148 | 149 | ast_expression(ast_copy_type_t, const ast_expression&); 150 | ast_expression(ast_copy_type_t, lex_ctx_t ctx, const ast_expression&); 151 | ast_expression(ast_copy_type_t, int nodetype, const ast_expression&); 152 | ast_expression(ast_copy_type_t, int nodetype, lex_ctx_t ctx, const ast_expression&); 153 | 154 | static ast_expression *shallowType(lex_ctx_t ctx, qc_type vtype); 155 | 156 | bool compareType(const ast_expression &other) const; 157 | void adoptType(const ast_expression &other); 158 | 159 | qc_type m_vtype = TYPE_VOID; 160 | ast_expression *m_next = nullptr; 161 | /* arrays get a member-count */ 162 | size_t m_count = 0; 163 | std::vector> m_type_params; 164 | 165 | ast_flag_t m_flags = 0; 166 | /* void foo(string...) gets varparam set as a restriction 167 | * for variadic parameters 168 | */ 169 | ast_expression *m_varparam = nullptr; 170 | /* The codegen functions should store their output values 171 | * so we can call it multiple times without re-evaluating. 172 | * Store lvalue and rvalue seperately though. So that 173 | * ast_entfield for example can generate both if required. 174 | */ 175 | ir_value *m_outl = nullptr; 176 | ir_value *m_outr = nullptr; 177 | 178 | virtual bool codegen(ast_function *current, bool lvalue, ir_value **out); 179 | }; 180 | 181 | /* Value 182 | * 183 | * Types are also values, both have a type and a name. 184 | * especially considering possible constructs like typedefs. 185 | * typedef float foo; 186 | * is like creating a 'float foo', foo serving as the type's name. 187 | */ 188 | union basic_value_t { 189 | qcfloat_t vfloat; 190 | int vint; 191 | vec3_t vvec; 192 | const char *vstring; 193 | int ventity; 194 | ast_function *vfunc; 195 | ast_value *vfield; 196 | }; 197 | 198 | struct ast_value : ast_expression 199 | { 200 | ast_value() = delete; 201 | ast_value(lex_ctx_t ctx, const std::string &name, qc_type qctype); 202 | ~ast_value(); 203 | 204 | ast_value(ast_copy_type_t, const ast_expression&, const std::string&); 205 | ast_value(ast_copy_type_t, const ast_value&); 206 | ast_value(ast_copy_type_t, const ast_value&, const std::string&); 207 | 208 | bool codegen(ast_function *current, bool lvalue, ir_value **out) override; 209 | 210 | void addParam(ast_value*); 211 | 212 | bool generateGlobal(ir_builder*, bool isfield); 213 | bool generateLocal(ir_function*, bool param); 214 | bool generateAccessors(ir_builder*); 215 | 216 | std::string m_name; 217 | std::string m_desc; 218 | 219 | const char *m_argcounter = nullptr; 220 | 221 | int m_cvq = CV_NONE; /* const/var qualifier */ 222 | bool m_isfield = false; /* this declares a field */ 223 | bool m_isimm = false; /* an immediate, not just const */ 224 | bool m_hasvalue = false; 225 | bool m_inexact = false; /* inexact coming from folded expression */ 226 | basic_value_t m_constval; 227 | /* for TYPE_ARRAY we have an optional vector 228 | * of constants when an initializer list 229 | * was provided. 230 | */ 231 | std::vector m_initlist; 232 | 233 | ir_value *m_ir_v = nullptr; 234 | std::vector m_ir_values; 235 | size_t m_ir_value_count = 0; 236 | 237 | /* ONLY for arrays in progs version up to 6 */ 238 | ast_value *m_setter = nullptr; 239 | ast_value *m_getter = nullptr; 240 | 241 | bool m_intrinsic = false; /* true if associated with intrinsic */ 242 | 243 | private: 244 | bool generateGlobalFunction(ir_builder*); 245 | bool generateGlobalField(ir_builder*); 246 | ir_value *prepareGlobalArray(ir_builder*); 247 | bool setGlobalArray(); 248 | bool checkArray(const ast_value &array) const; 249 | }; 250 | 251 | void ast_type_to_string(const ast_expression *e, char *buf, size_t bufsize); 252 | 253 | enum ast_binary_ref { 254 | AST_REF_NONE = 0, 255 | AST_REF_LEFT = 1 << 1, 256 | AST_REF_RIGHT = 1 << 2, 257 | AST_REF_ALL = (AST_REF_LEFT | AST_REF_RIGHT) 258 | }; 259 | 260 | 261 | /* Binary 262 | * 263 | * A value-returning binary expression. 264 | */ 265 | struct ast_binary : ast_expression 266 | { 267 | ast_binary() = delete; 268 | ast_binary(lex_ctx_t ctx, int op, ast_expression *l, ast_expression *r); 269 | ~ast_binary(); 270 | 271 | bool codegen(ast_function *current, bool lvalue, ir_value **out) override; 272 | 273 | int m_op; 274 | ast_expression *m_left; 275 | ast_expression *m_right; 276 | ast_binary_ref m_refs; 277 | bool m_right_first; 278 | }; 279 | 280 | /* Binstore 281 | * 282 | * An assignment including a binary expression with the source as left operand. 283 | * Eg. a += b; is a binstore { INSTR_STORE, INSTR_ADD, a, b } 284 | */ 285 | struct ast_binstore : ast_expression 286 | { 287 | ast_binstore() = delete; 288 | ast_binstore(lex_ctx_t ctx, int storeop, int mathop, ast_expression *l, ast_expression *r); 289 | ~ast_binstore(); 290 | 291 | bool codegen(ast_function *current, bool lvalue, ir_value **out) override; 292 | 293 | int m_opstore; 294 | int m_opbin; 295 | ast_expression *m_dest; 296 | ast_expression *m_source; 297 | /* for &~= which uses the destination in a binary in source we can use this */ 298 | bool m_keep_dest; 299 | }; 300 | 301 | /* Unary 302 | * 303 | * Regular unary expressions: not,neg 304 | */ 305 | struct ast_unary : ast_expression 306 | { 307 | ast_unary() = delete; 308 | ~ast_unary(); 309 | 310 | static ast_unary* make(lex_ctx_t ctx, int op, ast_expression *expr); 311 | 312 | bool codegen(ast_function *current, bool lvalue, ir_value **out) override; 313 | 314 | int m_op; 315 | ast_expression *m_operand; 316 | 317 | private: 318 | ast_unary(lex_ctx_t ctx, int op, ast_expression *expr); 319 | }; 320 | 321 | /* Return 322 | * 323 | * Make sure 'return' only happens at the end of a block, otherwise the IR 324 | * will refuse to create further instructions. 325 | * This should be honored by the parser. 326 | */ 327 | struct ast_return : ast_expression 328 | { 329 | ast_return() = delete; 330 | ast_return(lex_ctx_t ctx, ast_expression *expr); 331 | ~ast_return(); 332 | 333 | bool codegen(ast_function *current, bool lvalue, ir_value **out) override; 334 | 335 | ast_expression *m_operand; 336 | }; 337 | 338 | /* Entity-field 339 | * 340 | * This must do 2 things: 341 | * -) Provide a way to fetch an entity field value. (Rvalue) 342 | * -) Provide a pointer to an entity field. (Lvalue) 343 | * The problem: 344 | * In original QC, there's only a STORE via pointer, but 345 | * no LOAD via pointer. 346 | * So we must know beforehand if we are going to read or assign 347 | * the field. 348 | * For this we will have to extend the codegen() functions with 349 | * a flag saying whether or not we need an L or an R-value. 350 | */ 351 | struct ast_entfield : ast_expression 352 | { 353 | ast_entfield() = delete; 354 | ast_entfield(lex_ctx_t ctx, ast_expression *entity, ast_expression *field); 355 | ast_entfield(lex_ctx_t ctx, ast_expression *entity, ast_expression *field, const ast_expression *outtype); 356 | ~ast_entfield(); 357 | 358 | bool codegen(ast_function *current, bool lvalue, ir_value **out) override; 359 | 360 | // The entity can come from an expression of course. 361 | ast_expression *m_entity; 362 | // As can the field, it just must result in a value of TYPE_FIELD 363 | ast_expression *m_field; 364 | }; 365 | 366 | /* Member access: 367 | * 368 | * For now used for vectors. If we get structs or unions 369 | * we can have them handled here as well. 370 | */ 371 | struct ast_member : ast_expression 372 | { 373 | static ast_member *make(lex_ctx_t ctx, ast_expression *owner, unsigned int field, const std::string &name); 374 | ~ast_member(); 375 | 376 | bool codegen(ast_function *current, bool lvalue, ir_value **out) override; 377 | 378 | ast_expression *m_owner; 379 | unsigned int m_field; 380 | std::string m_name; 381 | bool m_rvalue; 382 | 383 | private: 384 | ast_member() = delete; 385 | ast_member(lex_ctx_t ctx, ast_expression *owner, unsigned int field, const std::string &name); 386 | }; 387 | 388 | /* Array index access: 389 | * 390 | * QC forces us to take special action on arrays: 391 | * an ast_store on an ast_array_index must not codegen the index, 392 | * but call its setter - unless we have an instruction set which supports 393 | * what we need. 394 | * Any other array index access will be codegened to a call to the getter. 395 | * In any case, accessing an element via a compiletime-constant index will 396 | * result in quick access to that variable. 397 | */ 398 | struct ast_array_index : ast_expression 399 | { 400 | static ast_array_index* make(lex_ctx_t ctx, ast_expression *array, ast_expression *index); 401 | ~ast_array_index(); 402 | 403 | bool codegen(ast_function *current, bool lvalue, ir_value **out) override; 404 | 405 | ast_expression *m_array; 406 | ast_expression *m_index; 407 | private: 408 | ast_array_index() = delete; 409 | ast_array_index(lex_ctx_t ctx, ast_expression *array, ast_expression *index); 410 | }; 411 | 412 | /* Vararg pipe node: 413 | * 414 | * copy all varargs starting from a specific index 415 | */ 416 | struct ast_argpipe : ast_expression 417 | { 418 | ast_argpipe() = delete; 419 | ast_argpipe(lex_ctx_t ctx, ast_expression *index); 420 | 421 | bool codegen(ast_function *current, bool lvalue, ir_value **out) override; 422 | 423 | ~ast_argpipe(); 424 | ast_expression *m_index; 425 | }; 426 | 427 | /* Store 428 | * 429 | * Stores left<-right and returns left. 430 | * Specialized binary expression node 431 | */ 432 | struct ast_store : ast_expression 433 | { 434 | ast_store() = delete; 435 | ast_store(lex_ctx_t ctx, int op, ast_expression *d, ast_expression *s); 436 | ~ast_store(); 437 | 438 | bool codegen(ast_function *current, bool lvalue, ir_value **out) override; 439 | 440 | int m_op; 441 | ast_expression *m_dest; 442 | ast_expression *m_source; 443 | }; 444 | 445 | /* If 446 | * 447 | * A general 'if then else' statement, either side can be nullptr and will 448 | * thus be omitted. It is an error for *both* cases to be nullptr at once. 449 | * 450 | * During its 'codegen' it'll be changing the ast_function's block. 451 | * 452 | * An if is also an "expression". Its codegen will put nullptr into the 453 | * output field though. For ternary expressions an ast_ternary will be 454 | * added. 455 | */ 456 | struct ast_ifthen : ast_expression 457 | { 458 | ast_ifthen() = delete; 459 | ast_ifthen(lex_ctx_t ctx, ast_expression *cond, ast_expression *ontrue, ast_expression *onfalse); 460 | ~ast_ifthen(); 461 | 462 | bool codegen(ast_function *current, bool lvalue, ir_value **out) override; 463 | 464 | ast_expression *m_cond; 465 | /* It's all just 'expressions', since an ast_block is one too. */ 466 | ast_expression *m_on_true; 467 | ast_expression *m_on_false; 468 | }; 469 | 470 | /* Ternary expressions... 471 | * 472 | * Contrary to 'if-then-else' nodes, ternary expressions actually 473 | * return a value, otherwise they behave the very same way. 474 | * The difference in 'codegen' is that it'll return the value of 475 | * a PHI node. 476 | * 477 | * The other difference is that in an ast_ternary, NEITHER side 478 | * must be nullptr, there's ALWAYS an else branch. 479 | * 480 | * This is the only ast_node beside ast_value which contains 481 | * an ir_value. Theoretically we don't need to remember it though. 482 | */ 483 | struct ast_ternary : ast_expression 484 | { 485 | ast_ternary() = delete; 486 | ast_ternary(lex_ctx_t ctx, ast_expression *cond, ast_expression *ontrue, ast_expression *onfalse); 487 | ~ast_ternary(); 488 | 489 | bool codegen(ast_function *current, bool lvalue, ir_value **out) override; 490 | 491 | ast_expression *m_cond; 492 | /* It's all just 'expressions', since an ast_block is one too. */ 493 | ast_expression *m_on_true; 494 | ast_expression *m_on_false; 495 | }; 496 | 497 | /* A general loop node 498 | * 499 | * For convenience it contains 4 parts: 500 | * -) (ini) = initializing expression 501 | * -) (pre) = pre-loop condition 502 | * -) (pst) = post-loop condition 503 | * -) (inc) = "increment" expression 504 | * The following is a psudo-representation of this loop 505 | * note that '=>' bears the logical meaning of "implies". 506 | * (a => b) equals (!a || b) 507 | 508 | {ini}; 509 | while (has_pre => {pre}) 510 | { 511 | {body}; 512 | 513 | continue: // a 'continue' will jump here 514 | if (has_pst => {pst}) 515 | break; 516 | 517 | {inc}; 518 | } 519 | */ 520 | struct ast_loop : ast_expression 521 | { 522 | ast_loop() = delete; 523 | ast_loop(lex_ctx_t ctx, 524 | ast_expression *initexpr, 525 | ast_expression *precond, bool pre_not, 526 | ast_expression *postcond, bool post_not, 527 | ast_expression *increment, 528 | ast_expression *body); 529 | ~ast_loop(); 530 | 531 | bool codegen(ast_function *current, bool lvalue, ir_value **out) override; 532 | 533 | ast_expression *m_initexpr; 534 | ast_expression *m_precond; 535 | ast_expression *m_postcond; 536 | ast_expression *m_increment; 537 | ast_expression *m_body; 538 | /* For now we allow a seperate flag on whether or not the condition 539 | * is supposed to be true or false. 540 | * That way, the parser can generate a 'while not(!x)' for `while(x)` 541 | * if desired, which is useful for the new -f{true,false}-empty-strings 542 | * flag. 543 | */ 544 | bool m_pre_not; 545 | bool m_post_not; 546 | }; 547 | 548 | /* Break/Continue 549 | */ 550 | struct ast_breakcont : ast_expression 551 | { 552 | ast_breakcont() = delete; 553 | ast_breakcont(lex_ctx_t ctx, bool iscont, unsigned int levels); 554 | ~ast_breakcont(); 555 | 556 | bool codegen(ast_function *current, bool lvalue, ir_value **out) override; 557 | 558 | 559 | bool m_is_continue; 560 | unsigned int m_levels; 561 | }; 562 | 563 | /* Switch Statements 564 | * 565 | * A few notes about this: with the original QCVM, no real optimization 566 | * is possible. The SWITCH instruction set isn't really helping a lot, since 567 | * it only collapes the EQ and IF instructions into one. 568 | * Note: Declaring local variables inside caseblocks is normal. 569 | * Since we don't have to deal with a stack there's no unnatural behaviour to 570 | * be expected from it. 571 | * TODO: Ticket #20 572 | */ 573 | struct ast_switch_case { 574 | ast_expression *m_value; /* #20 will replace this */ 575 | ast_expression *m_code; 576 | }; 577 | 578 | struct ast_switch : ast_expression 579 | { 580 | ast_switch() = delete; 581 | ast_switch(lex_ctx_t ctx, ast_expression *op); 582 | ~ast_switch(); 583 | 584 | bool codegen(ast_function *current, bool lvalue, ir_value **out) override; 585 | 586 | ast_expression *m_operand; 587 | std::vector m_cases; 588 | }; 589 | 590 | 591 | /* Label nodes 592 | * 593 | * Introduce a label which can be used together with 'goto' 594 | */ 595 | struct ast_label : ast_expression 596 | { 597 | ast_label() = delete; 598 | ast_label(lex_ctx_t ctx, const std::string &name, bool undefined); 599 | ~ast_label(); 600 | 601 | bool codegen(ast_function *current, bool lvalue, ir_value **out) override; 602 | 603 | std::string m_name; 604 | ir_block *m_irblock; 605 | std::vector m_gotos; 606 | 607 | /* means it has not yet been defined */ 608 | bool m_undefined; 609 | 610 | private: 611 | void registerGoto(ast_goto*); 612 | friend struct ast_goto; 613 | }; 614 | 615 | /* GOTO nodes 616 | * 617 | * Go to a label, the label node is filled in at a later point! 618 | */ 619 | struct ast_goto : ast_expression 620 | { 621 | ast_goto() = delete; 622 | ast_goto(lex_ctx_t ctx, const std::string &name); 623 | ~ast_goto(); 624 | 625 | bool codegen(ast_function *current, bool lvalue, ir_value **out) override; 626 | 627 | void setLabel(ast_label*); 628 | 629 | std::string m_name; 630 | ast_label *m_target; 631 | ir_block *m_irblock_from; 632 | }; 633 | 634 | /* STATE node 635 | * 636 | * For frame/think state updates: void foo() [framenum, nextthink] {} 637 | */ 638 | struct ast_state : ast_expression 639 | { 640 | ast_state() = delete; 641 | ast_state(lex_ctx_t ctx, ast_expression *frame, ast_expression *think); 642 | ~ast_state(); 643 | 644 | bool codegen(ast_function *current, bool lvalue, ir_value **out) override; 645 | 646 | ast_expression *m_framenum; 647 | ast_expression *m_nextthink; 648 | }; 649 | 650 | /* CALL node 651 | * 652 | * Contains an ast_expression as target, rather than an ast_function/value. 653 | * Since it's how QC works, every ast_function has an ast_value 654 | * associated anyway - in other words, the VM contains function 655 | * pointers for every function anyway. Thus, this node will call 656 | * expression. 657 | * Additionally it contains a list of ast_expressions as parameters. 658 | * Since calls can return values, an ast_call is also an ast_expression. 659 | */ 660 | struct ast_call : ast_expression 661 | { 662 | ast_call() = delete; 663 | static ast_call *make(lex_ctx_t, ast_expression*); 664 | ~ast_call(); 665 | 666 | bool codegen(ast_function *current, bool lvalue, ir_value **out) override; 667 | 668 | bool checkTypes(ast_expression *this_func_va_type) const; 669 | 670 | ast_expression *m_func; 671 | std::vector m_params; 672 | ast_expression *m_va_count; 673 | 674 | private: 675 | ast_call(lex_ctx_t ctx, ast_expression *funcexpr); 676 | bool checkVararg(ast_expression *va_type, ast_expression *exp_type) const; 677 | }; 678 | 679 | /* Blocks 680 | * 681 | */ 682 | struct ast_block : ast_expression 683 | { 684 | ast_block() = delete; 685 | ast_block(lex_ctx_t ctx); 686 | ~ast_block(); 687 | 688 | bool codegen(ast_function *current, bool lvalue, ir_value **out) override; 689 | 690 | std::vector m_locals; 691 | std::vector m_exprs; 692 | std::vector m_collect; 693 | 694 | void setType(const ast_expression &from); 695 | bool GMQCC_WARN addExpr(ast_expression*); 696 | void collect(ast_expression*); 697 | }; 698 | 699 | /* Function 700 | * 701 | * Contains a list of blocks... at least in theory. 702 | * Usually there's just the main block, other blocks are inside that. 703 | * 704 | * Technically, functions don't need to be an AST node, since we have 705 | * neither functions inside functions, nor lambdas, and function 706 | * pointers could just work with a name. However, this way could be 707 | * more flexible, and adds no real complexity. 708 | * 709 | * The destructor will NOT delete the underlying ast_value 710 | * 711 | */ 712 | struct ast_function : ast_node 713 | { 714 | ast_function() = delete; 715 | static ast_function *make(lex_ctx_t ctx, const std::string &name, ast_value *vtype); 716 | ~ast_function(); 717 | 718 | const char* makeLabel(const char *prefix); 719 | virtual bool generateFunction(ir_builder*); 720 | 721 | ast_value *m_function_type = nullptr; 722 | std::string m_name; 723 | 724 | int m_builtin = 0; 725 | 726 | /* list of used-up names for statics without the count suffix */ 727 | std::vector m_static_names; 728 | /* number of static variables, by convention this includes the 729 | * ones without the count-suffix - remember this when dealing 730 | * with savegames. uint instead of size_t as %zu in printf is 731 | * C99, so no windows support. */ 732 | unsigned int m_static_count = 0; 733 | 734 | ir_function *m_ir_func = nullptr; 735 | ir_block *m_curblock = nullptr; 736 | std::vector m_breakblocks; 737 | std::vector m_continueblocks; 738 | 739 | size_t m_labelcount = 0; 740 | /* in order for thread safety - for the optional 741 | * channel abesed multithreading... keeping a buffer 742 | * here to use in ast_function_label. 743 | */ 744 | std::vector> m_blocks; 745 | std::unique_ptr m_varargs; 746 | std::unique_ptr m_argc; 747 | ast_value *m_fixedparams = nullptr; // these use unref() 748 | ast_value *m_return_value = nullptr; 749 | 750 | private: 751 | ast_function(lex_ctx_t ctx, const std::string &name, ast_value *vtype); 752 | 753 | char m_labelbuf[64]; 754 | }; 755 | 756 | /* 757 | * If the condition creates a situation where this becomes -1 size it means there are 758 | * more AST_FLAGs than the type ast_flag_t is capable of holding. So either eliminate 759 | * the AST flag count or change the ast_flag_t typedef to a type large enough to accomodate 760 | * all the flags. 761 | */ 762 | typedef int static_assert_is_ast_flag_safe [((AST_FLAG_LAST) <= (ast_flag_t)(-1)) ? 1 : -1]; 763 | #endif 764 | -------------------------------------------------------------------------------- /code.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "gmqcc.h" 3 | 4 | /* 5 | * We could use the old method of casting to uintptr_t then to void* 6 | * or qcint_t; however, it's incredibly unsafe for two reasons. 7 | * 1) The compilers aliasing optimization can legally make it unstable 8 | * (it's undefined behaviour). 9 | * 10 | * 2) The cast itself depends on fresh storage (newly allocated in which 11 | * ever function is using the cast macros), the contents of which are 12 | * transferred in a way that the obligation to release storage is not 13 | * propagated. 14 | */ 15 | typedef union { 16 | void *enter; 17 | qcint_t leave; 18 | } code_hash_entry_t; 19 | 20 | /* Some sanity macros */ 21 | #define CODE_HASH_ENTER(ENTRY) ((ENTRY).enter) 22 | #define CODE_HASH_LEAVE(ENTRY) ((ENTRY).leave) 23 | 24 | void code_push_statement(code_t *code, prog_section_statement_t *stmt_in, lex_ctx_t ctx) 25 | { 26 | prog_section_statement_t stmt = *stmt_in; 27 | 28 | if (OPTS_FLAG(TYPELESS_STORES)) { 29 | switch (stmt.opcode) { 30 | case INSTR_LOAD_S: 31 | case INSTR_LOAD_ENT: 32 | case INSTR_LOAD_FLD: 33 | case INSTR_LOAD_FNC: 34 | stmt.opcode = INSTR_LOAD_F; 35 | break; 36 | case INSTR_STORE_S: 37 | case INSTR_STORE_ENT: 38 | case INSTR_STORE_FLD: 39 | case INSTR_STORE_FNC: 40 | stmt.opcode = INSTR_STORE_F; 41 | break; 42 | case INSTR_STOREP_S: 43 | case INSTR_STOREP_ENT: 44 | case INSTR_STOREP_FLD: 45 | case INSTR_STOREP_FNC: 46 | stmt.opcode = INSTR_STOREP_F; 47 | break; 48 | } 49 | } 50 | 51 | 52 | if (OPTS_FLAG(SORT_OPERANDS)) { 53 | uint16_t pair; 54 | 55 | switch (stmt.opcode) { 56 | case INSTR_MUL_F: 57 | case INSTR_MUL_V: 58 | case INSTR_ADD_F: 59 | case INSTR_EQ_F: 60 | case INSTR_EQ_S: 61 | case INSTR_EQ_E: 62 | case INSTR_EQ_FNC: 63 | case INSTR_NE_F: 64 | case INSTR_NE_V: 65 | case INSTR_NE_S: 66 | case INSTR_NE_E: 67 | case INSTR_NE_FNC: 68 | case INSTR_AND: 69 | case INSTR_OR: 70 | case INSTR_BITAND: 71 | case INSTR_BITOR: 72 | if (stmt.o1.u1 < stmt.o2.u1) { 73 | uint16_t a = stmt.o2.u1; 74 | stmt.o1.u1 = stmt.o2.u1; 75 | stmt.o2.u1 = a; 76 | } 77 | break; 78 | 79 | case INSTR_MUL_VF: pair = INSTR_MUL_FV; goto case_pair_gen; 80 | case INSTR_MUL_FV: pair = INSTR_MUL_VF; goto case_pair_gen; 81 | case INSTR_LT: pair = INSTR_GT; goto case_pair_gen; 82 | case INSTR_GT: pair = INSTR_LT; goto case_pair_gen; 83 | case INSTR_LE: pair = INSTR_GE; goto case_pair_gen; 84 | case INSTR_GE: pair = INSTR_LE; 85 | 86 | case_pair_gen: 87 | if (stmt.o1.u1 < stmt.o2.u1) { 88 | uint16_t x = stmt.o1.u1; 89 | stmt.o1.u1 = stmt.o2.u1; 90 | stmt.o2.u1 = x; 91 | stmt.opcode = pair; 92 | } 93 | break; 94 | } 95 | } 96 | 97 | code->statements.push_back(stmt); 98 | code->linenums.push_back(ctx.line); 99 | code->columnnums.push_back(ctx.column); 100 | } 101 | 102 | void code_pop_statement(code_t *code) 103 | { 104 | code->statements.pop_back(); 105 | code->linenums.pop_back(); 106 | code->columnnums.pop_back(); 107 | } 108 | 109 | void *code_t::operator new(std::size_t bytes) { 110 | return mem_a(bytes); 111 | } 112 | 113 | void code_t::operator delete(void *ptr) { 114 | mem_d(ptr); 115 | } 116 | 117 | code_t::code_t() 118 | { 119 | static lex_ctx_t empty_ctx = {0, 0, 0}; 120 | static prog_section_function_t empty_function = {0,0,0,0,0,0,0,{0,0,0,0,0,0,0,0}}; 121 | static prog_section_statement_t empty_statement = {0,{0},{0},{0}}; 122 | static prog_section_def_t empty_def = {0, 0, 0}; 123 | 124 | string_cache = util_htnew(OPTS_OPTIMIZATION(OPTIM_OVERLAP_STRINGS) ? 0x100 : 1024); 125 | 126 | // The way progs.dat is suppose to work is odd, there needs to be 127 | // some null (empty) statements, functions, and 28 globals 128 | globals.insert(globals.begin(), 28, 0); 129 | 130 | chars.push_back('\0'); 131 | functions.push_back(empty_function); 132 | 133 | code_push_statement(this, &empty_statement, empty_ctx); 134 | 135 | defs.push_back(empty_def); 136 | fields.push_back(empty_def); 137 | } 138 | 139 | code_t::~code_t() 140 | { 141 | util_htdel(string_cache); 142 | } 143 | 144 | void *code_util_str_htgeth(hash_table_t *ht, const char *key, size_t bin); 145 | 146 | uint32_t code_genstring(code_t *code, const char *str) { 147 | size_t hash; 148 | code_hash_entry_t existing; 149 | 150 | if (!str) 151 | return 0; 152 | 153 | if (!*str) { 154 | if (!code->string_cached_empty) { 155 | code->string_cached_empty = code->chars.size(); 156 | code->chars.push_back(0); 157 | } 158 | return code->string_cached_empty; 159 | } 160 | 161 | if (OPTS_OPTIMIZATION(OPTIM_OVERLAP_STRINGS)) { 162 | hash = ((unsigned char*)str)[strlen(str)-1]; 163 | CODE_HASH_ENTER(existing) = code_util_str_htgeth(code->string_cache, str, hash); 164 | } else { 165 | hash = util_hthash(code->string_cache, str); 166 | CODE_HASH_ENTER(existing) = util_htgeth(code->string_cache, str, hash); 167 | } 168 | 169 | if (CODE_HASH_ENTER(existing)) 170 | return CODE_HASH_LEAVE(existing); 171 | 172 | CODE_HASH_LEAVE(existing) = code->chars.size(); 173 | code->chars.insert(code->chars.end(), str, str + strlen(str) + 1); 174 | 175 | util_htseth(code->string_cache, str, hash, CODE_HASH_ENTER(existing)); 176 | return CODE_HASH_LEAVE(existing); 177 | } 178 | 179 | qcint_t code_alloc_field (code_t *code, size_t qcsize) 180 | { 181 | qcint_t pos = (qcint_t)code->entfields; 182 | code->entfields += qcsize; 183 | return pos; 184 | } 185 | 186 | static size_t code_size_generic(code_t *code, prog_header_t *code_header, bool lno) { 187 | size_t size = 0; 188 | if (lno) { 189 | size += 4; /* LNOF */ 190 | size += sizeof(uint32_t); /* version */ 191 | size += sizeof(code_header->defs.length); 192 | size += sizeof(code_header->globals.length); 193 | size += sizeof(code_header->fields.length); 194 | size += sizeof(code_header->statements.length); 195 | size += sizeof(code->linenums[0]) * code->linenums.size(); 196 | size += sizeof(code->columnnums[0]) * code->columnnums.size(); 197 | } else { 198 | size += sizeof(prog_header_t); 199 | size += sizeof(prog_section_statement_t) * code->statements.size(); 200 | size += sizeof(prog_section_def_t) * code->defs.size(); 201 | size += sizeof(prog_section_field_t) * code->fields.size(); 202 | size += sizeof(prog_section_function_t) * code->functions.size(); 203 | size += sizeof(int32_t) * code->globals.size(); 204 | size += 1 * code->chars.size(); 205 | } 206 | return size; 207 | } 208 | 209 | #define code_size_binary(C, H) code_size_generic((C), (H), false) 210 | #define code_size_debug(C, H) code_size_generic((C), (H), true) 211 | 212 | static void code_create_header(code_t *code, prog_header_t *code_header, const char *filename, const char *lnofile) { 213 | size_t i; 214 | 215 | code_header->statements.offset = sizeof(prog_header_t); 216 | code_header->statements.length = code->statements.size(); 217 | code_header->defs.offset = code_header->statements.offset + (sizeof(prog_section_statement_t) * code->statements.size()); 218 | code_header->defs.length = code->defs.size(); 219 | code_header->fields.offset = code_header->defs.offset + (sizeof(prog_section_def_t) * code->defs.size()); 220 | code_header->fields.length = code->fields.size(); 221 | code_header->functions.offset = code_header->fields.offset + (sizeof(prog_section_field_t) * code->fields.size()); 222 | code_header->functions.length = code->functions.size(); 223 | code_header->globals.offset = code_header->functions.offset + (sizeof(prog_section_function_t) * code->functions.size()); 224 | code_header->globals.length = code->globals.size(); 225 | code_header->strings.offset = code_header->globals.offset + (sizeof(int32_t) * code->globals.size()); 226 | code_header->strings.length = code->chars.size(); 227 | code_header->version = 6; 228 | code_header->skip = 0; 229 | 230 | if (OPTS_OPTION_BOOL(OPTION_FORCECRC)) 231 | code_header->crc16 = OPTS_OPTION_U16(OPTION_FORCED_CRC); 232 | else 233 | code_header->crc16 = code->crc; 234 | code_header->entfield = code->entfields; 235 | 236 | if (OPTS_FLAG(DARKPLACES_STRING_TABLE_BUG)) { 237 | /* >= + P */ 238 | code->chars.push_back('\0'); /* > */ 239 | code->chars.push_back('\0'); /* = */ 240 | code->chars.push_back('\0'); /* P */ 241 | } 242 | 243 | /* ensure all data is in LE format */ 244 | util_swap_header(*code_header); 245 | util_swap_statements(code->statements); 246 | util_swap_defs_fields(code->defs); 247 | util_swap_defs_fields(code->fields); 248 | util_swap_functions(code->functions); 249 | util_swap_globals(code->globals); 250 | 251 | if (!OPTS_OPTION_BOOL(OPTION_QUIET)) { 252 | if (lnofile) 253 | con_out("writing '%s' and '%s'...\n", filename, lnofile); 254 | else 255 | con_out("writing '%s'\n", filename); 256 | } 257 | 258 | if (!OPTS_OPTION_BOOL(OPTION_QUIET) && 259 | !OPTS_OPTION_BOOL(OPTION_PP_ONLY)) 260 | { 261 | char buffer[1024]; 262 | con_out("\nOptimizations:\n"); 263 | for (i = 0; i < COUNT_OPTIMIZATIONS; ++i) { 264 | if (opts_optimizationcount[i]) { 265 | util_optimizationtostr(opts_opt_list[i].name, buffer, sizeof(buffer)); 266 | con_out( 267 | " %s: %u\n", 268 | buffer, 269 | (unsigned int)opts_optimizationcount[i] 270 | ); 271 | } 272 | } 273 | } 274 | } 275 | 276 | static void code_stats(const char *filename, const char *lnofile, code_t *code, prog_header_t *code_header) { 277 | if (OPTS_OPTION_BOOL(OPTION_QUIET) || 278 | OPTS_OPTION_BOOL(OPTION_PP_ONLY)) 279 | return; 280 | 281 | con_out("\nFile statistics:\n"); 282 | con_out(" dat:\n"); 283 | con_out(" name: %s\n", filename); 284 | con_out(" size: %u (bytes)\n", code_size_binary(code, code_header)); 285 | con_out(" crc: 0x%04X\n", code->crc); 286 | 287 | if (lnofile) { 288 | con_out(" lno:\n"); 289 | con_out(" name: %s\n", lnofile); 290 | con_out(" size: %u (bytes)\n", code_size_debug(code, code_header)); 291 | } 292 | 293 | con_out("\n"); 294 | } 295 | 296 | bool code_write(code_t *code, const char *filename, const char *lnofile) { 297 | prog_header_t code_header; 298 | FILE *fp = nullptr; 299 | 300 | code_create_header(code, &code_header, filename, lnofile); 301 | 302 | if (lnofile) { 303 | uint32_t version = 1; 304 | 305 | fp = fopen(lnofile, "wb"); 306 | if (!fp) 307 | return false; 308 | 309 | util_endianswap(&version, 1, sizeof(version)); 310 | util_endianswap(&code->linenums[0], code->linenums.size(), sizeof(code->linenums[0])); 311 | util_endianswap(&code->columnnums[0], code->columnnums.size(), sizeof(code->columnnums[0])); 312 | 313 | if (fwrite("LNOF", 4, 1, fp) != 1 || 314 | fwrite(&version, sizeof(version), 1, fp) != 1 || 315 | fwrite(&code_header.defs.length, sizeof(code_header.defs.length), 1, fp) != 1 || 316 | fwrite(&code_header.globals.length, sizeof(code_header.globals.length), 1, fp) != 1 || 317 | fwrite(&code_header.fields.length, sizeof(code_header.fields.length), 1, fp) != 1 || 318 | fwrite(&code_header.statements.length, sizeof(code_header.statements.length), 1, fp) != 1 || 319 | fwrite(&code->linenums[0], sizeof(code->linenums[0]), code->linenums.size(), fp) != code->linenums.size() || 320 | fwrite(&code->columnnums[0], sizeof(code->columnnums[0]), code->columnnums.size(), fp) != code->columnnums.size()) 321 | { 322 | con_err("failed to write lno file\n"); 323 | } 324 | 325 | fclose(fp); 326 | fp = nullptr; 327 | } 328 | 329 | fp = fopen(filename, "wb"); 330 | if (!fp) 331 | return false; 332 | 333 | if (1 != fwrite(&code_header, sizeof(prog_header_t) , 1 , fp) || 334 | code->statements.size() != fwrite(&code->statements[0], sizeof(prog_section_statement_t), code->statements.size(), fp) || 335 | code->defs.size() != fwrite(&code->defs[0], sizeof(prog_section_def_t) , code->defs.size() , fp) || 336 | code->fields.size() != fwrite(&code->fields[0], sizeof(prog_section_field_t) , code->fields.size() , fp) || 337 | code->functions.size() != fwrite(&code->functions[0], sizeof(prog_section_function_t) , code->functions.size() , fp) || 338 | code->globals.size() != fwrite(&code->globals[0], sizeof(int32_t) , code->globals.size() , fp) || 339 | code->chars.size() != fwrite(&code->chars[0], 1 , code->chars.size() , fp)) 340 | { 341 | fclose(fp); 342 | return false; 343 | } 344 | 345 | fclose(fp); 346 | code_stats(filename, lnofile, code, &code_header); 347 | return true; 348 | } 349 | -------------------------------------------------------------------------------- /conout.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "gmqcc.h" 3 | 4 | #define GMQCC_IS_STDOUT(X) ((X) == stdout) 5 | #define GMQCC_IS_STDERR(X) ((X) == stderr) 6 | #define GMQCC_IS_DEFINE(X) (GMQCC_IS_STDERR(X) || GMQCC_IS_STDOUT(X)) 7 | 8 | struct con_t { 9 | FILE *handle_err; 10 | FILE *handle_out; 11 | int color_err; 12 | int color_out; 13 | }; 14 | 15 | static con_t console; 16 | 17 | /* 18 | * Enables color on output if supported. 19 | * NOTE: The support for checking colors is nullptr. On windows this will 20 | * always work, on *nix it depends if the term has colors. 21 | * 22 | * NOTE: This prevents colored output to piped stdout/err via isatty 23 | * checks. 24 | */ 25 | static void con_enablecolor(void) { 26 | console.color_err = util_isatty(console.handle_err); 27 | console.color_out = util_isatty(console.handle_out); 28 | } 29 | 30 | /* 31 | * Does a write to the handle with the format string and list of 32 | * arguments. This colorizes for windows as well via translate 33 | * step. 34 | */ 35 | static int con_write(FILE *handle, const char *fmt, va_list va) { 36 | return vfprintf(handle, fmt, va); 37 | } 38 | 39 | /********************************************************************** 40 | * EXPOSED INTERFACE BEGINS 41 | *********************************************************************/ 42 | 43 | void con_close() { 44 | if (!GMQCC_IS_DEFINE(console.handle_err)) 45 | fclose(console.handle_err); 46 | if (!GMQCC_IS_DEFINE(console.handle_out)) 47 | fclose(console.handle_out); 48 | } 49 | 50 | void con_color(int state) { 51 | if (state) 52 | con_enablecolor(); 53 | else { 54 | console.color_err = 0; 55 | console.color_out = 0; 56 | } 57 | } 58 | 59 | void con_init() { 60 | console.handle_err = stderr; 61 | console.handle_out = stdout; 62 | con_enablecolor(); 63 | } 64 | 65 | void con_reset() { 66 | con_close(); 67 | con_init(); 68 | } 69 | 70 | /* 71 | * Defaultizer because stdio.h shouldn't be used anywhere except here 72 | * and inside file.c To prevent mis-match of wrapper-interfaces. 73 | */ 74 | FILE *con_default_out() { 75 | return console.handle_out = stdout; 76 | } 77 | 78 | FILE *con_default_err() { 79 | return console.handle_err = stderr; 80 | } 81 | 82 | int con_verr(const char *fmt, va_list va) { 83 | return con_write(console.handle_err, fmt, va); 84 | } 85 | int con_vout(const char *fmt, va_list va) { 86 | return con_write(console.handle_out, fmt, va); 87 | } 88 | 89 | /* 90 | * Standard stdout/stderr printf functions used generally where they need 91 | * to be used. 92 | */ 93 | int con_err(const char *fmt, ...) { 94 | va_list va; 95 | int ln = 0; 96 | va_start(va, fmt); 97 | con_verr(fmt, va); 98 | va_end(va); 99 | return ln; 100 | } 101 | int con_out(const char *fmt, ...) { 102 | va_list va; 103 | int ln = 0; 104 | va_start(va, fmt); 105 | con_vout(fmt, va); 106 | va_end (va); 107 | return ln; 108 | } 109 | 110 | /* 111 | * Utility console message writes for lexer contexts. These will allow 112 | * for reporting of file:line based on lexer context, These are used 113 | * heavily in the parser/ir/ast. 114 | */ 115 | static void con_vprintmsg_c(int level, const char *name, size_t line, size_t column, const char *msgtype, const char *msg, va_list ap, const char *condname) { 116 | /* color selection table */ 117 | static int sel[] = { 118 | CON_WHITE, 119 | CON_CYAN, 120 | CON_RED 121 | }; 122 | 123 | int err = !!(level == LVL_ERROR); 124 | int color = (err) ? console.color_err : console.color_out; 125 | int (*print) (const char *, ...) = (err) ? &con_err : &con_out; 126 | int (*vprint)(const char *, va_list) = (err) ? &con_verr : &con_vout; 127 | 128 | if (color) 129 | print("\033[0;%dm%s:%d:%d: \033[0;%dm%s: \033[0m", CON_CYAN, name, (int)line, (int)column, sel[level], msgtype); 130 | else 131 | print("%s:%d:%d: %s: ", name, (int)line, (int)column, msgtype); 132 | 133 | vprint(msg, ap); 134 | if (condname) 135 | print(" [%s]\n", condname); 136 | else 137 | print("\n"); 138 | } 139 | 140 | void con_vprintmsg(int level, const char *name, size_t line, size_t column, const char *msgtype, const char *msg, va_list ap) { 141 | con_vprintmsg_c(level, name, line, column, msgtype, msg, ap, nullptr); 142 | } 143 | 144 | void con_printmsg(int level, const char *name, size_t line, size_t column, const char *msgtype, const char *msg, ...) { 145 | va_list va; 146 | va_start(va, msg); 147 | con_vprintmsg(level, name, line, column, msgtype, msg, va); 148 | va_end (va); 149 | } 150 | 151 | void con_cvprintmsg(lex_ctx_t ctx, int lvl, const char *msgtype, const char *msg, va_list ap) { 152 | con_vprintmsg(lvl, ctx.file, ctx.line, ctx.column, msgtype, msg, ap); 153 | } 154 | 155 | void con_cprintmsg(lex_ctx_t ctx, int lvl, const char *msgtype, const char *msg, ...) { 156 | va_list va; 157 | va_start(va, msg); 158 | con_cvprintmsg(ctx, lvl, msgtype, msg, va); 159 | va_end (va); 160 | } 161 | 162 | /* General error interface: TODO seperate as part of the compiler front-end */ 163 | size_t compile_errors = 0; 164 | size_t compile_warnings = 0; 165 | size_t compile_Werrors = 0; 166 | static lex_ctx_t first_werror; 167 | 168 | void compile_show_werrors() 169 | { 170 | con_cprintmsg(first_werror, LVL_ERROR, "first warning", "was here"); 171 | } 172 | 173 | void vcompile_error(lex_ctx_t ctx, const char *msg, va_list ap) 174 | { 175 | ++compile_errors; 176 | con_cvprintmsg(ctx, LVL_ERROR, "error", msg, ap); 177 | } 178 | 179 | void compile_error_(lex_ctx_t ctx, const char *msg, ...) 180 | { 181 | va_list ap; 182 | va_start(ap, msg); 183 | vcompile_error(ctx, msg, ap); 184 | va_end(ap); 185 | } 186 | 187 | bool GMQCC_WARN vcompile_warning(lex_ctx_t ctx, int warntype, const char *fmt, va_list ap) 188 | { 189 | const char *msgtype = "warning"; 190 | int lvl = LVL_WARNING; 191 | char warn_name[1024]; 192 | 193 | if (!OPTS_WARN(warntype)) 194 | return false; 195 | 196 | warn_name[0] = '-'; 197 | warn_name[1] = 'W'; 198 | (void)util_strtononcmd(opts_warn_list[warntype].name, warn_name+2, sizeof(warn_name)-2); 199 | 200 | ++compile_warnings; 201 | if (OPTS_WERROR(warntype)) { 202 | if (!compile_Werrors) 203 | first_werror = ctx; 204 | ++compile_Werrors; 205 | msgtype = "Werror"; 206 | if (OPTS_FLAG(BAIL_ON_WERROR)) { 207 | msgtype = "error"; 208 | ++compile_errors; 209 | } 210 | lvl = LVL_ERROR; 211 | } 212 | 213 | con_vprintmsg_c(lvl, ctx.file, ctx.line, ctx.column, msgtype, fmt, ap, warn_name); 214 | 215 | return OPTS_WERROR(warntype) && OPTS_FLAG(BAIL_ON_WERROR); 216 | } 217 | 218 | bool GMQCC_WARN compile_warning_(lex_ctx_t ctx, int warntype, const char *fmt, ...) 219 | { 220 | bool r; 221 | va_list ap; 222 | va_start(ap, fmt); 223 | r = vcompile_warning(ctx, warntype, fmt, ap); 224 | va_end(ap); 225 | return r; 226 | } 227 | -------------------------------------------------------------------------------- /doc/qcvm.1: -------------------------------------------------------------------------------- 1 | .\" qcvm mdoc manpage 2 | .Dd January 31, 2013 3 | .Dt QCVM 1 PRM 4 | .Os 5 | .Sh NAME 6 | .Nm qcvm 7 | .Nd A standalone QuakeC VM binary executor 8 | .Sh SYNOPSIS 9 | .Nm qcvm 10 | .Op Cm options 11 | .Op Cm parameters 12 | .Ar program-file 13 | .Sh DESCRIPTION 14 | .Nm qcvm 15 | is an executor for QuakeC VM binary files created using a QC 16 | compiler such as gmqcc(1) or fteqcc. It provides a small set of 17 | builtin functions, and by default executes the 18 | .Fn main 19 | function if there is one. Some options useful for debugging are 20 | available as well. 21 | .Sh OPTIONS 22 | There are 2 types of options. Options for the executor, and parameter 23 | options used to add parameters which are passed to the main function 24 | on execution. 25 | .Bl -tag -width Ds 26 | .It Fl h , Fl -help 27 | Show a usage message and exit. 28 | .It Fl trace 29 | Trace the execution. Each instruction will be printed to stdout before 30 | executing it. 31 | .It Fl profile 32 | Perform some profiling. This is currently not really implemented, the 33 | option is available nonetheless. 34 | .It Fl info 35 | Print information from the program's header instead of executing. 36 | .It Fl disasm 37 | Disassemble the program by function instead of executing. 38 | .It Fl disasm-func Ar function 39 | Search for and disassemble the given function. 40 | .It Fl printdefs 41 | List all entries from the program's defs-section. Effectively 42 | listing all the global variables of the program. 43 | This option disables execution. 44 | .It Fl printfields 45 | List all entries from the program's fields-section. Listing all 46 | entity-fields declared in the program. 47 | This option disables execution. 48 | .It Fl printfuns 49 | List functions and some information about their parameters. 50 | This option disables execution. With a verbosity level of 1, builtin 51 | numbers are printed. With a verbosity of 2, the function's sizes are 52 | printed as well. This takes a little longer since the size is found by 53 | searching for a 54 | .Ql DONE 55 | instruction in the code. 56 | .It Fl v 57 | Increase verbosity level, can be used multiple times. 58 | .It Fl vector Ar 'x y z' 59 | Append a vector parameter to be passed to 60 | .Fn main Ns . 61 | .It Fl float Ar number 62 | Append a float parameter to be passed to 63 | .Fn main Ns . 64 | .It Fl string Ar 'text' 65 | Append a string parameter to be passed to 66 | .Fn main Ns . 67 | .El 68 | .Sh BUILTINS 69 | The following builtin functions are available: 70 | .Bl -ohang 71 | .It Li 1) void print(string...) = #1; 72 | .Bd -unfilled -offset indent -compact 73 | Print the passed strings to stdout. At most 8 strings are allowed. 74 | .Ed 75 | .It Li 2) string ftos(float) = #2; 76 | .D1 Convert a float to a string. 77 | .It Li 3) entity spawn() = #3; 78 | .D1 Spawn an entity. 79 | .It Li 4) void remove(entity) = #4; 80 | .D1 Remove an entity. 81 | .It Li 5) string vtos(vector) = #5; 82 | .D1 Convert a vector to a string. 83 | .It Li 6) void error(string...) = #6; 84 | .D1 Print strings to stdout and then exit with an error (limited to 8 arguments) 85 | .It Li 7) float vlen(vector) = #7; 86 | .D1 Get the length of a vector. 87 | .It Li 8) string etos(entity) = #8; 88 | .D1 Get the entity ID as string. 89 | .It Li 9) float stof(string) = #9; 90 | .D1 Convert a string to a float. 91 | .It Li 10) string strcat(string, string) = #10; 92 | .D1 Concatenate two strings, returning a tempstring. 93 | .It Li 11) float strcmp(string, string) = #11; 94 | .Li 12) float strncmp(string, string, float) = #11; 95 | .D1 Compare two strings. Returns the same as the corresponding C functions. 96 | .It Li 12) vector normalize(vector) = #12; 97 | .D1 Normalize a vector so its length is 1. 98 | .It Li 13) float sqrt(float) = #13; 99 | .D1 Get a value's square root. 100 | .El 101 | .Sh SEE ALSO 102 | .Xr gmqcc 1 103 | .Sh AUTHOR 104 | See . 105 | .Sh BUGS 106 | Please report bugs on , 107 | or see on how to contact us. 108 | -------------------------------------------------------------------------------- /fold.h: -------------------------------------------------------------------------------- 1 | #ifndef GMQCC_FOLD_HDR 2 | #define GMQCC_FOLD_HDR 3 | #include "lexer.h" 4 | #include "gmqcc.h" 5 | 6 | struct ir_builder; 7 | struct ir_value; 8 | 9 | struct ast_function; 10 | struct ast_ifthen; 11 | struct ast_ternary; 12 | struct ast_expression; 13 | struct ast_value; 14 | 15 | struct parser_t; 16 | 17 | struct fold { 18 | fold(); 19 | fold(parser_t *parser); 20 | ~fold(); 21 | 22 | // Bitmask describing which branches of a conditional to take after folding. 23 | // Zero indicates all the branches can be removed. 24 | // ON_TRUE means ON_FALSE can be removed. 25 | // ON_FALSE means ON_TRUE can be removed. 26 | // ON_TRUE | ON_FALSE means nothing can be removed. 27 | enum { 28 | ON_TRUE = 1 << 0, 29 | ON_FALSE = 1 << 1, 30 | }; 31 | 32 | bool generate(ir_builder *ir); 33 | ast_expression *op(const oper_info *info, ast_expression **opexprs); 34 | ast_expression *intrinsic(const char *intrinsic, size_t n_args, ast_expression **args); 35 | 36 | static uint32_t cond_ternary(ast_value *condval, ast_ternary *branch); 37 | static uint32_t cond_ifthen(ast_value *condval, ast_ifthen *branch); 38 | 39 | static ast_expression *superfluous(ast_expression *left, ast_expression *right, int op); 40 | static ast_expression *binary(lex_ctx_t ctx, int op, ast_expression *left, ast_expression *right); 41 | 42 | ast_expression *constgen_float(qcfloat_t value, bool inexact); 43 | ast_expression *constgen_vector(vec3_t value); 44 | ast_expression *constgen_string(const char *str, bool translate); 45 | ast_expression *constgen_string(const std::string &str, bool translate); 46 | 47 | ast_value *imm_float(size_t index) const { return m_imm_float[index]; } 48 | ast_value *imm_vector(size_t index) const { return m_imm_vector[index]; } 49 | 50 | protected: 51 | static qcfloat_t immvalue_float(ast_value *value); 52 | static vec3_t immvalue_vector(ast_value *value); 53 | static const char *immvalue_string(ast_value *value); 54 | 55 | lex_ctx_t ctx(); 56 | 57 | bool immediate_true(ast_value *v); 58 | 59 | bool check_except_float_impl(void (*callback)(void), ast_value *a, ast_value *b); 60 | bool check_inexact_float(ast_value *a, ast_value *b); 61 | 62 | ast_expression *op_mul_vec(vec3_t vec, ast_value *sel, const char *set); 63 | ast_expression *op_neg(ast_value *a); 64 | ast_expression *op_not(ast_value *a); 65 | ast_expression *op_add(ast_value *a, ast_value *b); 66 | ast_expression *op_sub(ast_value *a, ast_value *b); 67 | ast_expression *op_mul(ast_value *a, ast_value *b); 68 | ast_expression *op_div(ast_value *a, ast_value *b); 69 | ast_expression *op_mod(ast_value *a, ast_value *b); 70 | ast_expression *op_bor(ast_value *a, ast_value *b); 71 | ast_expression *op_band(ast_value *a, ast_value *b); 72 | ast_expression *op_xor(ast_value *a, ast_value *b); 73 | ast_expression *op_lshift(ast_value *a, ast_value *b); 74 | ast_expression *op_rshift(ast_value *a, ast_value *b); 75 | ast_expression *op_andor(ast_value *a, ast_value *b, float expr); 76 | ast_expression *op_tern(ast_value *a, ast_value *b, ast_value *c); 77 | ast_expression *op_exp(ast_value *a, ast_value *b); 78 | ast_expression *op_lteqgt(ast_value *a, ast_value *b); 79 | ast_expression *op_ltgt(ast_value *a, ast_value *b, bool lt); 80 | ast_expression *op_cmp(ast_value *a, ast_value *b, bool ne); 81 | ast_expression *op_bnot(ast_value *a); 82 | ast_expression *op_cross(ast_value *a, ast_value *b); 83 | ast_expression *op_length(ast_value *a); 84 | 85 | ast_expression *intrinsic_isfinite(ast_value *a); 86 | ast_expression *intrinsic_isinf(ast_value *a); 87 | ast_expression *intrinsic_isnan(ast_value *a); 88 | ast_expression *intrinsic_isnormal(ast_value *a); 89 | ast_expression *intrinsic_signbit(ast_value *a); 90 | ast_expression *intrinsic_acosh(ast_value *a); 91 | ast_expression *intrinsic_asinh(ast_value *a); 92 | ast_expression *intrinsic_atanh(ast_value *a); 93 | ast_expression *intrinsic_exp(ast_value *a); 94 | ast_expression *intrinsic_exp2(ast_value *a); 95 | ast_expression *intrinsic_expm1(ast_value *a); 96 | ast_expression *intrinsic_pow(ast_value *lhs, ast_value *rhs); 97 | ast_expression *intrinsic_mod(ast_value *lhs, ast_value *rhs); 98 | ast_expression *intrinsic_fabs(ast_value *a); 99 | 100 | ast_expression* intrinsic_nan(void); 101 | ast_expression* intrinsic_epsilon(void); 102 | ast_expression* intrinsic_inf(void); 103 | 104 | static qcfloat_t immvalue_float(ir_value *value); 105 | static vec3_t immvalue_vector(ir_value *value); 106 | 107 | static uint32_t cond(ast_value *condval, ast_ifthen *branch); 108 | 109 | private: 110 | friend struct intrin; 111 | 112 | std::vector m_imm_float; 113 | std::vector m_imm_vector; 114 | std::vector m_imm_string; 115 | hash_table_t *m_imm_string_untranslate; /* map */ 116 | hash_table_t *m_imm_string_dotranslate; /* map */ 117 | parser_t *m_parser; 118 | bool m_initialized; 119 | }; 120 | 121 | #endif 122 | -------------------------------------------------------------------------------- /intrin.h: -------------------------------------------------------------------------------- 1 | #ifndef GMQCC_INTRIN_HDR 2 | #define GMQCC_INTRIN_HDR 3 | #include "gmqcc.h" 4 | 5 | struct fold; 6 | struct parser_t; 7 | 8 | struct ast_function; 9 | struct ast_expression; 10 | struct ast_value; 11 | 12 | struct intrin; 13 | 14 | struct intrin_func_t { 15 | ast_expression *(intrin::*function)(); 16 | const char *name; 17 | const char *alias; 18 | size_t args; 19 | }; 20 | 21 | struct intrin { 22 | intrin() = default; 23 | intrin(parser_t *parser); 24 | 25 | ast_expression *debug_typestring(); 26 | ast_expression *do_fold(ast_value *val, ast_expression **exprs); 27 | ast_expression *func_try(size_t offset, const char *compare); 28 | ast_expression *func_self(const char *name, const char *from); 29 | ast_expression *func(const char *name); 30 | 31 | protected: 32 | lex_ctx_t ctx() const; 33 | ast_function *value(ast_value **out, const char *name, qc_type vtype); 34 | void reg(ast_value *const value, ast_function *const func); 35 | 36 | ast_expression *nullfunc(); 37 | ast_expression *isfinite_(); 38 | ast_expression *isinf_(); 39 | ast_expression *isnan_(); 40 | ast_expression *isnormal_(); 41 | ast_expression *signbit_(); 42 | ast_expression *acosh_(); 43 | ast_expression *asinh_(); 44 | ast_expression *atanh_(); 45 | ast_expression *exp_(); 46 | ast_expression *exp2_(); 47 | ast_expression *expm1_(); 48 | ast_expression *pow_(); 49 | ast_expression *mod_(); 50 | ast_expression *fabs_(); 51 | ast_expression *epsilon_(); 52 | ast_expression *nan_(); 53 | ast_expression *inf_(); 54 | ast_expression *ln_(); 55 | ast_expression *log_variant(const char *name, float base); 56 | ast_expression *log_(); 57 | ast_expression *log10_(); 58 | ast_expression *log2_(); 59 | ast_expression *logb_(); 60 | ast_expression *shift_variant(const char *name, size_t instr); 61 | ast_expression *lshift(); 62 | ast_expression *rshift(); 63 | 64 | void error(const char *fmt, ...); 65 | 66 | private: 67 | parser_t *m_parser; 68 | fold *m_fold; 69 | std::vector m_intrinsics; 70 | std::vector m_generated; 71 | }; 72 | 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /ir.h: -------------------------------------------------------------------------------- 1 | #ifndef GMQCC_IR_HDR 2 | #define GMQCC_IR_HDR 3 | #include "gmqcc.h" 4 | 5 | /* 6 | * Type large enough to hold all the possible IR flags. This should be 7 | * changed if the static assertion at the end of this file fails. 8 | */ 9 | typedef uint8_t ir_flag_t; 10 | 11 | struct ir_value; 12 | struct ir_instr; 13 | struct ir_block; 14 | struct ir_function; 15 | struct ir_builder; 16 | 17 | struct ir_life_entry_t { 18 | /* both inclusive */ 19 | size_t start; 20 | size_t end; 21 | }; 22 | 23 | enum { 24 | IR_FLAG_HAS_ARRAYS = 1 << 0, 25 | IR_FLAG_HAS_UNINITIALIZED = 1 << 1, 26 | IR_FLAG_HAS_GOTO = 1 << 2, 27 | IR_FLAG_INCLUDE_DEF = 1 << 3, 28 | IR_FLAG_ERASABLE = 1 << 4, 29 | IR_FLAG_BLOCK_COVERAGE = 1 << 5, 30 | IR_FLAG_NOREF = 1 << 6, 31 | IR_FLAG_SPLIT_VECTOR = 1 << 7, 32 | 33 | IR_FLAG_LAST, 34 | IR_FLAG_MASK_NO_OVERLAP = (IR_FLAG_HAS_ARRAYS | IR_FLAG_HAS_UNINITIALIZED), 35 | IR_FLAG_MASK_NO_LOCAL_TEMPS = (IR_FLAG_HAS_ARRAYS | IR_FLAG_HAS_UNINITIALIZED) 36 | }; 37 | 38 | struct ir_value { 39 | ir_value(std::string&& name, store_type storetype, qc_type vtype); 40 | ir_value(ir_function *owner, std::string&& name, store_type storetype, qc_type vtype); 41 | ~ir_value(); 42 | 43 | ir_value *vectorMember(unsigned int member); 44 | 45 | bool GMQCC_WARN setFloat(float); 46 | bool GMQCC_WARN setFunc(int); 47 | bool GMQCC_WARN setString(const char*); 48 | bool GMQCC_WARN setVector(vec3_t); 49 | bool GMQCC_WARN setField(ir_value*); 50 | #if 0 51 | bool GMQCC_WARN setInt(int); 52 | #endif 53 | 54 | bool lives(size_t at); 55 | void dumpLife(int (*oprintf)(const char*, ...)) const; 56 | 57 | void setCodeAddress(int32_t gaddr); 58 | int32_t codeAddress() const; 59 | 60 | bool insertLife(size_t idx, ir_life_entry_t); 61 | bool setAlive(size_t position); 62 | bool mergeLife(const ir_value *other); 63 | 64 | std::string m_name; 65 | 66 | qc_type m_vtype; 67 | store_type m_store; 68 | lex_ctx_t m_context; 69 | qc_type m_fieldtype; // even the IR knows the subtype of a field 70 | qc_type m_outtype; // and the output type of a function 71 | int m_cvq; // 'const' vs 'var' qualifier 72 | ir_flag_t m_flags; 73 | 74 | std::vector m_reads; 75 | std::vector m_writes; 76 | 77 | // constant values 78 | bool m_hasvalue; 79 | union { 80 | qcfloat_t vfloat; 81 | int vint; 82 | vec3_t vvec; 83 | int32_t ivec[3]; 84 | char *vstring; 85 | ir_value *vpointer; 86 | ir_function *vfunc; 87 | } m_constval; 88 | 89 | struct { 90 | int32_t globaladdr; 91 | int32_t name; 92 | int32_t local; // filled by the local-allocator 93 | int32_t addroffset; // added for members 94 | int32_t fieldaddr; // to generate field-addresses early 95 | } m_code; 96 | 97 | // for accessing vectors 98 | ir_value *m_members[3]; 99 | ir_value *m_memberof; 100 | 101 | bool m_unique_life; // arrays will never overlap with temps 102 | bool m_locked; // temps living during a CALL must be locked 103 | bool m_callparam; 104 | 105 | std::vector m_life; // For the temp allocator 106 | 107 | size_t size() const; 108 | 109 | void dump(int (*oprintf)(const char*, ...)) const; 110 | }; 111 | 112 | /* PHI data */ 113 | struct ir_phi_entry_t { 114 | ir_value *value; 115 | ir_block *from; 116 | }; 117 | 118 | /* instruction */ 119 | struct ir_instr { 120 | ir_instr(lex_ctx_t, ir_block *owner, int opcode); 121 | ~ir_instr(); 122 | 123 | int m_opcode; 124 | lex_ctx_t m_context; 125 | ir_value *(_m_ops[3]) = { nullptr, nullptr, nullptr }; 126 | ir_block *(m_bops[2]) = { nullptr, nullptr }; 127 | 128 | std::vector m_phi; 129 | std::vector m_params; 130 | 131 | // For the temp-allocation 132 | size_t m_eid = 0; 133 | 134 | // For IFs 135 | bool m_likely = true; 136 | 137 | ir_block *m_owner; 138 | }; 139 | 140 | /* block */ 141 | struct ir_block { 142 | ir_block(ir_function *owner, const std::string& name); 143 | ~ir_block(); 144 | 145 | ir_function *m_owner; 146 | std::string m_label; 147 | 148 | lex_ctx_t m_context; 149 | bool m_final = false; /* once a jump is added we're done */ 150 | 151 | std::vector m_instr; 152 | std::vector m_entries; 153 | std::vector m_exits; 154 | std::vector m_living; 155 | 156 | /* For the temp-allocation */ 157 | size_t m_entry_id = 0; 158 | size_t m_eid = 0; 159 | bool m_is_return = false; 160 | 161 | bool m_generated = false; 162 | size_t m_code_start = 0; 163 | }; 164 | 165 | ir_value* ir_block_create_binop(ir_block*, lex_ctx_t, const char *label, int op, ir_value *left, ir_value *right); 166 | ir_value* ir_block_create_unary(ir_block*, lex_ctx_t, const char *label, int op, ir_value *operand); 167 | bool GMQCC_WARN ir_block_create_store_op(ir_block*, lex_ctx_t, int op, ir_value *target, ir_value *what); 168 | bool GMQCC_WARN ir_block_create_storep(ir_block*, lex_ctx_t, ir_value *target, ir_value *what); 169 | ir_value* ir_block_create_load_from_ent(ir_block*, lex_ctx_t, const char *label, ir_value *ent, ir_value *field, qc_type outype); 170 | ir_value* ir_block_create_fieldaddress(ir_block*, lex_ctx_t, const char *label, ir_value *entity, ir_value *field); 171 | bool GMQCC_WARN ir_block_create_state_op(ir_block*, lex_ctx_t, ir_value *frame, ir_value *think); 172 | 173 | /* This is to create an instruction of the form 174 | * %label := opcode a, b 175 | */ 176 | ir_instr* ir_block_create_phi(ir_block*, lex_ctx_t, const char *label, qc_type vtype); 177 | ir_value* ir_phi_value(ir_instr*); 178 | void ir_phi_add(ir_instr*, ir_block *b, ir_value *v); 179 | ir_instr* ir_block_create_call(ir_block*, lex_ctx_t, const char *label, ir_value *func, bool noreturn); 180 | ir_value* ir_call_value(ir_instr*); 181 | void ir_call_param(ir_instr*, ir_value*); 182 | 183 | bool GMQCC_WARN ir_block_create_return(ir_block*, lex_ctx_t, ir_value *opt_value); 184 | 185 | bool GMQCC_WARN ir_block_create_if(ir_block*, lex_ctx_t, ir_value *cond, 186 | ir_block *ontrue, ir_block *onfalse); 187 | /* 188 | * A 'goto' is an actual 'goto' coded in QC, whereas 189 | * a 'jump' is a virtual construct which simply names the 190 | * next block to go to. 191 | * A goto usually becomes an OP_GOTO in the resulting code, 192 | * whereas a 'jump' usually doesn't add any actual instruction. 193 | */ 194 | bool GMQCC_WARN ir_block_create_jump(ir_block*, lex_ctx_t, ir_block *to); 195 | bool GMQCC_WARN ir_block_create_goto(ir_block*, lex_ctx_t, ir_block *to); 196 | 197 | /* function */ 198 | struct ir_function { 199 | ir_function(ir_builder *owner, qc_type returntype); 200 | ~ir_function(); 201 | 202 | ir_builder *m_owner; 203 | 204 | std::string m_name; 205 | qc_type m_outtype; 206 | std::vector m_params; 207 | ir_flag_t m_flags = 0; 208 | int m_builtin = 0; 209 | 210 | std::vector> m_blocks; 211 | 212 | /* 213 | * values generated from operations 214 | * which might get optimized away, so anything 215 | * in there needs to be deleted in the dtor. 216 | */ 217 | std::vector> m_values; 218 | std::vector> m_locals; /* locally defined variables */ 219 | ir_value *m_value = nullptr; 220 | 221 | size_t m_allocated_locals = 0; 222 | size_t m_globaltemps = 0; 223 | 224 | ir_block* m_first = nullptr; 225 | ir_block* m_last = nullptr; 226 | 227 | lex_ctx_t m_context; 228 | 229 | /* 230 | * for prototypes - first we generate all the 231 | * globals, and we remember teh function-defs 232 | * so we can later fill in the entry pos 233 | * 234 | * remember the ID: 235 | */ 236 | qcint_t m_code_function_def = -1; 237 | 238 | /* for temp allocation */ 239 | size_t m_run_id = 0; 240 | 241 | /* vararg support: */ 242 | size_t m_max_varargs = 0; 243 | }; 244 | 245 | 246 | ir_value* ir_function_create_local(ir_function *self, const std::string& name, qc_type vtype, bool param); 247 | bool GMQCC_WARN ir_function_finalize(ir_function*); 248 | ir_block* ir_function_create_block(lex_ctx_t ctx, ir_function*, const char *label); 249 | 250 | /* builder */ 251 | #define IR_HT_SIZE 1024 252 | #define IR_MAX_VINSTR_TEMPS 2 253 | 254 | struct ir_builder { 255 | ir_builder(const std::string& modulename); 256 | ~ir_builder(); 257 | 258 | ir_function *createFunction(const std::string &name, qc_type outtype); 259 | ir_value *createGlobal(const std::string &name, qc_type vtype); 260 | ir_value *createField(const std::string &name, qc_type vtype); 261 | ir_value *get_va_count(); 262 | bool generate(const char *filename); 263 | void dump(int (*oprintf)(const char*, ...)) const; 264 | 265 | ir_value *generateExtparamProto(); 266 | void generateExtparam(); 267 | 268 | ir_value *literalFloat(float value, bool add_to_list); 269 | 270 | std::string m_name; 271 | std::vector> m_functions; 272 | std::vector> m_globals; 273 | std::vector> m_fields; 274 | // for reusing them in vector-splits, TODO: sort this or use a radix-tree 275 | std::vector m_const_floats; 276 | std::map m_reusable_const_floats; 277 | 278 | ht m_htfunctions; 279 | ht m_htglobals; 280 | ht m_htfields; 281 | 282 | // extparams' ir_values reference the ones from extparam_protos 283 | std::vector> m_extparam_protos; 284 | std::vector m_extparams; 285 | 286 | // the highest func->allocated_locals 287 | size_t m_max_locals = 0; 288 | size_t m_max_globaltemps = 0; 289 | uint32_t m_first_common_local = 0; 290 | uint32_t m_first_common_globaltemp = 0; 291 | 292 | std::vector m_filenames; 293 | std::vector m_filestrings; 294 | 295 | // we cache the #IMMEDIATE string here 296 | qcint_t m_str_immediate = 0; 297 | 298 | // there should just be this one nil 299 | ir_value *m_nil; 300 | ir_value *m_reserved_va_count = nullptr; 301 | ir_value *m_coverage_func = nullptr; 302 | 303 | /* some virtual instructions require temps, and their code is isolated 304 | * so that we don't need to keep track of their liveness. 305 | */ 306 | ir_value *m_vinstr_temp[IR_MAX_VINSTR_TEMPS]; 307 | 308 | /* code generator */ 309 | std::unique_ptr m_code; 310 | 311 | private: 312 | qcint_t filestring(const char *filename); 313 | bool generateGlobal(ir_value*, bool is_local); 314 | bool generateGlobalFunction(ir_value*); 315 | bool generateGlobalFunctionCode(ir_value*); 316 | bool generateFunctionLocals(ir_value*); 317 | bool generateMergedFloat(ir_value*, bool register_only); 318 | }; 319 | 320 | /* 321 | * This code assumes 32 bit floats while generating binary 322 | * Blub: don't use extern here, it's annoying and shows up in nm 323 | * for some reason :P 324 | */ 325 | typedef int static_assert_is_32bit_float [(sizeof(int32_t) == 4) ? 1 : -1]; 326 | typedef int static_assert_is_32bit_integer[(sizeof(qcfloat_t) == 4) ? 1 : -1]; 327 | 328 | /* 329 | * If the condition creates a situation where this becomes -1 size it means there are 330 | * more IR_FLAGs than the type ir_flag_t is capable of holding. So either eliminate 331 | * the IR flag count or change the ir_flag_t typedef to a type large enough to accomodate 332 | * all the flags. 333 | */ 334 | typedef int static_assert_is_ir_flag_safe [((IR_FLAG_LAST) <= (ir_flag_t)(-1)) ? 1 : -1]; 335 | 336 | #endif 337 | -------------------------------------------------------------------------------- /lexer.h: -------------------------------------------------------------------------------- 1 | #ifndef GMQCC_LEXER_HDR 2 | #define GMQCC_LEXER_HDR 3 | #include "gmqcc.h" 4 | 5 | struct token { 6 | int ttype; 7 | char *value; 8 | union { 9 | vec3_t v; 10 | int i; 11 | qcfloat_t f; 12 | qc_type t; /* type */ 13 | } constval; 14 | lex_ctx_t ctx; 15 | }; 16 | 17 | /* Lexer 18 | * 19 | */ 20 | enum { 21 | /* Other tokens which we can return: */ 22 | TOKEN_NONE = 0, 23 | TOKEN_START = 128, 24 | 25 | TOKEN_IDENT, 26 | 27 | TOKEN_TYPENAME, 28 | 29 | TOKEN_OPERATOR, 30 | 31 | TOKEN_KEYWORD, /* loop */ 32 | 33 | TOKEN_DOTS, /* 3 dots, ... */ 34 | 35 | TOKEN_ATTRIBUTE_OPEN, /* [[ */ 36 | TOKEN_ATTRIBUTE_CLOSE, /* ]] */ 37 | 38 | TOKEN_VA_ARGS, /* for the ftepp only */ 39 | TOKEN_VA_ARGS_ARRAY, /* for the ftepp only */ 40 | TOKEN_VA_COUNT, /* to get the count of vaargs */ 41 | 42 | TOKEN_STRINGCONST, /* not the typename but an actual "string" */ 43 | TOKEN_CHARCONST, 44 | TOKEN_VECTORCONST, 45 | TOKEN_INTCONST, 46 | TOKEN_FLOATCONST, 47 | 48 | TOKEN_WHITE, 49 | TOKEN_EOL, 50 | 51 | /* if we add additional tokens before this, the exposed API 52 | * should not be broken anyway, but EOF/ERROR/... should 53 | * still be at the bottom 54 | */ 55 | TOKEN_EOF = 1024, 56 | 57 | /* We use '< TOKEN_ERROR', so TOKEN_FATAL must come after it and any 58 | * other error related tokens as well 59 | */ 60 | TOKEN_ERROR, 61 | TOKEN_FATAL /* internal error, eg out of memory */ 62 | }; 63 | 64 | struct frame_macro { 65 | char *name; 66 | int value; 67 | }; 68 | 69 | struct lex_file { 70 | FILE *file; 71 | const char *open_string; 72 | size_t open_string_length; 73 | size_t open_string_pos; 74 | 75 | char *name; 76 | size_t line; 77 | size_t sline; /* line at the start of a token */ 78 | size_t column; 79 | 80 | int peek[256]; 81 | size_t peekpos; 82 | 83 | bool eof; 84 | 85 | token tok; /* not a pointer anymore */ 86 | 87 | struct { 88 | unsigned noops:1; 89 | unsigned nodigraphs:1; /* used when lexing string constants */ 90 | unsigned preprocessing:1; /* whitespace and EOLs become actual tokens */ 91 | unsigned mergelines:1; /* backslash at the end of a line escapes the newline */ 92 | } flags; /* sizeof == 1 */ 93 | 94 | int framevalue; 95 | frame_macro *frames; 96 | char *modelname; 97 | 98 | size_t push_line; 99 | }; 100 | 101 | lex_file* lex_open (const char *file); 102 | lex_file* lex_open_string(const char *str, size_t len, const char *name); 103 | void lex_close(lex_file *lex); 104 | int lex_do (lex_file *lex); 105 | void lex_cleanup(void); 106 | 107 | /* Parser 108 | * 109 | */ 110 | 111 | enum { 112 | ASSOC_LEFT, 113 | ASSOC_RIGHT 114 | }; 115 | 116 | #define OP_SUFFIX 1 117 | #define OP_PREFIX 2 118 | 119 | struct oper_info { 120 | const char *op; 121 | unsigned int operands; 122 | unsigned int id; 123 | unsigned int assoc; 124 | signed int prec; 125 | unsigned int flags; 126 | bool folds; 127 | }; 128 | 129 | /* 130 | * Explicit uint8_t casts since the left operand of shift operator cannot 131 | * be negative, even though it won't happen, this supresses the future 132 | * possibility. 133 | */ 134 | #define opid1(a) ((uint8_t)a) 135 | #define opid2(a,b) (((uint8_t)a<<8) |(uint8_t)b) 136 | #define opid3(a,b,c) (((uint8_t)a<<16)|((uint8_t)b<<8)|(uint8_t)c) 137 | 138 | static const oper_info c_operators[] = { 139 | { "(", 0, opid1('('), ASSOC_LEFT, 99, OP_PREFIX, false}, /* paren expression - non function call */ 140 | { "_length", 1, opid3('l','e','n'), ASSOC_RIGHT, 98, OP_PREFIX, true}, 141 | 142 | { "++", 1, opid3('S','+','+'), ASSOC_LEFT, 17, OP_SUFFIX, false}, 143 | { "--", 1, opid3('S','-','-'), ASSOC_LEFT, 17, OP_SUFFIX, false}, 144 | { ".", 2, opid1('.'), ASSOC_LEFT, 17, 0, false}, 145 | { "(", 0, opid1('('), ASSOC_LEFT, 17, 0, false}, /* function call */ 146 | { "[", 2, opid1('['), ASSOC_LEFT, 17, 0, false}, /* array subscript */ 147 | 148 | { "++", 1, opid3('+','+','P'), ASSOC_RIGHT, 16, OP_PREFIX, false}, 149 | { "--", 1, opid3('-','-','P'), ASSOC_RIGHT, 16, OP_PREFIX, false}, 150 | 151 | { "**", 2, opid2('*','*'), ASSOC_RIGHT, 14, 0, true}, 152 | { "!", 1, opid2('!','P'), ASSOC_RIGHT, 14, OP_PREFIX, true}, 153 | { "~", 1, opid2('~','P'), ASSOC_RIGHT, 14, OP_PREFIX, true}, 154 | { "+", 1, opid2('+','P'), ASSOC_RIGHT, 14, OP_PREFIX, false}, 155 | { "-", 1, opid2('-','P'), ASSOC_RIGHT, 14, OP_PREFIX, true}, 156 | /* { "&", 1, opid2('&','P'), ASSOC_RIGHT, 14, OP_PREFIX, false}, */ 157 | 158 | { "*", 2, opid1('*'), ASSOC_LEFT, 13, 0, true}, 159 | { "/", 2, opid1('/'), ASSOC_LEFT, 13, 0, true}, 160 | { "%", 2, opid1('%'), ASSOC_LEFT, 13, 0, true}, 161 | { "><", 2, opid2('>','<'), ASSOC_LEFT, 13, 0, true}, 162 | 163 | { "+", 2, opid1('+'), ASSOC_LEFT, 12, 0, true}, 164 | { "-", 2, opid1('-'), ASSOC_LEFT, 12, 0, true}, 165 | 166 | { "<<", 2, opid2('<','<'), ASSOC_LEFT, 11, 0, true}, 167 | { ">>", 2, opid2('>','>'), ASSOC_LEFT, 11, 0, true}, 168 | 169 | { "<", 2, opid1('<'), ASSOC_LEFT, 10, 0, false}, 170 | { ">", 2, opid1('>'), ASSOC_LEFT, 10, 0, false}, 171 | { "<=>", 2, opid3('<','=','>'), ASSOC_LEFT, 10, 0, true}, 172 | { "<=", 2, opid2('<','='), ASSOC_LEFT, 10, 0, false}, 173 | { ">=", 2, opid2('>','='), ASSOC_LEFT, 10, 0, false}, 174 | 175 | { "==", 2, opid2('=','='), ASSOC_LEFT, 9, 0, true}, 176 | { "!=", 2, opid2('!','='), ASSOC_LEFT, 9, 0, true}, 177 | 178 | { "&", 2, opid1('&'), ASSOC_LEFT, 8, 0, true}, 179 | 180 | { "^", 2, opid1('^'), ASSOC_LEFT, 7, 0, true}, 181 | 182 | { "|", 2, opid1('|'), ASSOC_LEFT, 6, 0, true}, 183 | 184 | { "&&", 2, opid2('&','&'), ASSOC_LEFT, 5, 0, true}, 185 | 186 | { "||", 2, opid2('|','|'), ASSOC_LEFT, 4, 0, true}, 187 | 188 | { "?", 3, opid2('?',':'), ASSOC_RIGHT, 3, 0, true}, 189 | 190 | { "=", 2, opid1('='), ASSOC_RIGHT, 2, 0, false}, 191 | { "+=", 2, opid2('+','='), ASSOC_RIGHT, 2, 0, false}, 192 | { "-=", 2, opid2('-','='), ASSOC_RIGHT, 2, 0, false}, 193 | { "*=", 2, opid2('*','='), ASSOC_RIGHT, 2, 0, false}, 194 | { "/=", 2, opid2('/','='), ASSOC_RIGHT, 2, 0, false}, 195 | { "%=", 2, opid2('%','='), ASSOC_RIGHT, 2, 0, false}, 196 | { ">>=", 2, opid3('>','>','='), ASSOC_RIGHT, 2, 0, false}, 197 | { "<<=", 2, opid3('<','<','='), ASSOC_RIGHT, 2, 0, false}, 198 | { "&=", 2, opid2('&','='), ASSOC_RIGHT, 2, 0, false}, 199 | { "^=", 2, opid2('^','='), ASSOC_RIGHT, 2, 0, false}, 200 | { "|=", 2, opid2('|','='), ASSOC_RIGHT, 2, 0, false}, 201 | 202 | { ":", 0, opid2(':','?'), ASSOC_RIGHT, 1, 0, false}, 203 | 204 | { ",", 2, opid1(','), ASSOC_LEFT, 0, 0, false} 205 | }; 206 | 207 | static const oper_info fte_operators[] = { 208 | { "(", 0, opid1('('), ASSOC_LEFT, 99, OP_PREFIX, false}, /* paren expression - non function call */ 209 | 210 | { "++", 1, opid3('S','+','+'), ASSOC_LEFT, 15, OP_SUFFIX, false}, 211 | { "--", 1, opid3('S','-','-'), ASSOC_LEFT, 15, OP_SUFFIX, false}, 212 | { ".", 2, opid1('.'), ASSOC_LEFT, 15, 0, false}, 213 | { "(", 0, opid1('('), ASSOC_LEFT, 15, 0, false}, /* function call */ 214 | { "[", 2, opid1('['), ASSOC_LEFT, 15, 0, false}, /* array subscript */ 215 | 216 | { "!", 1, opid2('!','P'), ASSOC_RIGHT, 14, OP_PREFIX, true}, 217 | { "+", 1, opid2('+','P'), ASSOC_RIGHT, 14, OP_PREFIX, false}, 218 | { "-", 1, opid2('-','P'), ASSOC_RIGHT, 14, OP_PREFIX, true}, 219 | { "++", 1, opid3('+','+','P'), ASSOC_RIGHT, 14, OP_PREFIX, false}, 220 | { "--", 1, opid3('-','-','P'), ASSOC_RIGHT, 14, OP_PREFIX, false}, 221 | 222 | { "*", 2, opid1('*'), ASSOC_LEFT, 13, 0, true}, 223 | { "/", 2, opid1('/'), ASSOC_LEFT, 13, 0, true}, 224 | { "&", 2, opid1('&'), ASSOC_LEFT, 13, 0, true}, 225 | { "|", 2, opid1('|'), ASSOC_LEFT, 13, 0, true}, 226 | 227 | { "+", 2, opid1('+'), ASSOC_LEFT, 12, 0, true}, 228 | { "-", 2, opid1('-'), ASSOC_LEFT, 12, 0, true}, 229 | 230 | { "<<", 2, opid2('<','<'), ASSOC_LEFT, 11, 0, true}, 231 | { ">>", 2, opid2('>','>'), ASSOC_LEFT, 11, 0, true}, 232 | 233 | { "<", 2, opid1('<'), ASSOC_LEFT, 10, 0, false}, 234 | { ">", 2, opid1('>'), ASSOC_LEFT, 10, 0, false}, 235 | { "<=", 2, opid2('<','='), ASSOC_LEFT, 10, 0, false}, 236 | { ">=", 2, opid2('>','='), ASSOC_LEFT, 10, 0, false}, 237 | { "==", 2, opid2('=','='), ASSOC_LEFT, 10, 0, true}, 238 | { "!=", 2, opid2('!','='), ASSOC_LEFT, 10, 0, true}, 239 | 240 | { "?", 3, opid2('?',':'), ASSOC_RIGHT, 9, 0, true}, 241 | 242 | { "=", 2, opid1('='), ASSOC_RIGHT, 8, 0, false}, 243 | { "+=", 2, opid2('+','='), ASSOC_RIGHT, 8, 0, false}, 244 | { "-=", 2, opid2('-','='), ASSOC_RIGHT, 8, 0, false}, 245 | { "*=", 2, opid2('*','='), ASSOC_RIGHT, 8, 0, false}, 246 | { "/=", 2, opid2('/','='), ASSOC_RIGHT, 8, 0, false}, 247 | { "%=", 2, opid2('%','='), ASSOC_RIGHT, 8, 0, false}, 248 | { "&=", 2, opid2('&','='), ASSOC_RIGHT, 8, 0, false}, 249 | { "|=", 2, opid2('|','='), ASSOC_RIGHT, 8, 0, false}, 250 | { "&~=", 2, opid3('&','~','='), ASSOC_RIGHT, 8, 0, false}, 251 | 252 | { "&&", 2, opid2('&','&'), ASSOC_LEFT, 5, 0, true}, 253 | { "||", 2, opid2('|','|'), ASSOC_LEFT, 5, 0, true}, 254 | 255 | /* Leave precedence 3 for : with -fcorrect-ternary */ 256 | { ",", 2, opid1(','), ASSOC_LEFT, 2, 0, false}, 257 | { ":", 0, opid2(':','?'), ASSOC_RIGHT, 1, 0, false} 258 | }; 259 | 260 | static const oper_info qcc_operators[] = { 261 | { "(", 0, opid1('('), ASSOC_LEFT, 99, OP_PREFIX, false}, /* paren expression - non function call */ 262 | 263 | { ".", 2, opid1('.'), ASSOC_LEFT, 15, 0, false}, 264 | { "(", 0, opid1('('), ASSOC_LEFT, 15, 0, false}, /* function call */ 265 | { "[", 2, opid1('['), ASSOC_LEFT, 15, 0, false}, /* array subscript */ 266 | 267 | { "!", 1, opid2('!','P'), ASSOC_RIGHT, 14, OP_PREFIX, true}, 268 | { "+", 1, opid2('+','P'), ASSOC_RIGHT, 14, OP_PREFIX, false}, 269 | { "-", 1, opid2('-','P'), ASSOC_RIGHT, 14, OP_PREFIX, true}, 270 | 271 | { "*", 2, opid1('*'), ASSOC_LEFT, 13, 0, true}, 272 | { "/", 2, opid1('/'), ASSOC_LEFT, 13, 0, true}, 273 | { "&", 2, opid1('&'), ASSOC_LEFT, 13, 0, true}, 274 | { "|", 2, opid1('|'), ASSOC_LEFT, 13, 0, true}, 275 | 276 | { "+", 2, opid1('+'), ASSOC_LEFT, 12, 0, true}, 277 | { "-", 2, opid1('-'), ASSOC_LEFT, 12, 0, true}, 278 | 279 | { "<", 2, opid1('<'), ASSOC_LEFT, 10, 0, false}, 280 | { ">", 2, opid1('>'), ASSOC_LEFT, 10, 0, false}, 281 | { "<=", 2, opid2('<','='), ASSOC_LEFT, 10, 0, false}, 282 | { ">=", 2, opid2('>','='), ASSOC_LEFT, 10, 0, false}, 283 | { "==", 2, opid2('=','='), ASSOC_LEFT, 10, 0, true}, 284 | { "!=", 2, opid2('!','='), ASSOC_LEFT, 10, 0, true}, 285 | 286 | { "=", 2, opid1('='), ASSOC_RIGHT, 8, 0, false}, 287 | { "+=", 2, opid2('+','='), ASSOC_RIGHT, 8, 0, false}, 288 | { "-=", 2, opid2('-','='), ASSOC_RIGHT, 8, 0, false}, 289 | { "*=", 2, opid2('*','='), ASSOC_RIGHT, 8, 0, false}, 290 | { "/=", 2, opid2('/','='), ASSOC_RIGHT, 8, 0, false}, 291 | { "%=", 2, opid2('%','='), ASSOC_RIGHT, 8, 0, false}, 292 | { "&=", 2, opid2('&','='), ASSOC_RIGHT, 8, 0, false}, 293 | { "|=", 2, opid2('|','='), ASSOC_RIGHT, 8, 0, false}, 294 | 295 | { "&&", 2, opid2('&','&'), ASSOC_LEFT, 5, 0, true}, 296 | { "||", 2, opid2('|','|'), ASSOC_LEFT, 5, 0, true}, 297 | 298 | { ",", 2, opid1(','), ASSOC_LEFT, 2, 0, false}, 299 | }; 300 | extern const oper_info *operators; 301 | extern size_t operator_count; 302 | 303 | #endif 304 | -------------------------------------------------------------------------------- /misc/check-doc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | prog=$0 3 | 4 | die() { 5 | echo "$@" 6 | exit 1 7 | } 8 | 9 | want() { 10 | test -e "$1" && return 11 | echo "$prog: missing $1" 12 | echo "$prog: run this script from the top of a gmqcc source tree" 13 | exit 1 14 | } 15 | 16 | for i in opts.def \ 17 | doc/gmqcc.1 \ 18 | gmqcc.ini.example 19 | do want "$i"; done 20 | 21 | # y/_ABCDEFGHIJKLMNOPQRSTUVWXYZ/-abcdefghijklmnopqrstuvwxyz/; 22 | check_opt() { 23 | opt_def_name=$1 24 | arg_char=$2 25 | 26 | for i in $(sed -ne \ 27 | '/^#ifdef GMQCC_TYPE_'${opt_def_name}'$/,/^#endif/{ 28 | /GMQCC_DEFINE_FLAG/{ 29 | s/^.*GMQCC_DEFINE_FLAG(\([^,)]*\)[),].*$/\1/;p; 30 | } 31 | }' opts.def) 32 | do 33 | opt=$(echo "$i" | tr -- '_A-Z' '-a-z') 34 | grep -qF -- ".It Fl "${arg_char}" Ns Cm $opt" \ 35 | doc/gmqcc.1 || echo "doc/gmqcc.1: missing: -${arg_char}$opt" 36 | grep -q -- "[^a-zA-Z_]$i[^a-zA-Z_]" \ 37 | gmqcc.ini.example || echo "gmqcc.ini.example: missing: $i" 38 | done 39 | } 40 | 41 | check_opt FLAGS f 42 | check_opt WARNS W 43 | check_opt OPTIMIZATIONS O 44 | 45 | # TODO: linux version 46 | if [ "$(uname -s)" != "Linux" ]; then 47 | for i in doc/*.1; 48 | do 49 | mandoc -Tlint -Wall "$i"; 50 | done 51 | fi 52 | -------------------------------------------------------------------------------- /opts.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "gmqcc.h" 5 | 6 | const unsigned int opts_opt_oflag[COUNT_OPTIMIZATIONS+1] = { 7 | # define GMQCC_TYPE_OPTIMIZATIONS 8 | # define GMQCC_DEFINE_FLAG(NAME, MIN_O) MIN_O, 9 | # include "opts.def" 10 | 0 11 | }; 12 | 13 | const opts_flag_def_t opts_opt_list[COUNT_OPTIMIZATIONS+1] = { 14 | # define GMQCC_TYPE_OPTIMIZATIONS 15 | # define GMQCC_DEFINE_FLAG(NAME, MIN_O) { #NAME, LONGBIT(OPTIM_##NAME) }, 16 | # include "opts.def" 17 | { nullptr, LONGBIT(0) } 18 | }; 19 | 20 | const opts_flag_def_t opts_warn_list[COUNT_WARNINGS+1] = { 21 | # define GMQCC_TYPE_WARNS 22 | # define GMQCC_DEFINE_FLAG(X) { #X, LONGBIT(WARN_##X) }, 23 | # include "opts.def" 24 | { nullptr, LONGBIT(0) } 25 | }; 26 | 27 | const opts_flag_def_t opts_flag_list[COUNT_FLAGS+1] = { 28 | # define GMQCC_TYPE_FLAGS 29 | # define GMQCC_DEFINE_FLAG(X) { #X, LONGBIT(X) }, 30 | # include "opts.def" 31 | { nullptr, LONGBIT(0) } 32 | }; 33 | 34 | unsigned int opts_optimizationcount[COUNT_OPTIMIZATIONS]; 35 | opts_cmd_t opts; /* command line options */ 36 | 37 | static void opts_setdefault(void) { 38 | memset(&opts, 0, sizeof(opts_cmd_t)); 39 | OPTS_OPTION_STR(OPTION_PROGSRC) = "progs.src"; 40 | 41 | /* warnings */ 42 | opts_set(opts.warn, WARN_UNUSED_VARIABLE, true); 43 | opts_set(opts.warn, WARN_USED_UNINITIALIZED, true); 44 | opts_set(opts.warn, WARN_UNKNOWN_CONTROL_SEQUENCE, true); 45 | opts_set(opts.warn, WARN_EXTENSIONS, true); 46 | opts_set(opts.warn, WARN_FIELD_REDECLARED, true); 47 | opts_set(opts.warn, WARN_MISSING_RETURN_VALUES, true); 48 | opts_set(opts.warn, WARN_INVALID_PARAMETER_COUNT, true); 49 | opts_set(opts.warn, WARN_LOCAL_CONSTANTS, true); 50 | opts_set(opts.warn, WARN_VOID_VARIABLES, true); 51 | opts_set(opts.warn, WARN_IMPLICIT_FUNCTION_POINTER, true); 52 | opts_set(opts.warn, WARN_VARIADIC_FUNCTION, true); 53 | opts_set(opts.warn, WARN_FRAME_MACROS, true); 54 | opts_set(opts.warn, WARN_EFFECTLESS_STATEMENT, true); 55 | opts_set(opts.warn, WARN_END_SYS_FIELDS, true); 56 | opts_set(opts.warn, WARN_ASSIGN_FUNCTION_TYPES, true); 57 | opts_set(opts.warn, WARN_CPP, true); 58 | opts_set(opts.warn, WARN_MULTIFILE_IF, true); 59 | opts_set(opts.warn, WARN_DOUBLE_DECLARATION, true); 60 | opts_set(opts.warn, WARN_CONST_VAR, true); 61 | opts_set(opts.warn, WARN_MULTIBYTE_CHARACTER, true); 62 | opts_set(opts.warn, WARN_UNKNOWN_PRAGMAS, true); 63 | opts_set(opts.warn, WARN_UNREACHABLE_CODE, true); 64 | opts_set(opts.warn, WARN_UNKNOWN_ATTRIBUTE, true); 65 | opts_set(opts.warn, WARN_RESERVED_NAMES, true); 66 | opts_set(opts.warn, WARN_UNINITIALIZED_CONSTANT, true); 67 | opts_set(opts.warn, WARN_DEPRECATED, true); 68 | opts_set(opts.warn, WARN_PARENTHESIS, true); 69 | opts_set(opts.warn, WARN_CONST_OVERWRITE, true); 70 | opts_set(opts.warn, WARN_DIRECTIVE_INMACRO, true); 71 | opts_set(opts.warn, WARN_BUILTINS, true); 72 | opts_set(opts.warn, WARN_INEXACT_COMPARES, true); 73 | 74 | /* flags */ 75 | opts_set(opts.flags, ADJUST_VECTOR_FIELDS, true); 76 | opts_set(opts.flags, CORRECT_TERNARY, true); 77 | opts_set(opts.flags, BAIL_ON_WERROR, true); 78 | opts_set(opts.flags, LEGACY_VECTOR_MATHS, true); 79 | opts_set(opts.flags, DARKPLACES_STRING_TABLE_BUG, true); 80 | 81 | /* options */ 82 | OPTS_OPTION_U32(OPTION_STATE_FPS) = 10; 83 | } 84 | 85 | void opts_backup_non_Wall() { 86 | size_t i; 87 | for (i = 0; i <= WARN_DEBUG; ++i) 88 | opts_set(opts.warn_backup, i, OPTS_WARN(i)); 89 | } 90 | 91 | void opts_restore_non_Wall() { 92 | size_t i; 93 | for (i = 0; i <= WARN_DEBUG; ++i) 94 | opts_set(opts.warn, i, OPTS_GENERIC(opts.warn_backup, i)); 95 | } 96 | 97 | void opts_backup_non_Werror_all() { 98 | size_t i; 99 | for (i = 0; i <= WARN_DEBUG; ++i) 100 | opts_set(opts.werror_backup, i, OPTS_WERROR(i)); 101 | } 102 | 103 | void opts_restore_non_Werror_all() { 104 | size_t i; 105 | for (i = 0; i <= WARN_DEBUG; ++i) 106 | opts_set(opts.werror, i, OPTS_GENERIC(opts.werror_backup, i)); 107 | } 108 | 109 | void opts_init(const char *output, int standard, size_t arraysize) { 110 | opts_setdefault(); 111 | 112 | OPTS_OPTION_STR(OPTION_OUTPUT) = output; 113 | OPTS_OPTION_U32(OPTION_STANDARD) = standard; 114 | OPTS_OPTION_U32(OPTION_MAX_ARRAY_SIZE) = arraysize; 115 | } 116 | 117 | static bool opts_setflag_all(const char *name, bool on, uint32_t *flags, const opts_flag_def_t *list, size_t listsize) { 118 | size_t i; 119 | 120 | for (i = 0; i < listsize; ++i) { 121 | if (!strcmp(name, list[i].name)) { 122 | longbit lb = list[i].bit; 123 | 124 | if (on) 125 | flags[lb.idx] |= (1<<(lb.bit)); 126 | else 127 | flags[lb.idx] &= ~(1<<(lb.bit)); 128 | 129 | return true; 130 | } 131 | } 132 | return false; 133 | } 134 | bool opts_setflag (const char *name, bool on) { 135 | return opts_setflag_all(name, on, opts.flags, opts_flag_list, COUNT_FLAGS); 136 | } 137 | bool opts_setwarn (const char *name, bool on) { 138 | return opts_setflag_all(name, on, opts.warn, opts_warn_list, COUNT_WARNINGS); 139 | } 140 | bool opts_setwerror(const char *name, bool on) { 141 | return opts_setflag_all(name, on, opts.werror, opts_warn_list, COUNT_WARNINGS); 142 | } 143 | bool opts_setoptim (const char *name, bool on) { 144 | return opts_setflag_all(name, on, opts.optimization, opts_opt_list, COUNT_OPTIMIZATIONS); 145 | } 146 | 147 | void opts_set(uint32_t *flags, size_t idx, bool on) { 148 | longbit lb; 149 | LONGBIT_SET(lb, idx); 150 | 151 | if (on) 152 | flags[lb.idx] |= (1u<<(lb.bit)); 153 | else 154 | flags[lb.idx] &= ~(1u<<(lb.bit)); 155 | } 156 | 157 | void opts_setoptimlevel(unsigned int level) { 158 | size_t i; 159 | for (i = 0; i < COUNT_OPTIMIZATIONS; ++i) 160 | opts_set(opts.optimization, i, level >= opts_opt_oflag[i]); 161 | 162 | if (!level) 163 | opts.optimizeoff = true; 164 | } 165 | 166 | /* 167 | * Standard configuration parser and subsystem. Yes, optionally you may 168 | * create ini files or cfg (the driver accepts both) for a project opposed 169 | * to supplying just a progs.src (since you also may need to supply command 170 | * line arguments or set the options of the compiler) [which cannot be done 171 | * from a progs.src. 172 | */ 173 | static char *opts_ini_rstrip(char *s) { 174 | char *p = s + strlen(s) - 1; 175 | while (p > s && util_isspace(*p)) 176 | *p = '\0', p--; 177 | return s; 178 | } 179 | 180 | static char *opts_ini_lskip(const char *s) { 181 | while (*s && util_isspace(*s)) 182 | s++; 183 | return (char*)s; 184 | } 185 | 186 | static char *opts_ini_next(const char *s, char c) { 187 | bool last = false; 188 | while (*s && *s != c && !(last && *s == ';')) 189 | last = !!util_isspace(*s), s++; 190 | 191 | return (char*)s; 192 | } 193 | 194 | static size_t opts_ini_parse ( 195 | FILE *filehandle, 196 | char *(*loadhandle)(const char *, const char *, const char *, char **), 197 | char **errorhandle, 198 | char **parse_file 199 | ) { 200 | size_t linesize; 201 | size_t lineno = 1; 202 | size_t error = 0; 203 | char *line = nullptr; 204 | char section_data[2048] = ""; 205 | char oldname_data[2048] = ""; 206 | 207 | /* parsing and reading variables */ 208 | char *parse_beg; 209 | char *parse_end; 210 | char *read_name; 211 | char *read_value; 212 | 213 | while (util_getline(&line, &linesize, filehandle) != EOF) { 214 | parse_beg = line; 215 | 216 | /* handle BOM */ 217 | if (lineno == 1 && ( 218 | (unsigned char)parse_beg[0] == 0xEF && 219 | (unsigned char)parse_beg[1] == 0xBB && 220 | (unsigned char)parse_beg[2] == 0xBF 221 | ) 222 | ) { 223 | parse_beg ++; /* 0xEF */ 224 | parse_beg ++; /* 0xBB */ 225 | parse_beg ++; /* 0xBF */ 226 | } 227 | 228 | if (*(parse_beg = opts_ini_lskip(opts_ini_rstrip(parse_beg))) == ';' || *parse_beg == '#') { 229 | /* ignore '#' is a perl extension */ 230 | } else if (*parse_beg == '[') { 231 | /* section found */ 232 | if (*(parse_end = opts_ini_next(parse_beg + 1, ']')) == ']') { 233 | * parse_end = '\0'; /* terminate bro */ 234 | util_strncpy(section_data, parse_beg + 1, sizeof(section_data)); 235 | section_data[sizeof(section_data) - 1] = '\0'; 236 | *oldname_data = '\0'; 237 | } else if (!error) { 238 | /* otherwise set error to the current line number */ 239 | error = lineno; 240 | } 241 | } else if (*parse_beg && *parse_beg != ';') { 242 | /* not a comment, must be a name value pair :) */ 243 | if (*(parse_end = opts_ini_next(parse_beg, '=')) != '=') 244 | parse_end = opts_ini_next(parse_beg, ':'); 245 | 246 | if (*parse_end == '=' || *parse_end == ':') { 247 | *parse_end = '\0'; /* terminate bro */ 248 | read_name = opts_ini_rstrip(parse_beg); 249 | read_value = opts_ini_lskip(parse_end + 1); 250 | if (*(parse_end = opts_ini_next(read_value, '\0')) == ';') 251 | * parse_end = '\0'; 252 | opts_ini_rstrip(read_value); 253 | 254 | /* valid name value pair, lets call down to handler */ 255 | util_strncpy(oldname_data, read_name, sizeof(oldname_data)); 256 | oldname_data[sizeof(oldname_data) - 1] ='\0'; 257 | 258 | if ((*errorhandle = loadhandle(section_data, read_name, read_value, parse_file)) && !error) 259 | error = lineno; 260 | } else if (!strcmp(section_data, "includes")) { 261 | /* Includes are special */ 262 | if (*(parse_end = opts_ini_next(parse_beg, '=')) == '=' 263 | || *(parse_end = opts_ini_next(parse_beg, ':')) == ':') { 264 | static const char *invalid_include = "invalid use of include"; 265 | vec_append(*errorhandle, strlen(invalid_include), invalid_include); 266 | error = lineno; 267 | } else { 268 | read_name = opts_ini_rstrip(parse_beg); 269 | if ((*errorhandle = loadhandle(section_data, read_name, read_name, parse_file)) && !error) 270 | error = lineno; 271 | } 272 | } else if (!error) { 273 | /* otherwise set error to the current line number */ 274 | error = lineno; 275 | } 276 | } 277 | lineno++; 278 | } 279 | mem_d(line); 280 | return error; 281 | 282 | } 283 | 284 | /* 285 | * returns true/false for a char that contains ("true" or "false" or numeric 0/1) 286 | */ 287 | static bool opts_ini_bool(const char *value) { 288 | if (!strcmp(value, "true")) return true; 289 | if (!strcmp(value, "false")) return false; 290 | return !!strtol(value, nullptr, 10); 291 | } 292 | 293 | static char *opts_ini_load(const char *section, const char *name, const char *value, char **parse_file) { 294 | char *error = nullptr; 295 | bool found = false; 296 | 297 | /* 298 | * undef all of these because they may still be defined like in my 299 | * case they where. 300 | */ 301 | #undef GMQCC_TYPE_FLAGS 302 | #undef GMQCC_TYPE_OPTIMIZATIONS 303 | #undef GMQCC_TYPE_WARNS 304 | 305 | /* deal with includes */ 306 | if (!strcmp(section, "includes")) { 307 | static const char *include_error_beg = "failed to open file `"; 308 | static const char *include_error_end = "' for inclusion"; 309 | FILE *file = fopen(value, "r"); 310 | found = true; 311 | if (!file) { 312 | vec_append(error, strlen(include_error_beg), include_error_beg); 313 | vec_append(error, strlen(value), value); 314 | vec_append(error, strlen(include_error_end), include_error_end); 315 | } else { 316 | if (opts_ini_parse(file, &opts_ini_load, &error, parse_file) != 0) 317 | found = false; 318 | /* Change the file name */ 319 | mem_d(*parse_file); 320 | *parse_file = util_strdup(value); 321 | fclose(file); 322 | } 323 | } 324 | 325 | /* flags */ 326 | #define GMQCC_TYPE_FLAGS 327 | #define GMQCC_DEFINE_FLAG(X) \ 328 | if (!strcmp(section, "flags") && !strcmp(name, #X)) { \ 329 | opts_set(opts.flags, X, opts_ini_bool(value)); \ 330 | found = true; \ 331 | } 332 | #include "opts.def" 333 | 334 | /* warnings */ 335 | #define GMQCC_TYPE_WARNS 336 | #define GMQCC_DEFINE_FLAG(X) \ 337 | if (!strcmp(section, "warnings") && !strcmp(name, #X)) { \ 338 | opts_set(opts.warn, WARN_##X, opts_ini_bool(value)); \ 339 | found = true; \ 340 | } 341 | #include "opts.def" 342 | 343 | /* Werror-individuals */ 344 | #define GMQCC_TYPE_WARNS 345 | #define GMQCC_DEFINE_FLAG(X) \ 346 | if (!strcmp(section, "errors") && !strcmp(name, #X)) { \ 347 | opts_set(opts.werror, WARN_##X, opts_ini_bool(value)); \ 348 | found = true; \ 349 | } 350 | #include "opts.def" 351 | 352 | /* optimizations */ 353 | #define GMQCC_TYPE_OPTIMIZATIONS 354 | #define GMQCC_DEFINE_FLAG(X,Y) \ 355 | if (!strcmp(section, "optimizations") && !strcmp(name, #X)) { \ 356 | opts_set(opts.optimization, OPTIM_##X, opts_ini_bool(value)); \ 357 | found = true; \ 358 | } 359 | #include "opts.def" 360 | 361 | /* nothing was found ever! */ 362 | if (!found) { 363 | if (strcmp(section, "includes") && 364 | strcmp(section, "flags") && 365 | strcmp(section, "warnings") && 366 | strcmp(section, "optimizations")) 367 | { 368 | static const char *invalid_section = "invalid_section `"; 369 | vec_append(error, strlen(invalid_section), invalid_section); 370 | vec_append(error, strlen(section), section); 371 | vec_push(error, '`'); 372 | } else if (strcmp(section, "includes")) { 373 | static const char *invalid_variable = "invalid_variable `"; 374 | static const char *in_section = "` in section: `"; 375 | vec_append(error, strlen(invalid_variable), invalid_variable); 376 | vec_append(error, strlen(name), name); 377 | vec_append(error, strlen(in_section), in_section); 378 | vec_append(error, strlen(section), section); 379 | vec_push(error, '`'); 380 | } else { 381 | static const char *expected_something = "expected something"; 382 | vec_append(error, strlen(expected_something), expected_something); 383 | } 384 | } 385 | vec_push(error, '\0'); 386 | return error; 387 | } 388 | 389 | /* 390 | * Actual loading subsystem, this finds the ini or cfg file, and properly 391 | * loads it and executes it to set compiler options. 392 | */ 393 | void opts_ini_init(const char *file) { 394 | /* 395 | * Possible matches are: 396 | * gmqcc.ini 397 | * gmqcc.cfg 398 | */ 399 | char *error = nullptr; 400 | char *parse_file = nullptr; 401 | size_t line; 402 | FILE *ini; 403 | 404 | if (!file) { 405 | /* try ini */ 406 | if (!(ini = fopen((file = "gmqcc.ini"), "r"))) 407 | /* try cfg */ 408 | if (!(ini = fopen((file = "gmqcc.cfg"), "r"))) 409 | return; 410 | } else if (!(ini = fopen(file, "r"))) 411 | return; 412 | 413 | con_out("found ini file `%s`\n", file); 414 | 415 | parse_file = util_strdup(file); 416 | if ((line = opts_ini_parse(ini, &opts_ini_load, &error, &parse_file)) != 0) { 417 | /* there was a parse error with the ini file */ 418 | con_printmsg(LVL_ERROR, parse_file, line, 0 /*TODO: column for ini error*/, "error", error); 419 | vec_free(error); 420 | } 421 | mem_d(parse_file); 422 | 423 | fclose(ini); 424 | } 425 | -------------------------------------------------------------------------------- /opts.def: -------------------------------------------------------------------------------- 1 | #ifndef GMQCC_DEFINE_FLAG 2 | # error "bad opts.def usage" 3 | #endif 4 | 5 | /* codegen flags */ 6 | #ifdef GMQCC_TYPE_FLAGS 7 | GMQCC_DEFINE_FLAG(DARKPLACES_STRING_TABLE_BUG) 8 | GMQCC_DEFINE_FLAG(ADJUST_VECTOR_FIELDS) 9 | GMQCC_DEFINE_FLAG(FTEPP) 10 | GMQCC_DEFINE_FLAG(FTEPP_PREDEFS) 11 | GMQCC_DEFINE_FLAG(FTEPP_MATHDEFS) 12 | GMQCC_DEFINE_FLAG(FTEPP_INDIRECT_EXPANSION) 13 | GMQCC_DEFINE_FLAG(RELAXED_SWITCH) 14 | GMQCC_DEFINE_FLAG(SHORT_LOGIC) 15 | GMQCC_DEFINE_FLAG(PERL_LOGIC) 16 | GMQCC_DEFINE_FLAG(TRANSLATABLE_STRINGS) 17 | GMQCC_DEFINE_FLAG(INITIALIZED_NONCONSTANTS) 18 | GMQCC_DEFINE_FLAG(ASSIGN_FUNCTION_TYPES) 19 | GMQCC_DEFINE_FLAG(LNO) 20 | GMQCC_DEFINE_FLAG(CORRECT_TERNARY) 21 | GMQCC_DEFINE_FLAG(SINGLE_VECTOR_DEFS) 22 | GMQCC_DEFINE_FLAG(CORRECT_LOGIC) 23 | GMQCC_DEFINE_FLAG(TRUE_EMPTY_STRINGS) 24 | GMQCC_DEFINE_FLAG(FALSE_EMPTY_STRINGS) 25 | GMQCC_DEFINE_FLAG(UTF8) 26 | GMQCC_DEFINE_FLAG(BAIL_ON_WERROR) 27 | GMQCC_DEFINE_FLAG(LOOP_LABELS) 28 | GMQCC_DEFINE_FLAG(UNTYPED_NIL) 29 | GMQCC_DEFINE_FLAG(PERMISSIVE) 30 | GMQCC_DEFINE_FLAG(VARIADIC_ARGS) 31 | GMQCC_DEFINE_FLAG(LEGACY_VECTOR_MATHS) 32 | GMQCC_DEFINE_FLAG(EXPRESSIONS_FOR_BUILTINS) 33 | GMQCC_DEFINE_FLAG(RETURN_ASSIGNMENTS) 34 | GMQCC_DEFINE_FLAG(UNSAFE_VARARGS) 35 | GMQCC_DEFINE_FLAG(TYPELESS_STORES) 36 | GMQCC_DEFINE_FLAG(SORT_OPERANDS) 37 | GMQCC_DEFINE_FLAG(EMULATE_STATE) 38 | GMQCC_DEFINE_FLAG(ARITHMETIC_EXCEPTIONS) 39 | GMQCC_DEFINE_FLAG(SPLIT_VECTOR_PARAMETERS) 40 | GMQCC_DEFINE_FLAG(DEFAULT_ERASEABLE) 41 | #endif 42 | 43 | /* warning flags */ 44 | #ifdef GMQCC_TYPE_WARNS 45 | GMQCC_DEFINE_FLAG(UNINITIALIZED_GLOBAL) 46 | GMQCC_DEFINE_FLAG(DEBUG) 47 | GMQCC_DEFINE_FLAG(UNUSED_VARIABLE) 48 | GMQCC_DEFINE_FLAG(UNUSED_COMPONENT) 49 | GMQCC_DEFINE_FLAG(USED_UNINITIALIZED) 50 | GMQCC_DEFINE_FLAG(UNKNOWN_CONTROL_SEQUENCE) 51 | GMQCC_DEFINE_FLAG(EXTENSIONS) 52 | GMQCC_DEFINE_FLAG(FIELD_REDECLARED) 53 | GMQCC_DEFINE_FLAG(MISSING_RETURN_VALUES) 54 | GMQCC_DEFINE_FLAG(INVALID_PARAMETER_COUNT) 55 | GMQCC_DEFINE_FLAG(LOCAL_SHADOWS) 56 | GMQCC_DEFINE_FLAG(LOCAL_CONSTANTS) 57 | GMQCC_DEFINE_FLAG(VOID_VARIABLES) 58 | GMQCC_DEFINE_FLAG(IMPLICIT_FUNCTION_POINTER) 59 | GMQCC_DEFINE_FLAG(VARIADIC_FUNCTION) 60 | GMQCC_DEFINE_FLAG(FRAME_MACROS) 61 | GMQCC_DEFINE_FLAG(EFFECTLESS_STATEMENT) 62 | GMQCC_DEFINE_FLAG(END_SYS_FIELDS) 63 | GMQCC_DEFINE_FLAG(ASSIGN_FUNCTION_TYPES) 64 | GMQCC_DEFINE_FLAG(CPP) 65 | GMQCC_DEFINE_FLAG(MULTIFILE_IF) 66 | GMQCC_DEFINE_FLAG(DOUBLE_DECLARATION) 67 | GMQCC_DEFINE_FLAG(CONST_VAR) 68 | GMQCC_DEFINE_FLAG(MULTIBYTE_CHARACTER) 69 | GMQCC_DEFINE_FLAG(TERNARY_PRECEDENCE) 70 | GMQCC_DEFINE_FLAG(UNKNOWN_PRAGMAS) 71 | GMQCC_DEFINE_FLAG(UNREACHABLE_CODE) 72 | GMQCC_DEFINE_FLAG(UNKNOWN_ATTRIBUTE) 73 | GMQCC_DEFINE_FLAG(RESERVED_NAMES) 74 | GMQCC_DEFINE_FLAG(UNINITIALIZED_CONSTANT) 75 | GMQCC_DEFINE_FLAG(DIFFERENT_QUALIFIERS) 76 | GMQCC_DEFINE_FLAG(DIFFERENT_ATTRIBUTES) 77 | GMQCC_DEFINE_FLAG(DEPRECATED) 78 | GMQCC_DEFINE_FLAG(PARENTHESIS) 79 | GMQCC_DEFINE_FLAG(UNSAFE_TYPES) 80 | GMQCC_DEFINE_FLAG(BREAKDEF) 81 | GMQCC_DEFINE_FLAG(CONST_OVERWRITE) 82 | GMQCC_DEFINE_FLAG(DIRECTIVE_INMACRO) 83 | GMQCC_DEFINE_FLAG(BUILTINS) 84 | GMQCC_DEFINE_FLAG(INEXACT_COMPARES) 85 | #endif 86 | 87 | #ifdef GMQCC_TYPE_OPTIMIZATIONS 88 | GMQCC_DEFINE_FLAG(PEEPHOLE, 1) 89 | GMQCC_DEFINE_FLAG(TAIL_RECURSION, 1) 90 | GMQCC_DEFINE_FLAG(OVERLAP_LOCALS, 3) 91 | GMQCC_DEFINE_FLAG(LOCAL_TEMPS, 3) 92 | GMQCC_DEFINE_FLAG(GLOBAL_TEMPS, 3) 93 | GMQCC_DEFINE_FLAG(STRIP_CONSTANT_NAMES, 1) 94 | GMQCC_DEFINE_FLAG(OVERLAP_STRINGS, 2) 95 | GMQCC_DEFINE_FLAG(CALL_STORES, 3) 96 | GMQCC_DEFINE_FLAG(VOID_RETURN, 1) 97 | GMQCC_DEFINE_FLAG(VECTOR_COMPONENTS, 1) 98 | GMQCC_DEFINE_FLAG(CONST_FOLD_DCE, 2) 99 | GMQCC_DEFINE_FLAG(CONST_FOLD, 0) /* cannot be turned off */ 100 | #endif 101 | 102 | #ifdef GMQCC_TYPE_OPTIONS 103 | GMQCC_DEFINE_FLAG(O) 104 | GMQCC_DEFINE_FLAG(OUTPUT) 105 | GMQCC_DEFINE_FLAG(QUIET) 106 | GMQCC_DEFINE_FLAG(G) 107 | GMQCC_DEFINE_FLAG(STANDARD) 108 | GMQCC_DEFINE_FLAG(DEBUG) 109 | GMQCC_DEFINE_FLAG(DUMPFIN) 110 | GMQCC_DEFINE_FLAG(DUMP) 111 | GMQCC_DEFINE_FLAG(FORCECRC) 112 | GMQCC_DEFINE_FLAG(FORCED_CRC) 113 | GMQCC_DEFINE_FLAG(PP_ONLY) 114 | GMQCC_DEFINE_FLAG(MAX_ARRAY_SIZE) 115 | GMQCC_DEFINE_FLAG(ADD_INFO) 116 | GMQCC_DEFINE_FLAG(PROGSRC) 117 | GMQCC_DEFINE_FLAG(COVERAGE) 118 | GMQCC_DEFINE_FLAG(STATE_FPS) 119 | #endif 120 | 121 | /* some cleanup so we don't have to */ 122 | #undef GMQCC_TYPE_FLAGS 123 | #undef GMQCC_TYPE_WARNS 124 | #undef GMQCC_TYPE_OPTIONS 125 | #undef GMQCC_TYPE_OPTIMIZATIONS 126 | #undef GMQCC_DEFINE_FLAG 127 | -------------------------------------------------------------------------------- /parser.h: -------------------------------------------------------------------------------- 1 | #ifndef GMQCC_PARSER_HDR 2 | #define GMQCC_PARSER_HDR 3 | #include "gmqcc.h" 4 | #include "lexer.h" 5 | #include "ast.h" 6 | 7 | #include "intrin.h" 8 | #include "fold.h" 9 | 10 | struct parser_t; 11 | 12 | #define parser_ctx(p) ((p)->lex->tok.ctx) 13 | 14 | struct parser_t { 15 | parser_t(); 16 | ~parser_t(); 17 | 18 | void remove_ast(); 19 | 20 | lex_file *lex; 21 | int tok; 22 | 23 | bool ast_cleaned; 24 | 25 | std::vector globals; 26 | std::vector fields; 27 | std::vector functions; 28 | size_t translated; 29 | 30 | /* must be deleted first, they reference immediates and values */ 31 | std::vector accessors; 32 | 33 | ast_value *nil; 34 | ast_value *reserved_version; 35 | 36 | size_t crc_globals; 37 | size_t crc_fields; 38 | 39 | ast_function *function; 40 | ht aliases; 41 | 42 | /* All the labels the function defined... 43 | * Should they be in ast_function instead? 44 | */ 45 | std::vector labels; 46 | std::vector gotos; 47 | std::vector breaks; 48 | std::vector continues; 49 | 50 | /* A list of hashtables for each scope */ 51 | std::vector variables; 52 | ht htfields; 53 | ht htglobals; 54 | std::vector typedefs; 55 | 56 | /* not to be used directly, we use the hash table */ 57 | std::vector _locals; 58 | std::vector _blocklocals; 59 | std::vector> _typedefs; 60 | std::vector _blocktypedefs; 61 | std::vector _block_ctx; 62 | 63 | /* we store the '=' operator info */ 64 | const oper_info *assign_op; 65 | 66 | /* magic values */ 67 | ast_value *const_vec[3]; 68 | 69 | /* pragma flags */ 70 | bool noref; 71 | 72 | /* collected information */ 73 | size_t max_param_count; 74 | 75 | fold m_fold; 76 | intrin m_intrin; 77 | }; 78 | 79 | 80 | /* parser.c */ 81 | char *parser_strdup (const char *str); 82 | ast_expression *parser_find_global(parser_t *parser, const char *name); 83 | 84 | #endif 85 | -------------------------------------------------------------------------------- /stat.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "gmqcc.h" 5 | 6 | /* 7 | * strdup does it's own malloc, we need to track malloc. We don't want 8 | * to overwrite malloc though, infact, we can't really hook it at all 9 | * without library specific assumptions. So we re implement strdup. 10 | */ 11 | char *stat_mem_strdup(const char *src, bool empty) { 12 | size_t len = 0; 13 | char *ptr = nullptr; 14 | 15 | if (!src) 16 | return nullptr; 17 | 18 | len = strlen(src); 19 | if ((!empty ? len : true) && (ptr = (char*)mem_a(len + 1))) { 20 | memcpy(ptr, src, len); 21 | ptr[len] = '\0'; 22 | } 23 | 24 | return ptr; 25 | } 26 | 27 | /* 28 | * The reallocate function for resizing vectors. 29 | */ 30 | void _util_vec_grow(void **a, size_t i, size_t s) { 31 | vector_t *d = nullptr; 32 | size_t m = 0; 33 | void *p = nullptr; 34 | 35 | if (*a) { 36 | d = vec_meta(*a); 37 | m = 2 * d->allocated + i; 38 | p = mem_r(d, s * m + sizeof(vector_t)); 39 | } else { 40 | m = i + 1; 41 | p = mem_a(s * m + sizeof(vector_t)); 42 | ((vector_t*)p)->used = 0; 43 | } 44 | 45 | d = (vector_t*)p; 46 | d->allocated = m; 47 | *a = d + 1; 48 | } 49 | 50 | void _util_vec_delete(void *data) { 51 | mem_d(vec_meta(data)); 52 | } 53 | 54 | /* 55 | * Hash table for generic data, based on dynamic memory allocations 56 | * all around. This is the internal interface, please look for 57 | * EXPOSED INTERFACE comment below 58 | */ 59 | struct hash_node_t { 60 | char *key; /* the key for this node in table */ 61 | void *value; /* pointer to the data as void* */ 62 | hash_node_t *next; /* next node (linked list) */ 63 | }; 64 | 65 | size_t hash(const char *key); 66 | 67 | size_t util_hthash(hash_table_t *ht, const char *key) { 68 | return hash(key) % ht->size; 69 | } 70 | 71 | static hash_node_t *_util_htnewpair(const char *key, void *value) { 72 | hash_node_t *node; 73 | if (!(node = (hash_node_t*)mem_a(sizeof(hash_node_t)))) 74 | return nullptr; 75 | 76 | if (!(node->key = util_strdupe(key))) { 77 | mem_d(node); 78 | return nullptr; 79 | } 80 | 81 | node->value = value; 82 | node->next = nullptr; 83 | 84 | return node; 85 | } 86 | 87 | /* 88 | * EXPOSED INTERFACE for the hashtable implementation 89 | * util_htnew(size) -- to make a new hashtable 90 | * util_htset(table, key, value, sizeof(value)) -- to set something in the table 91 | * util_htget(table, key) -- to get something from the table 92 | * util_htdel(table) -- to delete the table 93 | */ 94 | hash_table_t *util_htnew(size_t size) { 95 | hash_table_t *hashtable = nullptr; 96 | 97 | if (size < 1) 98 | return nullptr; 99 | 100 | if (!(hashtable = (hash_table_t*)mem_a(sizeof(hash_table_t)))) 101 | return nullptr; 102 | 103 | if (!(hashtable->table = (hash_node_t**)mem_a(sizeof(hash_node_t*) * size))) { 104 | mem_d(hashtable); 105 | return nullptr; 106 | } 107 | 108 | hashtable->size = size; 109 | memset(hashtable->table, 0, sizeof(hash_node_t*) * size); 110 | 111 | return hashtable; 112 | } 113 | 114 | void util_htseth(hash_table_t *ht, const char *key, size_t bin, void *value) { 115 | hash_node_t *newnode = nullptr; 116 | hash_node_t *next = nullptr; 117 | hash_node_t *last = nullptr; 118 | 119 | next = ht->table[bin]; 120 | 121 | while (next && next->key && strcmp(key, next->key) > 0) 122 | last = next, next = next->next; 123 | 124 | /* already in table, do a replace */ 125 | if (next && next->key && strcmp(key, next->key) == 0) { 126 | next->value = value; 127 | } else { 128 | /* not found, grow a pair man :P */ 129 | newnode = _util_htnewpair(key, value); 130 | if (next == ht->table[bin]) { 131 | newnode->next = next; 132 | ht->table[bin] = newnode; 133 | } else if (!next) { 134 | last->next = newnode; 135 | } else { 136 | newnode->next = next; 137 | last->next = newnode; 138 | } 139 | } 140 | } 141 | 142 | void util_htset(hash_table_t *ht, const char *key, void *value) { 143 | util_htseth(ht, key, util_hthash(ht, key), value); 144 | } 145 | 146 | void *util_htgeth(hash_table_t *ht, const char *key, size_t bin) { 147 | hash_node_t *pair = ht->table[bin]; 148 | 149 | while (pair && pair->key && strcmp(key, pair->key) > 0) 150 | pair = pair->next; 151 | 152 | if (!pair || !pair->key || strcmp(key, pair->key) != 0) 153 | return nullptr; 154 | 155 | return pair->value; 156 | } 157 | 158 | void *util_htget(hash_table_t *ht, const char *key) { 159 | return util_htgeth(ht, key, util_hthash(ht, key)); 160 | } 161 | 162 | void *code_util_str_htgeth(hash_table_t *ht, const char *key, size_t bin); 163 | void *code_util_str_htgeth(hash_table_t *ht, const char *key, size_t bin) { 164 | hash_node_t *pair; 165 | size_t len, keylen; 166 | int cmp; 167 | 168 | keylen = strlen(key); 169 | 170 | pair = ht->table[bin]; 171 | while (pair && pair->key) { 172 | len = strlen(pair->key); 173 | if (len < keylen) { 174 | pair = pair->next; 175 | continue; 176 | } 177 | if (keylen == len) { 178 | cmp = strcmp(key, pair->key); 179 | if (cmp == 0) 180 | return pair->value; 181 | if (cmp < 0) 182 | return nullptr; 183 | pair = pair->next; 184 | continue; 185 | } 186 | cmp = strcmp(key, pair->key + len - keylen); 187 | if (cmp == 0) { 188 | uintptr_t up = (uintptr_t)pair->value; 189 | up += len - keylen; 190 | return (void*)up; 191 | } 192 | pair = pair->next; 193 | } 194 | return nullptr; 195 | } 196 | 197 | /* 198 | * Free all allocated data in a hashtable, this is quite the amount 199 | * of work. 200 | */ 201 | void util_htrem(hash_table_t *ht, void (*callback)(void *data)) { 202 | size_t i = 0; 203 | 204 | for (; i < ht->size; ++i) { 205 | hash_node_t *n = ht->table[i]; 206 | hash_node_t *p; 207 | 208 | /* free in list */ 209 | while (n) { 210 | if (n->key) 211 | mem_d(n->key); 212 | if (callback) 213 | callback(n->value); 214 | p = n; 215 | n = p->next; 216 | mem_d(p); 217 | } 218 | 219 | } 220 | /* free table */ 221 | mem_d(ht->table); 222 | mem_d(ht); 223 | } 224 | 225 | void util_htrmh(hash_table_t *ht, const char *key, size_t bin, void (*cb)(void*)) { 226 | hash_node_t **pair = &ht->table[bin]; 227 | hash_node_t *tmp; 228 | 229 | while (*pair && (*pair)->key && strcmp(key, (*pair)->key) > 0) 230 | pair = &(*pair)->next; 231 | 232 | tmp = *pair; 233 | if (!tmp || !tmp->key || strcmp(key, tmp->key) != 0) 234 | return; 235 | 236 | if (cb) 237 | (*cb)(tmp->value); 238 | 239 | *pair = tmp->next; 240 | mem_d(tmp->key); 241 | mem_d(tmp); 242 | } 243 | 244 | void util_htrm(hash_table_t *ht, const char *key, void (*cb)(void*)) { 245 | util_htrmh(ht, key, util_hthash(ht, key), cb); 246 | } 247 | 248 | void util_htdel(hash_table_t *ht) { 249 | util_htrem(ht, nullptr); 250 | } 251 | -------------------------------------------------------------------------------- /tests/accumulate.qc: -------------------------------------------------------------------------------- 1 | #define ACCUMULATE_FUNCTION(FUNC) \ 2 | [[accumulate]] void FUNC () 3 | 4 | ACCUMULATE_FUNCTION(foo) { 5 | print("hello "); 6 | } 7 | 8 | ACCUMULATE_FUNCTION(foo) { 9 | print("accumulation "); 10 | } 11 | 12 | ACCUMULATE_FUNCTION(foo) { 13 | print("world\n"); 14 | } 15 | 16 | void main() { 17 | foo(); 18 | } 19 | -------------------------------------------------------------------------------- /tests/accumulate.tmpl: -------------------------------------------------------------------------------- 1 | I: accumulate.qc 2 | D: test function accumulation 3 | T: -execute 4 | C: -std=gmqcc -fftepp 5 | M: hello accumulation world 6 | -------------------------------------------------------------------------------- /tests/aliases.qc: -------------------------------------------------------------------------------- 1 | float alias_1 = 3.14; 2 | void alias_2() { 3 | print("alias_2\n"); 4 | } 5 | 6 | [[alias("alias_2")]] void alias_2_aliased(); 7 | [[alias("alias_1")]] float alias_1_aliased; 8 | 9 | 10 | // alias to an alias? 11 | vector alias_3; 12 | [[alias("alias_3")]] vector alias_3_aliased; 13 | 14 | // expected output 15 | // alias_2 16 | // 3.14 17 | void main() { 18 | alias_2_aliased(); 19 | 20 | alias_3_aliased= '1 2 3'; 21 | 22 | print( 23 | ftos( 24 | alias_1_aliased 25 | ), 26 | "\n" 27 | ); 28 | 29 | print( 30 | "x ", ftos(alias_3_aliased_x), "\n", 31 | "y ", ftos(alias_3_aliased_y), "\n", 32 | "z ", ftos(alias_3_aliased_z), "\n" 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /tests/aliases.tmpl: -------------------------------------------------------------------------------- 1 | I: aliases.qc 2 | D: test aliases 3 | T: -execute 4 | C: -std=gmqcc 5 | M: alias_2 6 | M: 3.14 7 | M: x 1 8 | M: y 2 9 | M: z 3 10 | -------------------------------------------------------------------------------- /tests/arithexcept.qc: -------------------------------------------------------------------------------- 1 | const float huge = 340282346638528859811704183484516925440.000000; // FLT_MAX 2 | 3 | #ifdef DIVBYZERO 4 | const float a = 1.0 / 0.0; 5 | #endif 6 | 7 | #ifdef OVERFLOW 8 | const float a = huge * huge; 9 | #endif 10 | 11 | #ifdef UNDERFLOW 12 | const float a = 1 / huge; 13 | #endif 14 | -------------------------------------------------------------------------------- /tests/arithexcept.tmpl: -------------------------------------------------------------------------------- 1 | I: arithexcept.qc 2 | D: arithmetic exceptions (divide by zero) 3 | T: -fail 4 | C: -std=fteqcc -farithmetic-exceptions -DDIVBYZERO 5 | -------------------------------------------------------------------------------- /tests/arithexcept_of.tmpl: -------------------------------------------------------------------------------- 1 | I: arithexcept.qc 2 | D: arithmetic exceptions (overflow) 3 | T: -fail 4 | C: -std=fteqcc -farithmetic-exceptions -DOVERFLOW 5 | -------------------------------------------------------------------------------- /tests/arithexcept_uf.tmpl: -------------------------------------------------------------------------------- 1 | I: arithexcept.qc 2 | D: arithmetic exceptions (underflow) 3 | T: -fail 4 | C: -std=fteqcc -farithmetic-exceptions -DUNDERFLOW 5 | -------------------------------------------------------------------------------- /tests/arrays.qc: -------------------------------------------------------------------------------- 1 | float glob[7]; 2 | 3 | .float above; 4 | .float flds[6]; 5 | .float below; 6 | 7 | void main() { 8 | float loc[6]; 9 | 10 | loc[0] = 1000; 11 | loc[1] = 1100; 12 | loc[2] = 1200; 13 | loc[3] = 1300; 14 | loc[4] = 1400; 15 | loc[5] = 1500; 16 | 17 | float i; 18 | 19 | for (i = 0; i < 6; i += 1) 20 | loc[i] += 1; 21 | for (i = 0; i < 5; i += 1) 22 | print(ftos(loc[i]), " "); 23 | print(ftos(loc[i]), "\n"); 24 | 25 | glob[0] = 1000; 26 | glob[1] = 1100; 27 | glob[2] = 1200; 28 | glob[3] = 1300; 29 | glob[4] = 1400; 30 | glob[5] = 1500; 31 | glob[6] = 1600; 32 | for (i = 0; i < 7; i += 1) 33 | glob[i] += 1; 34 | for (i = 0; i < 6; i += 1) 35 | print(ftos(glob[i]), " "); 36 | print(ftos(glob[i]), "\n"); 37 | 38 | entity e = spawn(); 39 | e.above = 7777; 40 | e.below = 9999; 41 | e.flds[0] = 1000; 42 | e.flds[1] = 1100; 43 | e.flds[2] = 1200; 44 | e.flds[3] = 1300; 45 | e.flds[4] = 1400; 46 | e.flds[5] = 1500; 47 | for (i = 0; i < 6; i += 1) 48 | e.flds[i] += 1; 49 | for (i = 0; i < 5; i += 1) 50 | print(ftos(e.flds[i]), " "); 51 | print(ftos(e.flds[i]), "\n"); 52 | } 53 | -------------------------------------------------------------------------------- /tests/arrays.tmpl: -------------------------------------------------------------------------------- 1 | I: arrays2.qc 2 | D: initialized arrays 3 | T: -execute 4 | C: -std=fteqcc 5 | M: 10 20 30 40 50 60 70 6 | M: 100 200 300 400 500 600 0 7 | M: Hello World 8 | -------------------------------------------------------------------------------- /tests/arrays2.qc: -------------------------------------------------------------------------------- 1 | float glob1[7] = { 10, 20, 30, 40, 50, 60, 70 }; 2 | float glob2[7] = { 100, 200, 300, 400, 500, 600 }; 3 | string globs[] = { "Hello ", "World" }; 4 | 5 | void main() { 6 | float i; 7 | print(ftos(glob1[0])); 8 | for (i = 1; i != 7; ++i) 9 | print(" ", ftos(glob1[i])); 10 | print("\n"); 11 | 12 | print(ftos(glob2[0])); 13 | for (i = 1; i != 7; ++i) 14 | print(" ", ftos(glob2[i])); 15 | print("\n"); 16 | 17 | print(globs[0], globs[1], "\n"); 18 | } 19 | -------------------------------------------------------------------------------- /tests/arrays2.tmpl: -------------------------------------------------------------------------------- 1 | I: arrays.qc 2 | D: array accessors and functionality 3 | T: -execute 4 | C: -std=fteqcc 5 | M: 1001 1101 1201 1301 1401 1501 6 | M: 1001 1101 1201 1301 1401 1501 1601 7 | M: 1001 1101 1201 1301 1401 1501 8 | -------------------------------------------------------------------------------- /tests/bitnot.qc: -------------------------------------------------------------------------------- 1 | void main() { 2 | float a; a = 1; 3 | float b; b = 1; 4 | float c; c = 1; 5 | float d; d = 1; 6 | vector e; e = '1 1 1'; 7 | vector f; f = '1 1 1'; 8 | 9 | #ifdef __STD_FTEQCC__ 10 | a &~= 1; // 0 11 | #else 12 | a &= ~1; // 0 13 | #endif 14 | #ifdef __STD_GMQCC__ 15 | b &= ~1; // 0 16 | c &= ~d; // 0 17 | #else 18 | b &~= 1; // 0 19 | c &~= 1; // 0 20 | #endif 21 | #ifdef __STD_FTEQCC__ 22 | f &~= e; // '0 0 0' 23 | #else 24 | f &= ~e; // '0 0 0' 25 | #endif 26 | #ifdef __STD_GMQCC__ 27 | e &= ~e; // '0 0 0' 28 | #else 29 | e &~= e; // '0 0 0' 30 | #endif 31 | 32 | print("a: ", ftos(a), "\nb: ", 33 | ftos(b), "\nc: ", 34 | ftos(c), "\n"); 35 | print("e: ", vtos(e), "\n"); 36 | print("f: ", vtos(f), "\n"); 37 | } 38 | -------------------------------------------------------------------------------- /tests/bitnot.tmpl: -------------------------------------------------------------------------------- 1 | # used to test the builtins 2 | I: bitnot.qc 3 | D: test bitwise not operators (fteqcc operators) 4 | T: -execute 5 | C: -std=fteqcc 6 | E: $null 7 | M: a: 0 8 | M: b: 0 9 | M: c: 0 10 | M: e: '0 0 0' 11 | M: f: '0 0 0' 12 | -------------------------------------------------------------------------------- /tests/bitnotgmqcc.tmpl: -------------------------------------------------------------------------------- 1 | # used to test the builtins 2 | I: bitnot.qc 3 | D: test bitwise not operators (gmqcc operators) 4 | T: -execute 5 | C: -std=gmqcc -fftepp 6 | E: $null 7 | M: a: 0 8 | M: b: 0 9 | M: c: 0 10 | M: e: '0 0 0' 11 | M: f: '0 0 0' 12 | -------------------------------------------------------------------------------- /tests/break.qc: -------------------------------------------------------------------------------- 1 | void test(float brkat, float contat) { 2 | float i; 3 | 4 | for (i = 0; i < 10; i += 1) { 5 | if (i == contat) { 6 | print("ct "); 7 | continue; 8 | } 9 | print(ftos(i), " "); 10 | if (i == brkat) { 11 | print("brk "); 12 | break; 13 | } 14 | } 15 | print("end\n"); 16 | } 17 | 18 | void main() { 19 | test(-1, -1); 20 | test( 3, -1); 21 | test(-1, 3); 22 | test( 5, 2); 23 | } 24 | -------------------------------------------------------------------------------- /tests/break.tmpl: -------------------------------------------------------------------------------- 1 | I: break.qc 2 | D: test break and continue 3 | T: -execute 4 | C: -std=fteqcc 5 | M: 0 1 2 3 4 5 6 7 8 9 end 6 | M: 0 1 2 3 brk end 7 | M: 0 1 2 ct 4 5 6 7 8 9 end 8 | M: 0 1 ct 3 4 5 brk end 9 | -------------------------------------------------------------------------------- /tests/builtin.qc: -------------------------------------------------------------------------------- 1 | void() main = { 2 | print("hello world"); 3 | } 4 | -------------------------------------------------------------------------------- /tests/builtin.tmpl: -------------------------------------------------------------------------------- 1 | # used to test the builtins 2 | I: builtin.qc 3 | D: test builtin functions 4 | T: -execute 5 | C: -std=gmqcc 6 | E: $null 7 | M: hello world 8 | -------------------------------------------------------------------------------- /tests/calls.qc: -------------------------------------------------------------------------------- 1 | float(float x, float y, float z) sum = { 2 | return x + y + z; 3 | }; 4 | 5 | void(float a, float b, float c) main = { 6 | local float f; 7 | f = sum(sum(a, sum(a, b, c), c), 8 | sum(sum(sum(a, b, c), b, sum(a, b, c)), b, sum(a, b, sum(a, b, c))), 9 | sum(sum(a, b, c), b, c)); 10 | print(ftos(f), "\n"); 11 | 12 | }; 13 | -------------------------------------------------------------------------------- /tests/calls.tmpl: -------------------------------------------------------------------------------- 1 | I: calls.qc 2 | D: test calls 3 | T: -execute 4 | C: -std=gmqcc 5 | E: -float 100 -float 200 -float 300 6 | M: 4600 7 | -------------------------------------------------------------------------------- /tests/correct-logic-1-s.tmpl: -------------------------------------------------------------------------------- 1 | I: correct-logic.qc 2 | D: vector logic flags 3 | T: -execute 4 | C: -std=fteqcc -fshort-logic 5 | M: ! & | i N 6 | M: 0, 0 -> 1 0 0 0 1 7 | M: 0, x -> 1 0 1 0 1 8 | M: x, 0 -> 0 0 1 1 0 9 | M: x, x -> 0 1 1 1 0 10 | M: 0, y -> 1 0 0 0 1 11 | M: y, 0 -> 0 0 0 0 1 12 | M: y, y -> 0 0 0 0 1 13 | -------------------------------------------------------------------------------- /tests/correct-logic-1.tmpl: -------------------------------------------------------------------------------- 1 | I: correct-logic.qc 2 | D: vector logic flags 3 | T: -execute 4 | C: -std=fteqcc 5 | M: ! & | i N 6 | M: 0, 0 -> 1 0 0 0 1 7 | M: 0, x -> 1 0 1 0 1 8 | M: x, 0 -> 0 0 1 1 0 9 | M: x, x -> 0 1 1 1 0 10 | M: 0, y -> 1 0 0 0 1 11 | M: y, 0 -> 0 0 0 0 1 12 | M: y, y -> 0 0 0 0 1 13 | -------------------------------------------------------------------------------- /tests/correct-logic-2-s.tmpl: -------------------------------------------------------------------------------- 1 | I: correct-logic.qc 2 | D: vector logic flags 3 | T: -execute 4 | C: -std=fteqcc -fcorrect-logic -fshort-logic 5 | M: ! & | i N 6 | M: 0, 0 -> 1 0 0 0 1 7 | M: 0, x -> 1 0 1 0 1 8 | M: x, 0 -> 0 0 1 1 0 9 | M: x, x -> 0 1 1 1 0 10 | M: 0, y -> 1 0 1 0 1 11 | M: y, 0 -> 0 0 1 1 0 12 | M: y, y -> 0 1 1 1 0 13 | -------------------------------------------------------------------------------- /tests/correct-logic-2.tmpl: -------------------------------------------------------------------------------- 1 | I: correct-logic.qc 2 | D: vector logic flags 3 | T: -execute 4 | C: -std=fteqcc -fcorrect-logic 5 | M: ! & | i N 6 | M: 0, 0 -> 1 0 0 0 1 7 | M: 0, x -> 1 0 1 0 1 8 | M: x, 0 -> 0 0 1 1 0 9 | M: x, x -> 0 1 1 1 0 10 | M: 0, y -> 1 0 1 0 1 11 | M: y, 0 -> 0 0 1 1 0 12 | M: y, y -> 0 1 1 1 0 13 | -------------------------------------------------------------------------------- /tests/correct-logic.qc: -------------------------------------------------------------------------------- 1 | float test_s_not (vector s) { return !s; } 2 | float test_s_and (vector s, vector t) { return s && t; } 3 | float test_s_or (vector s, vector t) { return s || t; } 4 | float test_s_if (vector s) { if (s) return 1; return 0; } 5 | float test_s_ifnot(vector s) { if not (s) return 1; return 0; } 6 | 7 | void test(vector s, vector t) { 8 | print(ftos(!!test_s_not (s)), " "); 9 | print(ftos(!!test_s_and (s, t)), " "); 10 | print(ftos(!!test_s_or (s, t)), " "); 11 | print(ftos(!!test_s_if (s)), " "); 12 | print(ftos(!!test_s_ifnot(s)), "\n"); 13 | } 14 | 15 | void main() { 16 | print(" ! & | i N\n"); 17 | print("0, 0 -> "); test('0 0 0', '0 0 0'); 18 | print("0, x -> "); test('0 0 0', '1 0 0'); 19 | print("x, 0 -> "); test('1 0 0', '0 0 0'); 20 | print("x, x -> "); test('1 0 0', '1 0 0'); 21 | print("0, y -> "); test('0 0 0', '0 1 0'); 22 | print("y, 0 -> "); test('0 1 0', '0 0 0'); 23 | print("y, y -> "); test('0 1 0', '0 1 0'); 24 | } 25 | -------------------------------------------------------------------------------- /tests/correct-vs-short-1.tmpl: -------------------------------------------------------------------------------- 1 | I: correct-vs-short.qc 2 | D: correct-logic vs short-logic without perl-logic 3 | T: -execute 4 | C: -std=fteqcc 5 | M: X & | B 6 | M: 0 0 0, 0 0 0 :: 0 0 0 7 | M: 0 0 0, 5 0 0 :: 0 2 1 8 | M: 5 0 0, 0 0 0 :: 0 2 1 9 | M: 5 0 0, 5 0 0 :: 2 2 2 10 | M: Y & | B 11 | M: 0 0 0, 0 0 0 :: 0 0 0 12 | M: 0 0 0, 0 5 0 :: 0 0 0 13 | M: 0 5 0, 0 0 0 :: 0 0 0 14 | M: 0 5 0, 0 5 0 :: 0 0 0 15 | -------------------------------------------------------------------------------- /tests/correct-vs-short-2.tmpl: -------------------------------------------------------------------------------- 1 | I: correct-vs-short.qc 2 | D: correct-logic vs short-logic without perl-logic 3 | T: -execute 4 | C: -std=fteqcc -fcorrect-logic 5 | M: X & | B 6 | M: 0 0 0, 0 0 0 :: 0 0 0 7 | M: 0 0 0, 5 0 0 :: 0 2 1 8 | M: 5 0 0, 0 0 0 :: 0 2 1 9 | M: 5 0 0, 5 0 0 :: 2 2 2 10 | M: Y & | B 11 | M: 0 0 0, 0 0 0 :: 0 0 0 12 | M: 0 0 0, 0 5 0 :: 0 2 1 13 | M: 0 5 0, 0 0 0 :: 0 2 1 14 | M: 0 5 0, 0 5 0 :: 2 2 2 15 | -------------------------------------------------------------------------------- /tests/correct-vs-short-3.tmpl: -------------------------------------------------------------------------------- 1 | I: correct-vs-short.qc 2 | D: correct-logic vs short-logic without perl-logic 3 | T: -execute 4 | C: -std=fteqcc -fcorrect-logic -fshort-logic 5 | M: X & | B 6 | M: 0 0 0, 0 0 0 :: 0 0 0 7 | M: 0 0 0, 5 0 0 :: 0 2 1 8 | M: 5 0 0, 0 0 0 :: 0 2 1 9 | M: 5 0 0, 5 0 0 :: 2 2 2 10 | M: Y & | B 11 | M: 0 0 0, 0 0 0 :: 0 0 0 12 | M: 0 0 0, 0 5 0 :: 0 2 1 13 | M: 0 5 0, 0 0 0 :: 0 2 1 14 | M: 0 5 0, 0 5 0 :: 2 2 2 15 | -------------------------------------------------------------------------------- /tests/correct-vs-short.qc: -------------------------------------------------------------------------------- 1 | void test(vector a, vector b) { 2 | print(ftos((a && b) + (a && b)), " "); 3 | print(ftos((a || b) + (a || b)), " "); 4 | print(ftos((a && b) + (a || b)), "\n"); 5 | } 6 | 7 | void main() { 8 | print("X & | B\n"); 9 | print("0 0 0, 0 0 0 :: "); test('0 0 0', '0 0 0'); 10 | print("0 0 0, 5 0 0 :: "); test('0 0 0', '5 0 0'); 11 | print("5 0 0, 0 0 0 :: "); test('5 0 0', '0 0 0'); 12 | print("5 0 0, 5 0 0 :: "); test('5 0 0', '5 0 0'); 13 | print("Y & | B\n"); 14 | print("0 0 0, 0 0 0 :: "); test('0 0 0', '0 0 0'); 15 | print("0 0 0, 0 5 0 :: "); test('0 0 0', '0 5 0'); 16 | print("0 5 0, 0 0 0 :: "); test('0 5 0', '0 0 0'); 17 | print("0 5 0, 0 5 0 :: "); test('0 5 0', '0 5 0'); 18 | } 19 | -------------------------------------------------------------------------------- /tests/defs.qh: -------------------------------------------------------------------------------- 1 | // builtins for the standalone qcvm included with gmqcc 2 | // in exec.c These should be updated to reflect the new 3 | // builtins. I no event shall you even consider adding 4 | // these individually per test. 5 | 6 | void (string str, ...) print = #1; 7 | string (float val) ftos = #2; 8 | entity () spawn = #3; 9 | void (entity ent) kill = #4; 10 | string (vector vec) vtos = #5; 11 | void (string str) error = #6; 12 | float (vector vec) vlen = #7; 13 | string (entity ent) etos = #8; 14 | float (string str) stof = #9; 15 | string (...) strcat = #10; 16 | float (string str1, string str2) strcmp = #11; 17 | vector (vector vec) normalize = #12; 18 | float (float val) sqrt = #13; 19 | float (float val) floor = #14; 20 | float (float val1, float val2) pow = #15; 21 | vector (string str) stov = #16; 22 | -------------------------------------------------------------------------------- /tests/dots.qc: -------------------------------------------------------------------------------- 1 | entity self; 2 | .float f; 3 | ..float fp; 4 | ...float fpp; 5 | 6 | void try(entity e, ...float pp) { 7 | print("and: ", ftos( e.(e.(e.pp)) ), "\n"); 8 | } 9 | 10 | typedef float Float; 11 | 12 | void try2(entity e, ...Float pp) { 13 | print("and: ", ftos( e.(e.(e.pp)) ), "\n"); 14 | } 15 | 16 | // whereas the varargs are tested in vararg tests 17 | 18 | void main() { 19 | self = spawn(); 20 | self.f = 123; 21 | self.fp = f; 22 | self.fpp = fp; 23 | print(ftos( self.(self.fp) ), "\n"); 24 | print(ftos( self.(self.(self.fpp)) ), "\n"); 25 | try(self, fpp); 26 | try2(self, fpp); 27 | } 28 | -------------------------------------------------------------------------------- /tests/dots.tmpl: -------------------------------------------------------------------------------- 1 | I: dots.qc 2 | D: TOKEN_DOTS disambiguation 3 | T: -execute 4 | C: -std=fteqcc 5 | M: 123 6 | M: 123 7 | M: and: 123 8 | M: and: 123 9 | -------------------------------------------------------------------------------- /tests/enum.qc: -------------------------------------------------------------------------------- 1 | enum { 2 | // this behaviour is confusing, but I like that 3 | // we support it. 4 | __ = (__ - 1), 5 | A = (__ + 1), 6 | 7 | B, 8 | C 9 | }; 10 | 11 | enum { 12 | D = C + B, 13 | E = C + C, 14 | F = C + D, 15 | }; 16 | 17 | enum { 18 | G = (B + F), H = (C + F), 19 | I = (D + F), J = (B + I) 20 | }; 21 | enum { 22 | K = A + B - C + D - E + F * 23 | G - H + I - J + A - B - 24 | J + A, 25 | L, 26 | M, 27 | N 28 | }; 29 | 30 | enum : flag { 31 | F1, /* = 1 << 1 */ 32 | F2, /* = 1 << 2 */ 33 | F3 /* = 1 << 3 */ 34 | }; 35 | 36 | /* reversed enumeration */ 37 | enum : reverse { 38 | R1, // 3 39 | R2, // 2 40 | R3, // 1 41 | R4 // 0 42 | }; 43 | 44 | void main() { 45 | print(ftos(A), "\n"); 46 | print(ftos(B), "\n"); 47 | print(ftos(C), "\n"); 48 | print(ftos(D), "\n"); 49 | print(ftos(E), "\n"); 50 | print(ftos(F), "\n"); 51 | print(ftos(G), "\n"); 52 | print(ftos(H), "\n"); 53 | print(ftos(I), "\n"); 54 | print(ftos(J), "\n"); 55 | print(ftos(K), "\n"); 56 | print(ftos(L), "\n"); 57 | print(ftos(M), "\n"); 58 | print(ftos(N), "\n"); 59 | 60 | print(ftos(F1), "\n"); 61 | print(ftos(F2), "\n"); 62 | print(ftos(F3), "\n"); 63 | 64 | print(ftos(R1), "\n"); 65 | print(ftos(R2), "\n"); 66 | print(ftos(R3), "\n"); 67 | print(ftos(R4), "\n"); 68 | }; 69 | -------------------------------------------------------------------------------- /tests/enum.tmpl: -------------------------------------------------------------------------------- 1 | I: enum.qc 2 | D: enumerations 3 | T: -execute 4 | C: -std=fteqcc 5 | M: 0 6 | M: 1 7 | M: 2 8 | M: 3 9 | M: 4 10 | M: 5 11 | M: 6 12 | M: 7 13 | M: 8 14 | M: 9 15 | M: 10 16 | M: 11 17 | M: 12 18 | M: 13 19 | M: 2 20 | M: 4 21 | M: 8 22 | M: 3 23 | M: 2 24 | M: 1 25 | M: 0 26 | -------------------------------------------------------------------------------- /tests/equality.qc: -------------------------------------------------------------------------------- 1 | void(float a, float b) main = { 2 | if (a == b) print("eq,"); 3 | if (a != b) print("ne,"); 4 | if (a > b) print("gt,"); 5 | if (a < b) print("lt,"); 6 | if (a >= b) print("ge,"); 7 | if (a <= b) print("le,"); 8 | }; 9 | -------------------------------------------------------------------------------- /tests/equality.tmpl: -------------------------------------------------------------------------------- 1 | I: equality.qc 2 | D: test equality 3 | T: -execute 4 | C: -std=gmqcc 5 | E: -float 100 -float 200 6 | M: ne,lt,le, 7 | -------------------------------------------------------------------------------- /tests/exponentiation.qc: -------------------------------------------------------------------------------- 1 | void main() { 2 | float hundy = __builtin_pow(10, 2); // 10^2 == 100 3 | print(ftos(hundy), "\n"); // prints: 100 4 | 5 | hundy = pow(10, 2); 6 | print(ftos(hundy), "\n"); 7 | 8 | hundy -= 90; // 100-90 = 10 9 | print(ftos(hundy ** 2), "\n"); // prints: 100 10 | print(ftos(pow(hundy, 2)), "\n"); // prints: 100 11 | 12 | hundy = 10.0f; 13 | print(ftos(__builtin_exp(hundy)), "\n"); // prints: 22026.5 14 | } 15 | -------------------------------------------------------------------------------- /tests/exponentiation.tmpl: -------------------------------------------------------------------------------- 1 | # used to test the builtins 2 | I: exponentiation.qc 3 | D: test exponentiation operator and __builtin_pow 4 | T: -execute 5 | C: -std=gmqcc 6 | E: $null 7 | M: 100 8 | M: 100 9 | M: 100 10 | M: 100 11 | M: 22026.5 12 | -------------------------------------------------------------------------------- /tests/exprforbuiltins.qc: -------------------------------------------------------------------------------- 1 | /* empty line required */ 2 | void print(string, ...) = #__LINE__ - 1; 3 | 4 | 5 | void main(string input) { 6 | print(input, "\n"); 7 | } 8 | -------------------------------------------------------------------------------- /tests/exprforbuiltins.tmpl: -------------------------------------------------------------------------------- 1 | I: exprforbuiltins.qc 2 | D: expressions for builtins 3 | T: -execute 4 | C: -std=gmqcc -fftepp -fftepp-predefs 5 | F: -no-defs 6 | E: -string test 7 | M: test 8 | -------------------------------------------------------------------------------- /tests/fieldfuncs.qc: -------------------------------------------------------------------------------- 1 | .float field; 2 | 3 | .float getfield() { 4 | return field; 5 | } 6 | 7 | void() main = { 8 | entity e = spawn(); 9 | e.field = 42; 10 | print(ftos(e.(getfield())), "\n"); 11 | .float memptr = getfield(); 12 | print(ftos(e.memptr), "\n"); 13 | } 14 | -------------------------------------------------------------------------------- /tests/fieldfuncs.tmpl: -------------------------------------------------------------------------------- 1 | I: fieldfuncs.qc 2 | D: test fields with functions 3 | T: -execute 4 | C: -std=fte 5 | M: 42 6 | M: 42 7 | -------------------------------------------------------------------------------- /tests/fieldparams.qc: -------------------------------------------------------------------------------- 1 | .string a; 2 | .string b; 3 | ..string ps; 4 | 5 | void(entity e, .string s) callout = { 6 | print(e.s, "\n"); 7 | }; 8 | 9 | void() main = { 10 | local entity e; 11 | e = spawn(); 12 | e.a = "foo"; 13 | e.b = "bar"; 14 | callout(e, b); 15 | e.ps = a; 16 | print(e.(e.ps), "\n"); 17 | }; 18 | -------------------------------------------------------------------------------- /tests/fieldparams.tmpl: -------------------------------------------------------------------------------- 1 | I: fieldparams.qc 2 | D: test field paramaters 3 | T: -execute 4 | C: -std=qcc 5 | E: $null 6 | M: bar 7 | M: foo 8 | -------------------------------------------------------------------------------- /tests/forloop.qc: -------------------------------------------------------------------------------- 1 | void main() { 2 | float j; 3 | for (j = 0; j < 2; ++j) 4 | print("+"); 5 | 6 | for (float i = 0; i < 5; ++i) 7 | print("*"); 8 | 9 | for (;;) { 10 | print("\n"); 11 | break; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/forloop.tmpl: -------------------------------------------------------------------------------- 1 | I: forloop.qc 2 | D: test for loops 3 | T: -execute 4 | C: -std=gmqcc 5 | M: ++***** 6 | -------------------------------------------------------------------------------- /tests/framemacro.qc: -------------------------------------------------------------------------------- 1 | $frame frame1 frame2 2 | 3 | float time; 4 | entity self; 5 | .float frame; 6 | .float nextthink; 7 | .void() think; 8 | 9 | // Mixing syntax, = is optional. 10 | void frame1_func_mixed_no_assign() [$frame1, frame2_func_mixed_no_assign] {} 11 | void frame2_func_mixed_no_assign() [$frame2, frame2_func_mixed_no_assign] {} 12 | 13 | void frame1_func_mixed() =[$frame1, frame2_func_mixed] {} 14 | void frame2_func_mixed() =[$frame2, frame2_func_mixed] {} 15 | 16 | void() frame1_func_old =[$frame1, frame2_func_old] {} 17 | void() frame2_func_old =[$frame2, frame2_func_old] {} 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/framemacro.tmpl: -------------------------------------------------------------------------------- 1 | I: framemacro.qc 2 | D: test frame macros 3 | T: -compile 4 | C: -std=gmqcc -------------------------------------------------------------------------------- /tests/functions-as-params.qc: -------------------------------------------------------------------------------- 1 | string() getter = { 2 | return "correct"; 3 | }; 4 | 5 | void(string() f) printer = { 6 | print(f(), "\n"); 7 | }; 8 | 9 | void() main = { 10 | printer(getter); 11 | }; 12 | -------------------------------------------------------------------------------- /tests/functions-as-params.tmpl: -------------------------------------------------------------------------------- 1 | I: functions-as-params.qc 2 | D: test functions as paramaters 3 | T: -execute 4 | C: -std=gmqcc 5 | E: $null 6 | M: correct 7 | -------------------------------------------------------------------------------- /tests/goto.qc: -------------------------------------------------------------------------------- 1 | // correct execution order: 2 | // label_3 3 | // label_2 4 | // label_4 5 | // label_3 6 | // label_1 7 | // label_5 8 | void main() { 9 | float x = 1; 10 | float y = 2; 11 | 12 | goto label_3; 13 | 14 | :label_1; print("label_1", "\n"); goto label_5; 15 | :label_2; print("label_2", "\n"); goto label_4; 16 | :label_3; print("label_3", "\n"); 17 | 18 | // will goto label_2 19 | goto (x == y) ? label_1 : label_2; 20 | 21 | :label_4; print("label_4", "\n"); 22 | { 23 | x = 1; 24 | y = 1; 25 | 26 | // will goto label_1 27 | // then goes label_5 28 | goto label_3; 29 | } 30 | 31 | :label_5; print("label_5", "\n"); 32 | } 33 | -------------------------------------------------------------------------------- /tests/goto.tmpl: -------------------------------------------------------------------------------- 1 | I: goto.qc 2 | D: test goto (both normal and computed) 3 | T: -execute 4 | C: -std=gmqcc 5 | M: label_3 6 | M: label_2 7 | M: label_4 8 | M: label_3 9 | M: label_1 10 | M: label_5 11 | -------------------------------------------------------------------------------- /tests/ifs.qc: -------------------------------------------------------------------------------- 1 | void(float c) main = { 2 | if (c == 1) 3 | print("One\n"); 4 | else if (c == 2) 5 | print("Two\n"); 6 | else if (c == 3) 7 | print("Three\n"); 8 | else 9 | print("Else\n"); 10 | }; 11 | -------------------------------------------------------------------------------- /tests/ifs.tmpl: -------------------------------------------------------------------------------- 1 | I: ifs.qc 2 | D: test if statement 3 | T: -execute 4 | C: -std=gmqcc 5 | E: -float 2 6 | M: Two 7 | -------------------------------------------------------------------------------- /tests/inexact-local.qc: -------------------------------------------------------------------------------- 1 | void main() { 2 | const float a = 1.0 / 3.0; 3 | const float b = 0.33333333333; 4 | if (a == b) { 5 | // Should trigger warning 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/inexact-local.tmpl: -------------------------------------------------------------------------------- 1 | I: inexact-local.qc 2 | D: inexact comparisons 3 | T: -fail 4 | C: -std=gmqcc -Winexact-compares -Wall -Werror 5 | -------------------------------------------------------------------------------- /tests/inexact.qc: -------------------------------------------------------------------------------- 1 | const float a = 1.0 / 3.0; 2 | const float b = 0.33333333333; 3 | 4 | void main() { 5 | if (a == b) { 6 | // Should trigger warning 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/inexact.tmpl: -------------------------------------------------------------------------------- 1 | I: inexact.qc 2 | D: inexact comparisons 3 | T: -fail 4 | C: -std=gmqcc -Winexact-compares -Wall -Werror 5 | -------------------------------------------------------------------------------- /tests/last.qc: -------------------------------------------------------------------------------- 1 | float here; 2 | [[last]] float here; 3 | #ifdef MAKE_IT_FAIL 4 | float here; 5 | #endif 6 | -------------------------------------------------------------------------------- /tests/last.tmpl: -------------------------------------------------------------------------------- 1 | I: last.qc 2 | D: last attribute 3 | T: -compile 4 | C: -std=gmqcc -fftepp 5 | F: -no-defs 6 | -------------------------------------------------------------------------------- /tests/last2.tmpl: -------------------------------------------------------------------------------- 1 | I: last.qc 2 | D: last attribute 3 | T: -fail 4 | C: -std=gmqcc -fftepp -DMAKE_IT_FAIL 5 | F: -no-defs 6 | -------------------------------------------------------------------------------- /tests/length.qc: -------------------------------------------------------------------------------- 1 | const string a = "hello world"; // 11 2 | float b[] = { 1, 2, 3 }; // 3 3 | float c[5] = { 5, 4, 3, 2, 1 }; // 5 4 | const float d[] = { 1 }; // 1 5 | 6 | void main() { 7 | print(ftos(_length a), "\n"); // 11 8 | print(ftos(_length b), "\n"); // 3 9 | print(ftos(_length c), "\n"); // 5 10 | print(ftos(_length d), "\n"); // 1 11 | 12 | static float al = _length(a); 13 | static float bl = _length(b); 14 | static float cl = _length(c); 15 | static float dl = _length(d); 16 | 17 | print(ftos(al), "\n"); // 11 18 | print(ftos(bl), "\n"); // 3 19 | print(ftos(cl), "\n"); // 5 20 | print(ftos(dl), "\n"); // 1 21 | 22 | print(ftos(_length "hello world"), "\n"); // 11 23 | } 24 | -------------------------------------------------------------------------------- /tests/length.tmpl: -------------------------------------------------------------------------------- 1 | I: length.qc 2 | D: length operator 3 | T: -execute 4 | C: -std=gmqcc 5 | M: 11 6 | M: 3 7 | M: 5 8 | M: 1 9 | M: 11 10 | M: 3 11 | M: 5 12 | M: 1 13 | M: 11 14 | -------------------------------------------------------------------------------- /tests/memberbinop.qc: -------------------------------------------------------------------------------- 1 | .vector k; 2 | 3 | void t(entity e) 4 | { 5 | e.k = '0 0 0'; 6 | e.k.x += 2; 7 | e.k.y += 4; 8 | e.k.z += 6; 9 | } 10 | 11 | void main() { 12 | entity e = spawn(); 13 | t(e); 14 | print(vtos(e.k)); 15 | } 16 | -------------------------------------------------------------------------------- /tests/memberbinop.tmpl: -------------------------------------------------------------------------------- 1 | I: memberbinop.qc 2 | D: test member bin ops 3 | T: -execute 4 | C: -std=gmqcc 5 | E: $null 6 | M: '2 4 6' 7 | -------------------------------------------------------------------------------- /tests/mul_vf.qc: -------------------------------------------------------------------------------- 1 | // getter to work around future -O 2 | vector get(vector v) { 3 | return v; 4 | } 5 | 6 | void test(vector in) { 7 | vector v = get(in); 8 | vector b = v * v_x; 9 | print(vtos(b), "\n"); 10 | } 11 | 12 | void main() { 13 | test('20 40 80'); 14 | } 15 | -------------------------------------------------------------------------------- /tests/mul_vf.tmpl: -------------------------------------------------------------------------------- 1 | I: mul_vf.qc 2 | D: mul-vf/fv liferanges 3 | T: -execute 4 | C: -std=fteqcc -Opeephole -Olocal-temps 5 | M: '400 800 1600' 6 | -------------------------------------------------------------------------------- /tests/ngraphs.qc: -------------------------------------------------------------------------------- 1 | void() main = ??< 2 | print("??=??'??(??)??!????-??/??/%>|"); 3 | print("#^[]|{}~\\%>\n"); 4 | %>; 5 | -------------------------------------------------------------------------------- /tests/ngraphs.tmpl: -------------------------------------------------------------------------------- 1 | I: ngraphs.qc 2 | D: test digraphs and trigraphs 3 | T: -execute 4 | C: -std=gmqcc 5 | E: $null 6 | M: #^[]|{}~\%>|#^[]|{}~\%> 7 | -------------------------------------------------------------------------------- /tests/noref.qc: -------------------------------------------------------------------------------- 1 | #pragma noref 1 2 | float unused_global_1; 3 | #pragma noref 0 4 | noref float unused_global_2; 5 | 6 | void() main = { 7 | }; 8 | -------------------------------------------------------------------------------- /tests/noref.tmpl: -------------------------------------------------------------------------------- 1 | I: noref.qc 2 | D: noref keyword and pragma 3 | T: -compile 4 | C: -std=qcc -Wall -Werror -Wno-uninitialized-global 5 | -------------------------------------------------------------------------------- /tests/noreturn.qc: -------------------------------------------------------------------------------- 1 | #ifndef NORETURN 2 | #define NORETURN [[noreturn]] 3 | #endif 4 | 5 | void (...) print = #1; 6 | string (float val) ftos = #2; 7 | 8 | NORETURN void error(...) = #6; 9 | 10 | #if TEST == 1 11 | void test1(float a) { 12 | float x; 13 | 14 | if (a == 1) x = 1337; 15 | else if (a == 2) x = 1338; 16 | else 17 | error("Error\n"); 18 | print("Is this initialized: ", ftos(x), "\n"); 19 | } 20 | 21 | #else 22 | 23 | float test2(float it) { 24 | switch (it) { 25 | case 1: return 0; 26 | case 2: return 1; 27 | default: error("End of non-void not reachable...\n"); 28 | } 29 | } 30 | #endif 31 | -------------------------------------------------------------------------------- /tests/noreturn1.tmpl: -------------------------------------------------------------------------------- 1 | I: noreturn.qc 2 | D: noreturn keyword - should work 3 | T: -compile 4 | C: -std=fteqcc -Wall -Werror -DTEST=1 -DNORETURN=[[noreturn]] 5 | F: -no-defs 6 | -------------------------------------------------------------------------------- /tests/noreturn2.tmpl: -------------------------------------------------------------------------------- 1 | I: noreturn.qc 2 | D: noreturn keyword - should work 3 | T: -compile 4 | C: -std=fteqcc -Wall -Werror -DTEST=2 -DNORETURN=[[noreturn]] 5 | F: -no-defs 6 | -------------------------------------------------------------------------------- /tests/noreturn3.tmpl: -------------------------------------------------------------------------------- 1 | I: noreturn.qc 2 | D: noreturn keyword - should fail 3 | T: -fail 4 | C: -std=fteqcc -Wall -Werror -DTEST=1 -DNORETURN 5 | F: -no-defs 6 | -------------------------------------------------------------------------------- /tests/noreturn4.tmpl: -------------------------------------------------------------------------------- 1 | I: noreturn.qc 2 | D: noreturn keyword - should fail 3 | T: -fail 4 | C: -std=fteqcc -Wall -Werror -DTEST=2 -DNORETURN 5 | F: -no-defs 6 | -------------------------------------------------------------------------------- /tests/octal.qc: -------------------------------------------------------------------------------- 1 | void main() { 2 | float a = 012; 3 | float b = 0204; 4 | float c = 076663; 5 | float d = 0777; 6 | 7 | print(ftos(a), "\n"); 8 | print(ftos(b), "\n"); 9 | print(ftos(c), "\n"); 10 | print(ftos(d), "\n"); 11 | } 12 | -------------------------------------------------------------------------------- /tests/octal.tmpl: -------------------------------------------------------------------------------- 1 | I: octal.qc 2 | D: test octal constants 3 | T: -execute 4 | C: -std=gmqcc 5 | M: 10 6 | M: 132 7 | M: 32179 8 | M: 511 9 | -------------------------------------------------------------------------------- /tests/operators.qc: -------------------------------------------------------------------------------- 1 | .float mem; 2 | 3 | void main() { 4 | float a; 5 | 6 | // regular binary+store 7 | a = 5; 8 | print(ftos(a += 1), " = "); 9 | print(ftos(a), "\n"); 10 | 11 | entity e = spawn(); 12 | e.mem = 10; 13 | print(ftos(e.mem += 1), " = "); 14 | print(ftos(e.mem), "\n"); 15 | 16 | // prefix 17 | print(ftos(++a), " = "); 18 | print(ftos(a), "\n"); 19 | print(ftos(--a), " = "); 20 | print(ftos(a), "\n"); 21 | print(ftos(++e.mem), " = "); 22 | print(ftos(e.mem), "\n"); 23 | 24 | // suffix 25 | print(ftos(a++), " = "); 26 | print(ftos(a-1), "\n"); 27 | // the CLANG way: 28 | a = 3; 29 | print(ftos((a++ + a) + a), " = 11\n"); 30 | 31 | // check if minus translates 32 | print(ftos(a--), "\n"); 33 | print(ftos(--a), "\n"); 34 | 35 | // postfix on members 36 | print(ftos(e.mem--), " = "); 37 | print(ftos(e.mem+1), "\n"); 38 | 39 | // compounds in general 40 | a = 3; 41 | print(ftos(a *= 2), " = 6\n"); 42 | print(ftos(a /= 2), " = 3\n"); 43 | 44 | // compounds on vectors 45 | vector v; 46 | v = '3 4 5'; 47 | print(vtos(v *= 2), " = '6 8 10'\n"); 48 | print(vtos(v /= 2), " = '3 4 5'\n"); 49 | 50 | // bit compounds 51 | a = 1; 52 | print(ftos(a |= 2), " = 3\n"); 53 | print(ftos(a &= 6), " = 2\n"); 54 | a = 7; 55 | 56 | print(ftos(a &~= 3), " = 4\n"); 57 | } 58 | -------------------------------------------------------------------------------- /tests/operators.tmpl: -------------------------------------------------------------------------------- 1 | I: operators.qc 2 | D: compound and pre/postfix operators 3 | T: -execute 4 | C: -std=fteqcc 5 | M: 6 = 6 6 | M: 11 = 11 7 | M: 7 = 7 8 | M: 6 = 6 9 | M: 12 = 12 10 | M: 6 = 6 11 | M: 11 = 11 12 | M: 4 13 | M: 2 14 | M: 12 = 12 15 | M: 6 = 6 16 | M: 3 = 3 17 | M: '6 8 10' = '6 8 10' 18 | M: '3 4 5' = '3 4 5' 19 | M: 3 = 3 20 | M: 2 = 2 21 | M: 4 = 4 22 | -------------------------------------------------------------------------------- /tests/order.qc: -------------------------------------------------------------------------------- 1 | float go(string x) { 2 | print(x, "\n"); 3 | return 1; 4 | } 5 | 6 | void main() { 7 | float x = go("A") + go("B"); 8 | } 9 | -------------------------------------------------------------------------------- /tests/order.tmpl: -------------------------------------------------------------------------------- 1 | I: order.qc 2 | D: execution order check 3 | T: -execute 4 | C: -std=gmqcc 5 | M: A 6 | M: B 7 | -------------------------------------------------------------------------------- /tests/param8.qc: -------------------------------------------------------------------------------- 1 | void p10(float a, float b, float c, float d, float e, float f, float g, float h, 2 | float e1, float e2) 3 | { 4 | print(ftos(a), " ", ftos(b), " ", ftos(c), " "); 5 | print(ftos(d), " ", ftos(e), " ", ftos(f), " "); 6 | print(ftos(g), " ", ftos(h), " "); 7 | print(ftos(e1), " ", ftos(e2), "\n"); 8 | } 9 | 10 | void overwrite(float a, float b, float c, float d, float e, float f, float g, float h, 11 | float e1, float e2) 12 | { 13 | // call >8 param functions within a >8 param function and see if the locals 14 | // are actual copies 15 | p10(a, b, c, d, e, f, g, h, e1, e2); 16 | p10(1, 1, 1, 1, 1, 1, 1, 1, 1, 1); 17 | p10(a, b, c, d, e, f, g, h, e1, e2); 18 | } 19 | 20 | void main() { 21 | overwrite(10, 20, 30, 40, 50, 60, 70, 80, 90, 100); 22 | } 23 | -------------------------------------------------------------------------------- /tests/param8.tmpl: -------------------------------------------------------------------------------- 1 | I: param8.qc 2 | D: test extended parameters 3 | T: -execute 4 | C: -std=fteqcc 5 | M: 10 20 30 40 50 60 70 80 90 100 6 | M: 1 1 1 1 1 1 1 1 1 1 7 | M: 10 20 30 40 50 60 70 80 90 100 8 | -------------------------------------------------------------------------------- /tests/paramomit.qc: -------------------------------------------------------------------------------- 1 | void foo(float) { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /tests/paramomit.tmpl: -------------------------------------------------------------------------------- 1 | I: paramomit.qc 2 | D: test parameter omit 3 | T: -fail 4 | C: -std=fteqcc 5 | -------------------------------------------------------------------------------- /tests/parens.qc: -------------------------------------------------------------------------------- 1 | float arr[2]; 2 | 3 | string gets() { return "S\n"; } 4 | void main(float x) { 5 | string s; 6 | 7 | s = gets(); // 0 params 8 | print(s); // 1 param 9 | print("A ", "B\n"); // 2 params 10 | print("A ", "B ", "C\n"); // more params 11 | print(gets()); // 0-param call in call 12 | print(gets(), "next\n"); // 0-param call and another 13 | print("-> ", gets()); // param + 0-param call 14 | print(ftos(x), "\n"); // param-call + another 15 | print(x ? "xA\n" : "xB\n"); // ternary in PAREN_FUNC 16 | print(!x ? "xA\n" : "xB\n"); // ternary in PAREN_FUNC 17 | // PAREN_INDEX 18 | arr[0] = 10; 19 | arr[1] = 11; 20 | // PAREN_TERNARY + PAREN_INDEX 21 | arr[x ? 0 : 1] += 100; 22 | print(ftos(arr[0]), "\n"); 23 | print(ftos(arr[1]), "\n"); 24 | print(ftos(arr[x ? 0 : 1]), "\n"); 25 | print(ftos(arr[!x ? 0 : 1]), "\n"); 26 | 27 | // loops with comma operators 28 | float i, j; 29 | for (i = 0, j = 0; i < x; ++i) 30 | print("-"); 31 | print("\n"); 32 | 33 | // if + PAREN_TERNARY2 34 | if (x ? 1 : 0) 35 | print("OK\n"); 36 | if (x ? 0 : 1) 37 | print("NO\n"); 38 | 39 | // PAREN_FUNC in PAREN_EXPR 40 | print(("Is this wrong ", "now?\n")); 41 | } 42 | -------------------------------------------------------------------------------- /tests/parens.tmpl: -------------------------------------------------------------------------------- 1 | I: parens.qc 2 | D: parentheses, SYA stuff 3 | T: -execute 4 | C: -std=fteqcc 5 | E: -float 4 6 | M: S 7 | M: A B 8 | M: A B C 9 | M: S 10 | M: S 11 | M: next 12 | M: -> S 13 | M: 4 14 | M: xA 15 | M: xB 16 | M: 110 17 | M: 11 18 | M: 110 19 | M: 11 20 | M: ---- 21 | M: OK 22 | M: now? 23 | -------------------------------------------------------------------------------- /tests/parent_block_scope_for_locals.qc: -------------------------------------------------------------------------------- 1 | void a() { 2 | if (1) 3 | for (float i = 0; i < 3; ++i) 4 | print(ftos(i)); 5 | } 6 | 7 | void b() { 8 | if (1) 9 | for (float i = 0; i < 3; ++i) 10 | print(ftos(i)); 11 | } 12 | 13 | void main() { 14 | a(); 15 | b(); 16 | } -------------------------------------------------------------------------------- /tests/parent_block_scope_for_locals.tmpl: -------------------------------------------------------------------------------- 1 | I: parent_block_scope_for_locals.qc 2 | D: when omitting braces ensure locals end up in parent block 3 | T: -execute 4 | C: -std=fteqcc 5 | M: 012012 6 | -------------------------------------------------------------------------------- /tests/perl-logic.qc: -------------------------------------------------------------------------------- 1 | void main() { 2 | vector va, vb; 3 | string sa, sb; 4 | print(__builtin_debug_typestring(va || vb), "\n"); 5 | print(__builtin_debug_typestring(sa || sb), "\n"); 6 | } 7 | -------------------------------------------------------------------------------- /tests/perl-logic.tmpl: -------------------------------------------------------------------------------- 1 | I: perl-logic.qc 2 | D: perl logic output types 3 | T: -execute 4 | C: -std=fteqcc -fperl-logic 5 | M: vector 6 | M: string 7 | -------------------------------------------------------------------------------- /tests/pmacros.qc: -------------------------------------------------------------------------------- 1 | /* will never happen unless blown up */ 2 | #ifndef GMQCC 3 | void() main = { error-gmqcc-is-not-defined; }; 4 | #else 5 | const string standard = 6 | # ifdef __STD_QCC__ 7 | "qcc" 8 | # else 9 | # ifdef __STD_GMQCC__ 10 | "gmqcc" 11 | # else 12 | # ifdef __STD_FTEQCC__ 13 | "fteqcc" 14 | # else 15 | "unknown" 16 | # endif 17 | # endif 18 | # endif 19 | ; 20 | const string verminor = __STD_VERSION_MINOR__; 21 | const string vermajor = __STD_VERSION_MAJOR__; 22 | 23 | # define ALPHA_a "a" 24 | # define ALPHA_b "b" 25 | # define ALPHA_c "c" 26 | 27 | # define ALPHA(SEL) ALPHA_##SEL 28 | 29 | # define ABC ALPHA(a)ALPHA(b)ALPHA(c) 30 | 31 | void() main = { 32 | if (ABC == "abc") 33 | print("ABC\n"); 34 | if (standard != "unknown") 35 | print("123\n"); 36 | }; 37 | #endif 38 | -------------------------------------------------------------------------------- /tests/pmacros.tmpl: -------------------------------------------------------------------------------- 1 | I: pmacros.qc 2 | D: test preprocessor 3 | T: -execute 4 | C: -std=fteqcc 5 | M: ABC 6 | M: 123 7 | -------------------------------------------------------------------------------- /tests/pointlife.qc: -------------------------------------------------------------------------------- 1 | var float foo = 0; 2 | 3 | void funcall() {} 4 | void bar(string str) {} 5 | 6 | void main(string str) { 7 | string pl; 8 | 9 | if (foo) 10 | return; // this is a block wher 'str' doesn't live 11 | // so the point-life will not overlap with str 12 | pl = "Got overwritten!\n"; // pl point-life 13 | 14 | print(str); 15 | 16 | pl = "Kill the lifrange here"; // pl life stops 17 | funcall(); // Now lock pl in case we have -Oglobal-temps 18 | bar(pl); // pl life starts here now 19 | } 20 | -------------------------------------------------------------------------------- /tests/pointlife.tmpl: -------------------------------------------------------------------------------- 1 | I: pointlife.qc 2 | D: local-temp liferange test 3 | T: -execute 4 | C: -std=fteqcc -Ono-global-temps -Olocal-temps 5 | E: -string Okay 6 | M: Okay 7 | -------------------------------------------------------------------------------- /tests/pops.qc: -------------------------------------------------------------------------------- 1 | void main() { 2 | /* so far only one perl operator is implemented */ 3 | float x = 100; 4 | float y = 200; 5 | float z = 300; 6 | 7 | /* to ensure runtime */ 8 | x += 1; 9 | y += 1; 10 | z += 1; 11 | 12 | float test_x = (x <=> x + 1); // -1 less than 13 | float test_y = (x <=> x); // 0 equal 14 | float test_z = (x <=> x - 1); // 1 greater than 15 | 16 | print(ftos(test_x), "\n"); 17 | print(ftos(test_y), "\n"); 18 | print(ftos(test_z), "\n"); 19 | } 20 | -------------------------------------------------------------------------------- /tests/pops.tmpl: -------------------------------------------------------------------------------- 1 | I: pops.qc 2 | D: test perl operators 3 | T: -execute 4 | C: -std=gmqcc 5 | M: -1 6 | M: 0 7 | M: 1 8 | -------------------------------------------------------------------------------- /tests/pp_va_args.qc: -------------------------------------------------------------------------------- 1 | // method 0 2 | #define METHOD__(...) __VA_ARGS__ 3 | #define METHOD_0(F,A) F METHOD__(A) 4 | 5 | // method 1 6 | #define METHOD_1(F,A) F(METHOD__ A) 7 | 8 | // method 2 9 | #define METHOD_2(F,...) F __VA_ARGS__##[0] 10 | 11 | // method 3 12 | #define METHOD_3(F,...) F __VA_ARGS__ 13 | 14 | // selector 15 | #define METHOD(I, F, ...) METHOD_##I (F, __VA_ARGS__) 16 | 17 | void main() { 18 | METHOD(0, print, ("Method", " \n")); 19 | METHOD(1, print, ("Method", " \n")); 20 | METHOD(2, print, ("Method", " \n")); 21 | METHOD(3, print, ("Method", " \n")); 22 | } 23 | -------------------------------------------------------------------------------- /tests/pp_va_args.tmpl: -------------------------------------------------------------------------------- 1 | I: pp_va_args.qc 2 | D: __VA_ARGS__ 3 | T: -execute 4 | C: -std=fteqcc 5 | M: Method 6 | M: Method 7 | M: Method 8 | M: Method 9 | -------------------------------------------------------------------------------- /tests/ppcat.qc: -------------------------------------------------------------------------------- 1 | #define CAT(X, Y) X##Y 2 | CAT(hello, world) 3 | 4 | #define REDIR(X, Y) CAT(X, Y) 5 | REDIR(CAT(hello, world), CAT(world, hello)) 6 | 7 | #define SCONS(X, ...) REDIR(X, __VA_ARGS__) 8 | SCONS(hello, world) 9 | 10 | #define FOO(X) X##X 11 | #define BAR(X) FOO(X)FOO(X) 12 | 13 | REDIR(BAR(hello),BAR(world)) 14 | -------------------------------------------------------------------------------- /tests/ppcat.tmpl: -------------------------------------------------------------------------------- 1 | I: ppcat.qc 2 | D: test preprocessor concatenation 3 | T: -pp 4 | C: -std=gmqcc 5 | F: -no-defs 6 | M: helloworld 7 | M: helloworldworldhello 8 | M: helloworld 9 | M: hellohellohellohelloworldworldworldworld 10 | 11 | -------------------------------------------------------------------------------- /tests/pperror.qc: -------------------------------------------------------------------------------- 1 | /* 2 | * Cause a compile-time error via preprocessor directive. This code should 3 | * not compile. 4 | */ 5 | #error "Fail to compile" 6 | -------------------------------------------------------------------------------- /tests/pperror.tmpl: -------------------------------------------------------------------------------- 1 | I: pperror.qc 2 | D: preprocessor #error directive 3 | T: -fail 4 | C: -std=fteqcc 5 | -------------------------------------------------------------------------------- /tests/ppindirectexpand.qc: -------------------------------------------------------------------------------- 1 | #define STR1(x) #x 2 | #define STR2(x) STR1(x) 3 | #define THE_ANSWER 42 4 | #define THE_ANSWER_STR STR2(THE_ANSWER) 5 | 6 | THE_ANSWER_STR 7 | -------------------------------------------------------------------------------- /tests/ppindirectexpand.tmpl: -------------------------------------------------------------------------------- 1 | I: ppindirectexpand.qc 2 | D: test preprocessor indirect macro expansion 3 | T: -pp 4 | C: -fftepp-indirect-expansion -std=gmqcc 5 | F: -no-defs 6 | M: "42" 7 | 8 | -------------------------------------------------------------------------------- /tests/predef_func.qc: -------------------------------------------------------------------------------- 1 | void main() { 2 | #ifdef SIMPLE 3 | print(__FUNC__, "\n"); 4 | #elifdef CONCATENATED 5 | print(__FUNC__ "\n"); 6 | #else 7 | # error this is wrong 8 | #endif 9 | } 10 | -------------------------------------------------------------------------------- /tests/predef_func.tmpl: -------------------------------------------------------------------------------- 1 | I: predef_func.qc 2 | D: simple __FUNC__ case 3 | T: -execute 4 | C: -std=fteqcc -DSIMPLE 5 | M: main 6 | -------------------------------------------------------------------------------- /tests/predef_func_concat.tmpl: -------------------------------------------------------------------------------- 1 | I: predef_func.qc 2 | D: concatenated __FUNC__ case 3 | T: -execute 4 | C: -std=fteqcc -DCONCATENATED 5 | M: main 6 | -------------------------------------------------------------------------------- /tests/rassign-fail.tmpl: -------------------------------------------------------------------------------- 1 | I: rassign.qc 2 | D: test return assignments 3 | T: -fail 4 | C: -fftepp -freturn-assignments -DFAIL_TEST -Wmissing-return-values -Werror 5 | -------------------------------------------------------------------------------- /tests/rassign.qc: -------------------------------------------------------------------------------- 1 | float f_float() { 2 | return = 100.0f; 3 | return = 200.0f; 4 | return; 5 | } 6 | 7 | vector f_vector() { 8 | vector foo; 9 | foo.x = f_float(); 10 | foo.y = f_float(); 11 | foo.z = f_float(); 12 | 13 | return = foo; 14 | return; 15 | } 16 | 17 | string f_string() { 18 | #ifndef FAIL_TEST 19 | return = "hello"; 20 | return = "world"; 21 | #endif 22 | return; 23 | } 24 | 25 | float factorial(float n) { 26 | if (n == 0) return = 1; 27 | else return = n * factorial(n - 1); 28 | } 29 | 30 | void main() { 31 | print(ftos(f_float()), "\n"); // 200.0f 32 | print(vtos(f_vector()), "\n"); // '1 2 3' 33 | print(f_string(), "\n"); // world 34 | print(ftos(factorial(4)), "\n"); // 24 35 | } 36 | -------------------------------------------------------------------------------- /tests/rassign.tmpl: -------------------------------------------------------------------------------- 1 | I: rassign.qc 2 | D: test return assignments 3 | T: -execute 4 | C: -std=gmqcc -fftepp -freturn-assignments 5 | M: 200 6 | M: '200 200 200' 7 | M: world 8 | M: 24 9 | -------------------------------------------------------------------------------- /tests/short-logic.qc: -------------------------------------------------------------------------------- 1 | float glob1; 2 | float glob2; 3 | float glob3; 4 | 5 | float side_effect_1(float r) { 6 | glob1 += 3; 7 | return r; 8 | } 9 | 10 | float side_effect_2(float r) { 11 | glob2 += 3; 12 | return r; 13 | } 14 | 15 | float side_effect_3(float r) { 16 | glob3 += 3; 17 | return r; 18 | } 19 | 20 | void main() { 21 | glob1 = 10; 22 | glob2 = 20; 23 | glob3 = 30; 24 | 25 | if (side_effect_1(0) || side_effect_2(1)) 26 | print(ftos(glob1), "=13 ", ftos(glob2), "=23 OK\n"); 27 | else 28 | print("Fail\n"); 29 | 30 | if (side_effect_3(1) || side_effect_1(1)) 31 | print(ftos(glob1), "=13 ", ftos(glob3), "=33 OK\n"); 32 | else 33 | print("Fail\n"); 34 | 35 | if (side_effect_1(0) && side_effect_3(1)) 36 | print("Fail\n"); 37 | else 38 | print(ftos(glob1), "=16 ", ftos(glob3), "=33 OK\n"); 39 | 40 | if (side_effect_2(1) && side_effect_3(1)) 41 | print(ftos(glob2), "=26 ", ftos(glob3), "=36 OK\n"); 42 | else 43 | print("Fail\n"); 44 | 45 | print(ftos(glob1), "=16 ", ftos(glob2), "=26 ", ftos(glob3), "=36 OK\n"); 46 | } 47 | -------------------------------------------------------------------------------- /tests/short-logic.tmpl: -------------------------------------------------------------------------------- 1 | I: short-logic.qc 2 | D: test short circuit logic 3 | T: -execute 4 | C: -std=fteqcc -fshort-logic 5 | M: 13=13 23=23 OK 6 | M: 13=13 33=33 OK 7 | M: 16=16 33=33 OK 8 | M: 26=26 36=36 OK 9 | M: 16=16 26=26 36=36 OK 10 | -------------------------------------------------------------------------------- /tests/split-vectors.qc: -------------------------------------------------------------------------------- 1 | void main() { 2 | print(vtos('1 2 3'), "\n"); 3 | print(vtos('4 5 6'), "\n"); 4 | print(vtos('7 8 9'), "\n"); 5 | print(vtos('1 5 9'), "\n"); 6 | } 7 | -------------------------------------------------------------------------------- /tests/split-vectors.tmpl: -------------------------------------------------------------------------------- 1 | I: split-vectors.qc 2 | D: test -fsplit-vector-parameters 3 | T: -execute 4 | C: -std=fteqcc -Wall -Werror -fsplit-vector-parameters 5 | M: '1 2 3' 6 | M: '4 5 6' 7 | M: '7 8 9' 8 | M: '1 5 9' 9 | -------------------------------------------------------------------------------- /tests/state-emu.tmpl: -------------------------------------------------------------------------------- 1 | I: state.qc 2 | D: test emulated state ops 3 | T: -execute 4 | C: -std=gmqcc -femulate-state 5 | M: st1, .frame=1, .nextthink=10.1 (now: 10) 6 | M: st2, .frame=2, .nextthink=11.1 (now: 11) 7 | M: st3, .frame=0, .nextthink=12.1 (now: 12) 8 | M: st1, .frame=1, .nextthink=13.1 (now: 13) 9 | M: st2, .frame=2, .nextthink=14.1 (now: 14) 10 | -------------------------------------------------------------------------------- /tests/state.qc: -------------------------------------------------------------------------------- 1 | float time; 2 | entity self; 3 | 4 | .void() think; 5 | .float nextthink; 6 | .float frame; 7 | 8 | void stprint(string fun) { 9 | print(fun, 10 | ", .frame=", ftos(self.frame), 11 | ", .nextthink=", ftos(self.nextthink), 12 | " (now: ", ftos(time), ")\n"); 13 | } 14 | 15 | void st1() = [1, st2] { stprint("st1"); } 16 | void st2() = [2, st3] { stprint("st2"); } 17 | void st3() = [0, st1] { stprint("st3"); } 18 | 19 | void main() { 20 | entity ea = spawn(); 21 | entity eb = spawn(); 22 | 23 | time = 10; 24 | self = ea; 25 | 26 | self.think = st1; 27 | self.nextthink = time; 28 | self.frame = 100; 29 | 30 | self.think(); 31 | time = 11; 32 | self.think(); 33 | time = 12; 34 | self.think(); 35 | time = 13; 36 | self.think(); 37 | time = 14; 38 | self.think(); 39 | }; 40 | -------------------------------------------------------------------------------- /tests/state.tmpl: -------------------------------------------------------------------------------- 1 | I: state.qc 2 | D: test state ops 3 | T: -execute 4 | C: -std=gmqcc 5 | M: st1, .frame=1, .nextthink=10.1 (now: 10) 6 | M: st2, .frame=2, .nextthink=11.1 (now: 11) 7 | M: st3, .frame=0, .nextthink=12.1 (now: 12) 8 | M: st1, .frame=1, .nextthink=13.1 (now: 13) 9 | M: st2, .frame=2, .nextthink=14.1 (now: 14) 10 | -------------------------------------------------------------------------------- /tests/switch.qc: -------------------------------------------------------------------------------- 1 | void test(float param, float p2) { 2 | float i; 3 | float c80 = 80; 4 | switch(param) { 5 | case 3: print("Three, falling through to 2 - "); 6 | case 2: print("Two\n"); break; 7 | case 1: print("One\n"); break; 8 | case 4: print("Four, falling through to default - "); 9 | default: print("Other\n"); break; 10 | 11 | case c80: 12 | print("Enhanced 80\n"); 13 | break; 14 | 15 | case 99: 16 | if (p2 > 5) { 17 | print("early break\n"); 18 | break; 19 | } 20 | for (i = 0; i < 5; i += 1) 21 | print(ftos(i), " "); 22 | print(ftos(i), "\n"); 23 | } 24 | } 25 | 26 | void main() { 27 | test(1, 0); 28 | test(2, 0); 29 | test(3, 0); 30 | test(4, 0); 31 | test(5, 0); 32 | test(80, 0); 33 | test(99, 0); 34 | test(99, 6); 35 | } 36 | -------------------------------------------------------------------------------- /tests/switch.tmpl: -------------------------------------------------------------------------------- 1 | I: switch.qc 2 | D: test switches 3 | T: -execute 4 | C: -std=fteqcc -frelaxed-switch 5 | M: One 6 | M: Two 7 | M: Three, falling through to 2 - Two 8 | M: Four, falling through to default - Other 9 | M: Other 10 | M: Enhanced 80 11 | M: 0 1 2 3 4 5 12 | M: early break 13 | -------------------------------------------------------------------------------- /tests/ternary-fte.tmpl: -------------------------------------------------------------------------------- 1 | I: ternary.qc 2 | D: nested ternary expressions - FTE-style ternary 3 | T: -execute 4 | C: -std=fteqcc 5 | M: 1 a=11 a=21 a=other0 6 | M: 0 7 | M: 0 8 | M: 1 a=11 a=21 a=other0 9 | M: 0 10 | M: 0 11 | M: 5 12 | M: 9 13 | M: 10 14 | M: select_a: 1 15 | M: select_b: 0 16 | -------------------------------------------------------------------------------- /tests/ternary.qc: -------------------------------------------------------------------------------- 1 | void test(float cond, float v1, float v2, float a) { 2 | print(ftos(cond ? v1 : v2), " "); 3 | print( (cond ? v1 : v2) ? ( (a == 1) ? "a=1" 4 | : (a == 2) ? "a=2" 5 | : "a=other" 6 | ) 7 | : "not met", 8 | "\n"); 9 | } 10 | 11 | void select_a(float x) { 12 | print("select_a: ", ftos(x), "\n"); 13 | } 14 | void select_b(float x) { 15 | print("select_b: ", ftos(x), "\n"); 16 | } 17 | 18 | void main() { 19 | float a, b; 20 | test(0, -99, 1, 1); 21 | test(0, -99, 1, 2); 22 | test(0, -99, 1, 3); 23 | test(0, -99, 0, 1); 24 | test(0, -99, 0, 2); 25 | test(0, -99, 0, 3); 26 | test(1, 1, -99, 1); 27 | test(1, 1, -99, 2); 28 | test(1, 1, -99, 3); 29 | test(1, 0, -99, 1); 30 | test(1, 0, -99, 2); 31 | test(1, 0, -99, 3); 32 | 33 | b = 5; 34 | a = b ? 5 : 6; 35 | print(ftos(a), "\n"); 36 | b ? a = 9 : a = 10; 37 | print(ftos(a), "\n"); 38 | !b ? a = 9 : a = 10; 39 | print(ftos(a), "\n"); 40 | 41 | ((1) ? select_a : select_b) (1); 42 | ((0) ? select_a : select_b) (0); 43 | } 44 | -------------------------------------------------------------------------------- /tests/ternary.tmpl: -------------------------------------------------------------------------------- 1 | I: ternary.qc 2 | D: nested ternary expressions 3 | T: -execute 4 | C: -std=fteqcc -fcorrect-ternary 5 | M: 1 a=1 6 | M: 1 a=2 7 | M: 1 a=other 8 | M: 0 not met 9 | M: 0 not met 10 | M: 0 not met 11 | M: 1 a=1 12 | M: 1 a=2 13 | M: 1 a=other 14 | M: 0 not met 15 | M: 0 not met 16 | M: 0 not met 17 | M: 5 18 | M: 9 19 | M: 10 20 | M: select_a: 1 21 | M: select_b: 0 22 | -------------------------------------------------------------------------------- /tests/truth-flags-1-s.tmpl: -------------------------------------------------------------------------------- 1 | I: truth-flags-2.qc 2 | D: logic flags 3 | T: -execute 4 | C: -std=fteqcc -fshort-logic 5 | M: ! & | i N 6 | M: 'str', 'str' -> 0 1 1 1 0 7 | M: 'str', '' -> 0 1 1 1 0 8 | M: 'str', 0 -> 0 0 1 1 0 9 | M: '', 'str' -> 1 1 1 1 0 10 | M: '', '' -> 1 1 1 1 0 11 | M: '', 0 -> 1 0 1 1 0 12 | M: 0, 'str' -> 1 0 1 0 1 13 | M: 0, '' -> 1 0 1 0 1 14 | M: 0, 0 -> 1 0 0 0 1 15 | -------------------------------------------------------------------------------- /tests/truth-flags-1.tmpl: -------------------------------------------------------------------------------- 1 | I: truth-flags-2.qc 2 | D: logic flags 3 | T: -execute 4 | C: -std=fteqcc 5 | M: ! & | i N 6 | M: 'str', 'str' -> 0 1 1 1 0 7 | M: 'str', '' -> 0 1 1 1 0 8 | M: 'str', 0 -> 0 0 1 1 0 9 | M: '', 'str' -> 1 1 1 1 0 10 | M: '', '' -> 1 1 1 1 0 11 | M: '', 0 -> 1 0 1 1 0 12 | M: 0, 'str' -> 1 0 1 0 1 13 | M: 0, '' -> 1 0 1 0 1 14 | M: 0, 0 -> 1 0 0 0 1 15 | -------------------------------------------------------------------------------- /tests/truth-flags-2-s.tmpl: -------------------------------------------------------------------------------- 1 | I: truth-flags-2.qc 2 | D: logic flags 3 | T: -execute 4 | C: -std=fteqcc -fshort-logic -ftrue-empty-strings 5 | M: ! & | i N 6 | M: 'str', 'str' -> 0 1 1 1 0 7 | M: 'str', '' -> 0 1 1 1 0 8 | M: 'str', 0 -> 0 0 1 1 0 9 | M: '', 'str' -> 0 1 1 1 0 10 | M: '', '' -> 0 1 1 1 0 11 | M: '', 0 -> 0 0 1 1 0 12 | M: 0, 'str' -> 1 0 1 0 1 13 | M: 0, '' -> 1 0 1 0 1 14 | M: 0, 0 -> 1 0 0 0 1 15 | -------------------------------------------------------------------------------- /tests/truth-flags-2.qc: -------------------------------------------------------------------------------- 1 | float test_s_not (string s) { return !s; } 2 | float test_s_and (string s, string t) { return s && t; } 3 | float test_s_or (string s, string t) { return s || t; } 4 | float test_s_if (string s) { if (s) return 1; return 0; } 5 | float test_s_ifnot(string s) { if not (s) return 1; return 0; } 6 | 7 | void test(string s, string t) { 8 | print(ftos(!!test_s_not (s)), " "); 9 | print(ftos(!!test_s_and (s, t)), " "); 10 | print(ftos(!!test_s_or (s, t)), " "); 11 | print(ftos(!!test_s_if (s)), " "); 12 | print(ftos(!!test_s_ifnot(s)), "\n"); 13 | } 14 | 15 | string nuls; 16 | void main() { 17 | print(" ! & | i N\n"); 18 | print("'str', 'str' -> "); test("FULL", "FULL"); 19 | print("'str', '' -> "); test("FULL", "" ); 20 | print("'str', 0 -> "); test("FULL", nuls ); 21 | print("'', 'str' -> "); test("", "FULL"); 22 | print("'', '' -> "); test("", "" ); 23 | print("'', 0 -> "); test("", nuls ); 24 | print("0, 'str' -> "); test(nuls, "FULL"); 25 | print("0, '' -> "); test(nuls, "" ); 26 | print("0, 0 -> "); test(nuls, nuls ); 27 | } 28 | -------------------------------------------------------------------------------- /tests/truth-flags-2.tmpl: -------------------------------------------------------------------------------- 1 | I: truth-flags-2.qc 2 | D: logic flags 3 | T: -execute 4 | C: -std=fteqcc -ftrue-empty-strings 5 | M: ! & | i N 6 | M: 'str', 'str' -> 0 1 1 1 0 7 | M: 'str', '' -> 0 1 1 1 0 8 | M: 'str', 0 -> 0 0 1 1 0 9 | M: '', 'str' -> 0 1 1 1 0 10 | M: '', '' -> 0 1 1 1 0 11 | M: '', 0 -> 0 0 1 1 0 12 | M: 0, 'str' -> 1 0 1 0 1 13 | M: 0, '' -> 1 0 1 0 1 14 | M: 0, 0 -> 1 0 0 0 1 15 | -------------------------------------------------------------------------------- /tests/truth-flags-3-s.tmpl: -------------------------------------------------------------------------------- 1 | I: truth-flags-2.qc 2 | D: logic flags 3 | T: -execute 4 | C: -std=fteqcc -fshort-logic -ffalse-empty-strings 5 | M: ! & | i N 6 | M: 'str', 'str' -> 0 1 1 1 0 7 | M: 'str', '' -> 0 0 1 1 0 8 | M: 'str', 0 -> 0 0 1 1 0 9 | M: '', 'str' -> 1 0 1 0 1 10 | M: '', '' -> 1 0 0 0 1 11 | M: '', 0 -> 1 0 0 0 1 12 | M: 0, 'str' -> 1 0 1 0 1 13 | M: 0, '' -> 1 0 0 0 1 14 | M: 0, 0 -> 1 0 0 0 1 15 | -------------------------------------------------------------------------------- /tests/truth-flags-3.tmpl: -------------------------------------------------------------------------------- 1 | I: truth-flags-2.qc 2 | D: logic flags 3 | T: -execute 4 | C: -std=fteqcc -ffalse-empty-strings 5 | M: ! & | i N 6 | M: 'str', 'str' -> 0 1 1 1 0 7 | M: 'str', '' -> 0 0 1 1 0 8 | M: 'str', 0 -> 0 0 1 1 0 9 | M: '', 'str' -> 1 0 1 0 1 10 | M: '', '' -> 1 0 0 0 1 11 | M: '', 0 -> 1 0 0 0 1 12 | M: 0, 'str' -> 1 0 1 0 1 13 | M: 0, '' -> 1 0 0 0 1 14 | M: 0, 0 -> 1 0 0 0 1 15 | -------------------------------------------------------------------------------- /tests/truth.qc: -------------------------------------------------------------------------------- 1 | void test(string s) { 2 | print(ftos(!s)); 3 | if (s) print(" on"); 4 | if (!s) print(" off"); 5 | if (!!s) print(" !!on"); 6 | print("\n"); 7 | } 8 | 9 | void test2(string s) { 10 | print(ftos(!s)); 11 | while (s) { print(" on"); break; } 12 | while (!s) { print(" off"); break; } 13 | while (!!s) { print(" !!on"); break; } 14 | print("\n"); 15 | } 16 | 17 | string str_nul; 18 | 19 | void main(vector v) { 20 | test("foo"); 21 | test2("foo"); 22 | test(""); 23 | test2(""); 24 | test(str_nul); 25 | test2(str_nul); 26 | } 27 | -------------------------------------------------------------------------------- /tests/truth.tmpl: -------------------------------------------------------------------------------- 1 | I: truth.qc 2 | D: -ffalse-empty-strings 3 | T: -execute 4 | C: -std=fteqcc -ffalse-empty-strings 5 | M: 0 on !!on 6 | M: 0 on !!on 7 | M: 1 off 8 | M: 1 off 9 | M: 1 off 10 | M: 1 off 11 | -------------------------------------------------------------------------------- /tests/truth2.tmpl: -------------------------------------------------------------------------------- 1 | I: truth.qc 2 | D: regular truth 3 | T: -execute 4 | C: -std=fteqcc 5 | M: 0 on !!on 6 | M: 0 on !!on 7 | M: 1 on off 8 | M: 1 on off 9 | M: 1 off 10 | M: 1 off 11 | -------------------------------------------------------------------------------- /tests/truth3.tmpl: -------------------------------------------------------------------------------- 1 | I: truth.qc 2 | D: -ftrue-empty-strings 3 | T: -execute 4 | C: -std=fteqcc -ftrue-empty-strings 5 | M: 0 on !!on 6 | M: 0 on !!on 7 | M: 0 on !!on 8 | M: 0 on !!on 9 | M: 1 off 10 | M: 1 off 11 | -------------------------------------------------------------------------------- /tests/typedefs.qc: -------------------------------------------------------------------------------- 1 | typedef void(...) ptype; 2 | typedef string(float a) funcsf; 3 | 4 | ptype print = #1; 5 | funcsf ftos = #2; 6 | 7 | void main() { 8 | typedef float funcsf; 9 | funcsf a; 10 | a = 0; 11 | print("A typedeffed function, 0=", ftos(a), "\n"); 12 | } 13 | -------------------------------------------------------------------------------- /tests/typedefs.tmpl: -------------------------------------------------------------------------------- 1 | I: typedefs.qc 2 | D: typedefs 3 | T: -execute 4 | C: -std=fteqcc 5 | M: A typedeffed function, 0=0 6 | F: -no-defs 7 | -------------------------------------------------------------------------------- /tests/uninit.qc: -------------------------------------------------------------------------------- 1 | vector main(float a, vector vin) { 2 | vector v; 3 | 4 | if (a < 4) { 5 | v = vin; 6 | v_x += 1; 7 | v_y += 1; 8 | v_z += 1; 9 | return v; 10 | } 11 | else if (a < 5) { 12 | v_x = 3; 13 | v_y = 3; 14 | #ifdef UNINIT 15 | print(vtos(v), "\n"); 16 | #endif 17 | v_z = 3; 18 | return v; 19 | } 20 | return '0 0 0'; 21 | } 22 | -------------------------------------------------------------------------------- /tests/uninit.tmpl: -------------------------------------------------------------------------------- 1 | I: uninit.qc 2 | D: catch another case of unused vector accesses 3 | T: -compile 4 | C: -std=fteqcc -Wall -Werror 5 | -------------------------------------------------------------------------------- /tests/uninit2.tmpl: -------------------------------------------------------------------------------- 1 | I: uninit.qc 2 | D: catch another case of unused vector accesses 3 | T: -fail 4 | C: -std=fteqcc -Wall -Werror -DUNINIT 5 | -------------------------------------------------------------------------------- /tests/utf8-2.tmpl: -------------------------------------------------------------------------------- 1 | I: utf8.qc 2 | D: utf8 3 | T: -execute 4 | C: -std=fteqcc -futf8 5 | M: Sum: ∑ ∑ ∑ 6 | M: Okay 7 | -------------------------------------------------------------------------------- /tests/utf8.qc: -------------------------------------------------------------------------------- 1 | void main() { 2 | print("Sum: \{x2211} "); 3 | print("\{8721} "); 4 | print("∑\n"); 5 | if ('\{x2211}' != '\{8721}' || '\{x2211}' != '∑') 6 | print("Fail!\n"); 7 | else 8 | print("Okay\n"); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /tests/utf8.tmpl: -------------------------------------------------------------------------------- 1 | I: utf8.qc 2 | D: trying to compile utf8 codes without -futf8 3 | T: -fail 4 | C: -std=fteqcc 5 | -------------------------------------------------------------------------------- /tests/var-search-order.qc: -------------------------------------------------------------------------------- 1 | float a = 1000; 2 | float b = 1001; 3 | float c = 1002; 4 | 5 | void test(float b) { 6 | float c = 3002; 7 | print(ftos(a), "\n"); 8 | print(ftos(b), "\n"); 9 | print(ftos(c), "\n"); 10 | { 11 | float b = 4001; 12 | print(ftos(b), "\n"); 13 | } 14 | } 15 | 16 | void main() { 17 | test(2001); 18 | } 19 | -------------------------------------------------------------------------------- /tests/var-search-order.tmpl: -------------------------------------------------------------------------------- 1 | I: var-search-order.qc 2 | D: test variable search order 3 | T: -execute 4 | C: -std=gmqcc 5 | M: 1000 6 | M: 2001 7 | M: 3002 8 | M: 4001 9 | -------------------------------------------------------------------------------- /tests/varargs.qc: -------------------------------------------------------------------------------- 1 | void nbva(float a, string...count) { 2 | print("You gave me ", ftos(count), " additional parameters\n"); 3 | print("First: ", ...(0, string), "\n"); 4 | print("You chose: ", ...(a, string), "\n"); 5 | for (a = 0; a < count; ++a) 6 | print("Vararg ", ftos(a), " = ", ...(a, string), "\n"); 7 | } 8 | 9 | var void unstable(...); 10 | void stability(float a, float b, ...count) 11 | { 12 | print("Got: ", ftos(count), "\n"); 13 | } 14 | 15 | void main() { 16 | nbva(1, "Hello", "You", "There"); 17 | stability(1, 2, 3, 4, 5); 18 | unstable = stability; 19 | unstable(1, 2, 3, 4, 5); 20 | } 21 | -------------------------------------------------------------------------------- /tests/varargs.tmpl: -------------------------------------------------------------------------------- 1 | I: varargs.qc 2 | D: non-builtin vararg support 3 | T: -execute 4 | C: -std=fteqcc -fvariadic-args 5 | M: You gave me 3 additional parameters 6 | M: First: Hello 7 | M: You chose: You 8 | M: Vararg 0 = Hello 9 | M: Vararg 1 = You 10 | M: Vararg 2 = There 11 | M: Got: 3 12 | M: Got: 3 13 | -------------------------------------------------------------------------------- /tests/varargs2.qc: -------------------------------------------------------------------------------- 1 | void past8(float a, float b, float c, float d, ...count) 2 | { 3 | float i; 4 | print("out:"); 5 | for (i = 0; i < count; ++i) 6 | print(" ", ftos(...(i, float)), ""); 7 | print("\n"); 8 | } 9 | 10 | void main() { 11 | past8(1, 2, 3, 4, 10, 20, 30, 40, 50, 60, 70, 80); 12 | } 13 | -------------------------------------------------------------------------------- /tests/varargs2.tmpl: -------------------------------------------------------------------------------- 1 | I: varargs2.qc 2 | D: non-builtin vararg support test 2 3 | T: -execute 4 | C: -std=fteqcc -fvariadic-args 5 | M: out: 10 20 30 40 50 60 70 80 6 | -------------------------------------------------------------------------------- /tests/variadic.qc: -------------------------------------------------------------------------------- 1 | void() main = { 2 | print("hello", " world"); 3 | } 4 | -------------------------------------------------------------------------------- /tests/variadic.tmpl: -------------------------------------------------------------------------------- 1 | I: variadic.qc 2 | D: test variadic arguments 3 | T: -execute 4 | C: -std=gmqcc 5 | E: $null 6 | M: hello world 7 | -------------------------------------------------------------------------------- /tests/vec_ops.qc: -------------------------------------------------------------------------------- 1 | void main(vector v) { 2 | print(vtos(v), "\n"); 3 | v /= 2; 4 | print(vtos(v), "\n"); 5 | print(vtos(v / 2), "\n"); 6 | print(vtos(v), "\n"); 7 | print(vtos(v | 16), "\n"); 8 | print(vtos(v & 16), "\n"); 9 | print(vtos(v | '25 42 51'), "\n"); 10 | print(vtos(v & '25 42 51'), "\n"); 11 | print(vtos(v >< '3 2 1')); 12 | } 13 | -------------------------------------------------------------------------------- /tests/vec_ops.tmpl: -------------------------------------------------------------------------------- 1 | I: vec_ops.qc 2 | D: some additional vector operations 3 | T: -execute 4 | C: -std=gmqcc 5 | E: -vector "8 16 32" 6 | M: '8 16 32' 7 | M: '4 8 16' 8 | M: '2 4 8' 9 | M: '4 8 16' 10 | M: '20 24 16' 11 | M: '0 0 16' 12 | M: '29 42 51' 13 | M: '0 8 16' 14 | M: '-24 44 -16' 15 | -------------------------------------------------------------------------------- /tests/vecfields-broken.tmpl: -------------------------------------------------------------------------------- 1 | I: vecfields.qc 2 | D: vector field member accesses 3 | T: -fail 4 | C: -std=gmqcc -fftepp -DBROKEN_ACCESS 5 | -------------------------------------------------------------------------------- /tests/vecfields.qc: -------------------------------------------------------------------------------- 1 | .vector v1; 2 | 3 | float set(entity e, float v) { 4 | e.v1.y = v; 5 | return e.v1.y; 6 | } 7 | 8 | void main() { 9 | entity e = spawn(); 10 | e.v1 = '1 2 3'; 11 | print(ftos(set(e, 42)), " => "); 12 | print(vtos(e.v1), "\n"); 13 | 14 | #ifdef BROKEN_ACCESS 15 | (e.v1 = '0 0 0').x += 1; 16 | #endif 17 | } 18 | -------------------------------------------------------------------------------- /tests/vecfields.tmpl: -------------------------------------------------------------------------------- 1 | I: vecfields.qc 2 | D: vector field member accesses 3 | T: -execute 4 | C: -std=gmqcc -fftepp 5 | M: 42 => '1 42 3' 6 | -------------------------------------------------------------------------------- /tests/vecmath.qc: -------------------------------------------------------------------------------- 1 | void main(vector vin) { 2 | stov("'15 43 0'"); // set OFS_RETURN 3 | vector v2 = -vin; 4 | print(vtos(v2), "\n"); 5 | } 6 | -------------------------------------------------------------------------------- /tests/vecmath.tmpl: -------------------------------------------------------------------------------- 1 | I: vecmath.qc 2 | D: previously problematic vector math 3 | T: -execute 4 | C: -std=gmqcc -fftepp 5 | E: -vector '5 5 5' 6 | M: '-5 -5 -5' 7 | -------------------------------------------------------------------------------- /tests/vector-init.qc: -------------------------------------------------------------------------------- 1 | void main() { 2 | vector v; 3 | 4 | //v_x = 0; 5 | //v_y = 0; 6 | //v_z = 0; 7 | #ifdef INIT_BY_VECTOR 8 | v = '3 4 5'; 9 | #elifdef INIT_BY_COMPONENTS 10 | v_x = 3; 11 | v_y = 4; 12 | v_z = 5; 13 | #elifdef INIT_PARTS 14 | v_z = 5; 15 | #endif 16 | 17 | print(ftos(v_y), "\n"); 18 | print(ftos(v_x), "\n"); 19 | print(ftos(v_z), "\n"); 20 | print(vtos(v), "\n"); 21 | } 22 | -------------------------------------------------------------------------------- /tests/vector-init.tmpl: -------------------------------------------------------------------------------- 1 | I: vector-init.qc 2 | D: vector init as a whole 3 | T: -compile 4 | C: -std=fteqcc -Wall -Werror -DINIT_BY_VECTOR 5 | -------------------------------------------------------------------------------- /tests/vector-init2.tmpl: -------------------------------------------------------------------------------- 1 | I: vector-init.qc 2 | D: vector init by components 3 | T: -compile 4 | C: -std=fteqcc -Wall -Werror -DINIT_BY_COMPONENTS 5 | -------------------------------------------------------------------------------- /tests/vector-init3.tmpl: -------------------------------------------------------------------------------- 1 | I: vector-init.qc 2 | D: only partial vector init 3 | T: -fail 4 | C: -std=fteqcc -Wall -Werror -DINIT_PARTS 5 | -------------------------------------------------------------------------------- /tests/xor-optimized.tmpl: -------------------------------------------------------------------------------- 1 | I: xor.qc 2 | D: test bitwise xor 3 | T: -execute 4 | C: -std=gmqcc -O3 5 | E: $null 6 | M: 6 7 | M: 8 8 | M: commutative 9 | M: assocative 10 | M: inverse 11 | M: vv: '6 8 6' 12 | M: vf: '15 8 15' 13 | M: vv: '6 8 6' 14 | M: vf: '15 8 15' 15 | M: 100:200 swapped is: 200:100 16 | M: '1 2 3':'4 5 6' swapped is: '4 5 6':'1 2 3' 17 | M: '4 7 6' 18 | M: '4 7 6' 19 | M: '4 7 6' 20 | M: '4 7 6' 21 | M: '4 7 6' 22 | -------------------------------------------------------------------------------- /tests/xor.qc: -------------------------------------------------------------------------------- 1 | vector swap(float x, float y) { 2 | vector ret = '0 0 0'; 3 | // everyone knows this trick 4 | ret.x = x; 5 | ret.y = y; 6 | 7 | ret.x = ret.x ^ ret.y; 8 | ret.y = ret.y ^ ret.x; 9 | ret.x = ret.x ^ ret.y; 10 | 11 | return ret; 12 | } 13 | 14 | float f(vector b) { 15 | return b.x+b.y+b.z; 16 | } 17 | 18 | void main() { 19 | float x = 5; 20 | float y = 3; 21 | float z = x ^ y; // 6 22 | 23 | float a = 2; 24 | float b = 10; 25 | float c = a ^ b; // 8 26 | 27 | print(ftos(z), "\n"); 28 | print(ftos(c), "\n"); 29 | 30 | // commutative? 31 | if (x ^ y == y ^ x) 32 | print("commutative\n"); 33 | 34 | // assocative? 35 | if (x ^ (y ^ z) == (x ^ y) ^ z) 36 | print("assocative\n"); 37 | 38 | // elements are their own inverse? 39 | if (x ^ 0 == x) 40 | print("inverse\n"); 41 | 42 | // vector ^ vector 43 | // vector ^ float 44 | // are legal in constant expressions (currently) 45 | vector v1 = '5 2 5'; 46 | vector v2 = '3 10 3'; 47 | 48 | print("vv: ", vtos(v1 ^ v2), "\n"); 49 | print("vf: ", vtos(v1 ^ 10), "\n"); 50 | 51 | const vector v3 = '5 2 5' ^ '3 10 3'; 52 | const vector v4 = '5 2 5' ^ 10; 53 | 54 | print("vv: ", vtos(v3), "\n"); 55 | print("vf: ", vtos(v4), "\n"); 56 | 57 | // good olde xor swap test too 58 | float swap_x = 100; 59 | float swap_y = 200; 60 | vector swaps = swap(swap_x, swap_y); 61 | print("100:200 swapped is: ", ftos(swaps.x), ":", ftos(swaps.y), "\n"); 62 | 63 | // good olde xor swap test too 64 | vector swap_u = '1 2 3'; 65 | vector swap_v = '4 5 6'; 66 | swap_u ^= swap_v; 67 | swap_v ^= swap_u; 68 | swap_u ^= swap_v; 69 | print("'1 2 3':'4 5 6' swapped is: ", vtos(swap_u), ":", vtos(swap_v), "\n"); 70 | 71 | // the one that showed us overlap bugs 72 | print(vtos('1 2 3' ^ f('3 2 1') ^ f('1 1 1')), "\n"); 73 | print(vtos('1 2 3' ^ f('3 2 1') ^ 3), "\n"); 74 | print(vtos('1 2 3' ^ 6 ^ 3), "\n"); 75 | print(vtos('1 2 3' ^ 6 ^ f('1 1 1')), "\n"); 76 | print(vtos('1 2 3' ^ 5), "\n"); 77 | } 78 | -------------------------------------------------------------------------------- /tests/xor.tmpl: -------------------------------------------------------------------------------- 1 | I: xor.qc 2 | D: test bitwise xor 3 | T: -execute 4 | C: -std=gmqcc 5 | E: $null 6 | M: 6 7 | M: 8 8 | M: commutative 9 | M: assocative 10 | M: inverse 11 | M: vv: '6 8 6' 12 | M: vf: '15 8 15' 13 | M: vv: '6 8 6' 14 | M: vf: '15 8 15' 15 | M: 100:200 swapped is: 200:100 16 | M: '1 2 3':'4 5 6' swapped is: '4 5 6':'1 2 3' 17 | M: '4 7 6' 18 | M: '4 7 6' 19 | M: '4 7 6' 20 | M: '4 7 6' 21 | M: '4 7 6' 22 | -------------------------------------------------------------------------------- /utf8.cpp: -------------------------------------------------------------------------------- 1 | #include "gmqcc.h" 2 | 3 | /* 4 | * Based on the flexible and economical utf8 decoder: 5 | * http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ 6 | * 7 | * This is slightly more economical, the fastest way to decode utf8 is 8 | * with a lookup table as in: 9 | * 10 | * first 1-byte lookup 11 | * if that fails, 2-byte lookup 12 | * if that fails, 3-byte lookup 13 | * if that fails, 4-byte lookup 14 | * 15 | * The following table can be generated with some interval trickery. 16 | * consider an interval [a, b): 17 | * 18 | * a must be 0x80 or b must be 0xc0, lower 3 bits 19 | * are clear, thus: 20 | * interval(a,b) = ((uint32_t)((a==0x80?0x40-b:-a)<<23)) 21 | * 22 | * The failstate can be represented as interval(0x80,0x80), it's 23 | * odd to see but this is a full state machine. 24 | * 25 | * The table than maps the corresponding sections as a serise of 26 | * intervals. 27 | * 28 | * In this table the transition values are pre-multiplied with 16 to 29 | * save a shift instruction for every byte, we throw away fillers 30 | * which makes the table smaller. 31 | * 32 | * The first section of the table handles bytes with leading C 33 | * The second section of the table handles bytes with leading D 34 | * The third section of the table handles bytes with leading E 35 | * The last section of the table handles bytes with leading F 36 | * 37 | * The values themselfs in the table are arranged so that when you 38 | * left shift them by 6 to shift continuation characters into place, the 39 | * new top bits tell you: 40 | * 41 | * 1 - if you keep going 42 | * 2 - the range of valid values for the next byte 43 | */ 44 | static const uint32_t utf8_tab[] = { 45 | 0xC0000002, 0xC0000003, 0xC0000004, 0xC0000005, 0xC0000006, 46 | 0xC0000007, 0xC0000008, 0xC0000009, 0xC000000A, 0xC000000B, 47 | 0xC000000C, 0xC000000D, 0xC000000E, 0xC000000F, 0xC0000010, 48 | 0xC0000011, 0xC0000012, 0xC0000013, 0xC0000014, 0xC0000015, 49 | 0xC0000016, 0xC0000017, 0xC0000018, 0xC0000019, 0xC000001A, 50 | 0xC000001B, 0xC000001C, 0xC000001D, 0xC000001E, 0xC000001F, 51 | 0xB3000000, 0xC3000001, 0xC3000002, 0xC3000003, 0xC3000004, 52 | 0xC3000005, 0xC3000006, 0xC3000007, 0xC3000008, 0xC3000009, 53 | 0xC300000A, 0xC300000B, 0xC300000C, 0xD300000D, 0xC300000E, 54 | 0xC300000F, 0xBB0C0000, 0xC30C0001, 0xC30C0002, 0xC30C0003, 55 | 0xD30C0004 56 | }; 57 | 58 | int utf8_from(char *s, utf8ch_t ch) { 59 | if (!s) 60 | return 0; 61 | 62 | if ((unsigned)ch < 0x80) { 63 | *s = ch; 64 | return 1; 65 | } else if ((unsigned)ch < 0x800) { 66 | *s++ = 0xC0 | (ch >> 6); 67 | *s = 0x80 | (ch & 0x3F); 68 | return 2; 69 | } else if ((unsigned)ch < 0xD800 || (unsigned)ch - 0xE000 < 0x2000) { 70 | *s++ = 0xE0 | (ch >> 12); 71 | *s++ = 0x80 | ((ch >> 6) & 0x3F); 72 | *s = 0x80 | (ch & 0x3F); 73 | return 3; 74 | } else if ((unsigned)ch - 0x10000 < 0x100000) { 75 | *s++ = 0xF0 | (ch >> 18); 76 | *s++ = 0x80 | ((ch >> 12) & 0x3F); 77 | *s++ = 0x80 | ((ch >> 6) & 0x3F); 78 | *s = 0x80 | (ch & 0x3F); 79 | return 4; 80 | } 81 | return 0; 82 | } 83 | 84 | int utf8_to(utf8ch_t *i, const unsigned char *s, size_t n) { 85 | unsigned c,j; 86 | 87 | if (!s || !n) 88 | return 0; 89 | 90 | /* This is consistent with mbtowc behaviour. */ 91 | if (!i) 92 | i = (utf8ch_t*)(void*)&i; 93 | 94 | if (*s < 0x80) 95 | return !!(*i = *s); 96 | if (*s-0xC2U > 0x32) 97 | return 0; 98 | 99 | c = utf8_tab[*s++-0xC2U]; 100 | 101 | /* 102 | * Avoid excessive checks against n. 103 | * 104 | * When shifting state `n-1` times does not clear the high bit, 105 | * then the value of `n` won't satisfy the condition to read a 106 | * character as it will be insufficent. 107 | */ 108 | if (n < 4 && ((c<<(6*n-6)) & (1U << 31))) 109 | return 0; 110 | 111 | /* 112 | * The upper 6 state bits are negitive integer offset to a bound-check 113 | * next byte equivlant to: ((b-0x80)+(b+offset))&~0x3f 114 | */ 115 | if ((((*s>>3)-0x10)|((*s>>3)+((int32_t)c>>26))) & ~7) 116 | return 0; 117 | 118 | for (j=2; j<3; j++) { 119 | if (!((c = c<<6 | (*s++-0x80))&(1U<<31))) { 120 | *i = c; 121 | return j; 122 | } 123 | if (*s-0x80U >= 0x40) 124 | return 0; 125 | } 126 | 127 | *i = c<<6 | (*s++-0x80); 128 | return 4; 129 | } 130 | --------------------------------------------------------------------------------