From 006916a0a68a2752549f5f7128c57d6951be1dc5 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Wed, 8 Apr 2020 00:19:49 +0200 Subject: [PATCH] - added the script compiler's front end. --- source/CMakeLists.txt | 16 +- source/common/scripting/core/imports.cpp | 253 + source/common/scripting/core/symbols.h | 6 + source/common/scripting/frontend/ast.cpp | 982 +++ .../common/scripting/frontend/zcc-parse.lemon | 2129 +++++++ .../common/scripting/frontend/zcc_compile.cpp | 3039 +++++++++ .../common/scripting/frontend/zcc_compile.h | 173 + .../common/scripting/frontend/zcc_exprlist.h | 76 + .../common/scripting/frontend/zcc_parser.cpp | 1381 +++++ source/common/scripting/frontend/zcc_parser.h | 621 ++ tools/lemon/CMakeLists.txt | 14 + tools/lemon/lemon.c | 5449 +++++++++++++++++ tools/lemon/lemon.html | 861 +++ tools/lemon/lempar.c | 972 +++ 14 files changed, 15971 insertions(+), 1 deletion(-) create mode 100644 source/common/scripting/core/imports.cpp create mode 100644 source/common/scripting/frontend/ast.cpp create mode 100644 source/common/scripting/frontend/zcc-parse.lemon create mode 100644 source/common/scripting/frontend/zcc_compile.cpp create mode 100644 source/common/scripting/frontend/zcc_compile.h create mode 100644 source/common/scripting/frontend/zcc_exprlist.h create mode 100644 source/common/scripting/frontend/zcc_parser.cpp create mode 100644 source/common/scripting/frontend/zcc_parser.h create mode 100644 tools/lemon/CMakeLists.txt create mode 100644 tools/lemon/lemon.c create mode 100644 tools/lemon/lemon.html create mode 100644 tools/lemon/lempar.c diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index c90c87c5f..bb7c054dc 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -197,7 +197,6 @@ if( NO_OPENAL ) add_definitions( -DNO_OPENAL=1 ) endif() - # Decide on SSE setup # SSE only matters on 32-bit targets. We check compiler flags to know if we can do it. @@ -561,6 +560,10 @@ else() endif( NOT NO_OPENMP ) endif() +add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/zcc-parse.c ${CMAKE_CURRENT_BINARY_DIR}/zcc-parse.h + COMMAND lemon -C${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/common/scripting/frontend/zcc-parse.lemon + DEPENDS lemon ${CMAKE_CURRENT_SOURCE_DIR}/common/scripting/frontend/zcc-parse.lemon ) + add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/sc_man_scanner.h COMMAND re2c --no-generation-date -s -o ${CMAKE_CURRENT_BINARY_DIR}/sc_man_scanner.h ${CMAKE_CURRENT_SOURCE_DIR}/common/engine/sc_man_scanner.re DEPENDS re2c ${CMAKE_CURRENT_SOURCE_DIR}/common/engine/sc_man_scanner.re ) @@ -617,6 +620,7 @@ file( GLOB HEADER_FILES common/scripting/jit/*h common/scripting/interface/*.h common/scripting/backend/*.h + common/scripting/frontend/*.h build/src/*.h platform/win32/*.h @@ -635,6 +639,9 @@ set( NOT_COMPILED_SOURCE_FILES ${OTHER_SYSTEM_SOURCES} sc_man_scanner.h common/engine/sc_man_scanner.re + common/scripting/frontend/zcc-parse.lemon + zcc-parse.c + zcc-parse.h platform/win32/zutil.natvis ) @@ -797,9 +804,14 @@ set (PCH_SOURCES common/scripting/core/types.cpp common/scripting/core/scopebarrier.cpp common/scripting/core/vmdisasm.cpp + common/scripting/core/imports.cpp common/scripting/vm/vmexec.cpp common/scripting/vm/vmframe.cpp common/scripting/interface/stringformat.cpp + #common/scripting/interface/exports.cpp + common/scripting/frontend/ast.cpp + common/scripting/frontend/zcc_compile.cpp + common/scripting/frontend/zcc_parser.cpp common/scripting/backend/vmbuilder.cpp common/scripting/backend/codegen.cpp @@ -965,6 +977,7 @@ include_directories( common/scripting/jit common/scripting/core common/scripting/interface + common/scripting/frontend common/scripting/backend ${CMAKE_BINARY_DIR}/libraries/gdtoa @@ -1083,6 +1096,7 @@ source_group("Common\\Fonts" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/co source_group("Common\\File System" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/filesystem/.+") source_group("Common\\Scripting" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/scripting/.+") source_group("Common\\Scripting\\Interface" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/scripting/interface/.+") +source_group("Common\\Scripting\\Frontend" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/scripting/frontend/.+" FILES ${CMAKE_CURRENT_BINARY_DIR}/zcc-parse.c ${CMAKE_CURRENT_BINARY_DIR}/zcc-parse.h) source_group("Common\\Scripting\\Backend" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/scripting/backend/.+") source_group("Common\\Scripting\\Core" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/scripting/core/.+") source_group("Common\\Scripting\\JIT" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/scripting/jit/.+") diff --git a/source/common/scripting/core/imports.cpp b/source/common/scripting/core/imports.cpp new file mode 100644 index 000000000..bbb0146d0 --- /dev/null +++ b/source/common/scripting/core/imports.cpp @@ -0,0 +1,253 @@ +/* +** thingdef_data.cpp +** +** DECORATE data tables +** +**--------------------------------------------------------------------------- +** Copyright 2002-2020 Christoph Oelckers +** Copyright 2004-2008 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** 4. When not used as part of ZDoom or a ZDoom derivative, this code will be +** covered by the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or (at +** your option) any later version. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "gstrings.h" +#include "v_font.h" +#include "menu/menu.h" +#include "types.h" +#include "dictionary.h" +#include "vm.h" +#include "symbols.h" + +static TArray AFTable; +static TArray FieldTable; + + +//========================================================================== +// +// +// +//========================================================================== + +template +static int CompareClassNames(const char* const aname, const Desc& b) +{ + // ++ to get past the prefix letter of the native class name, which gets omitted by the FName for the class. + const char* bname = b.ClassName; + if ('\0' != *bname) ++bname; + return stricmp(aname, bname); +} + +template +static int CompareClassNames(const Desc& a, const Desc& b) +{ + // ++ to get past the prefix letter of the native class name, which gets omitted by the FName for the class. + const char* aname = a.ClassName; + if ('\0' != *aname) ++aname; + return CompareClassNames(aname, b); +} + +//========================================================================== +// +// Find a function by name using a binary search +// +//========================================================================== + +AFuncDesc *FindFunction(PContainerType *cls, const char * string) +{ + int min = 0, max = AFTable.Size() - 1; + + while (min <= max) + { + int mid = (min + max) / 2; + int lexval = CompareClassNames(cls->TypeName.GetChars(), AFTable[mid]); + if (lexval == 0) lexval = stricmp(string, AFTable[mid].FuncName); + if (lexval == 0) + { + return &AFTable[mid]; + } + else if (lexval > 0) + { + min = mid + 1; + } + else + { + max = mid - 1; + } + } + return nullptr; +} + +//========================================================================== +// +// Find a function by name using a binary search +// +//========================================================================== + +FieldDesc *FindField(PContainerType *cls, const char * string) +{ + int min = 0, max = FieldTable.Size() - 1; + const char * cname = cls ? cls->TypeName.GetChars() : ""; + + while (min <= max) + { + int mid = (min + max) / 2; + int lexval = CompareClassNames(cname, FieldTable[mid]); + if (lexval == 0) lexval = stricmp(string, FieldTable[mid].FieldName); + if (lexval == 0) + { + return &FieldTable[mid]; + } + else if (lexval > 0) + { + min = mid + 1; + } + else + { + max = mid - 1; + } + } + return nullptr; +} + + +//========================================================================== +// +// Find an action function in AActor's table +// +//========================================================================== + +VMFunction *FindVMFunction(PClass *cls, const char *name) +{ + auto f = dyn_cast(cls->FindSymbol(name, true)); + return f == nullptr ? nullptr : f->Variants[0].Implementation; +} + + +//========================================================================== +// +// Sorting helpers +// +//========================================================================== + +static int funccmp(const void * a, const void * b) +{ + int res = CompareClassNames(*(AFuncDesc*)a, *(AFuncDesc*)b); + if (res == 0) res = stricmp(((AFuncDesc*)a)->FuncName, ((AFuncDesc*)b)->FuncName); + return res; +} + +static int fieldcmp(const void * a, const void * b) +{ + int res = CompareClassNames(*(FieldDesc*)a, *(FieldDesc*)b); + if (res == 0) res = stricmp(((FieldDesc*)a)->FieldName, ((FieldDesc*)b)->FieldName); + return res; +} + +//========================================================================== +// +// Initialization +// +//========================================================================== + +void InitImports() +{ + auto fontstruct = NewStruct("FFont", nullptr, true); + fontstruct->Size = sizeof(FFont); + fontstruct->Align = alignof(FFont); + NewPointer(fontstruct, false)->InstallHandlers( + [](FSerializer &ar, const char *key, const void *addr) + { + ar(key, *(FFont **)addr); + }, + [](FSerializer &ar, const char *key, void *addr) + { + Serialize(ar, key, *(FFont **)addr, nullptr); + return true; + } + ); + + // Create a sorted list of native action functions + AFTable.Clear(); + if (AFTable.Size() == 0) + { + FAutoSegIterator probe(ARegHead, ARegTail); + + while (*++probe != NULL) + { + AFuncDesc *afunc = (AFuncDesc *)*probe; + assert(afunc->VMPointer != NULL); + *(afunc->VMPointer) = new VMNativeFunction(afunc->Function, afunc->FuncName); + (*(afunc->VMPointer))->PrintableName.Format("%s.%s [Native]", afunc->ClassName+1, afunc->FuncName); + (*(afunc->VMPointer))->DirectNativeCall = afunc->DirectNative; + AFTable.Push(*afunc); + } + AFTable.ShrinkToFit(); + qsort(&AFTable[0], AFTable.Size(), sizeof(AFTable[0]), funccmp); + } + + FieldTable.Clear(); + if (FieldTable.Size() == 0) + { + FAutoSegIterator probe(FRegHead, FRegTail); + + while (*++probe != NULL) + { + FieldDesc *afield = (FieldDesc *)*probe; + FieldTable.Push(*afield); + } + FieldTable.ShrinkToFit(); + qsort(&FieldTable[0], FieldTable.Size(), sizeof(FieldTable[0]), fieldcmp); + } +} + +//========================================================================== +// +// SetImplicitArgs +// +// Adds the parameters implied by the function flags. +// +//========================================================================== + +void SetImplicitArgs(TArray *args, TArray *argflags, TArray *argnames, PContainerType *cls, uint32_t funcflags, int useflags) +{ + // Must be called before adding any other arguments. + assert(args == nullptr || args->Size() == 0); + assert(argflags == nullptr || argflags->Size() == 0); + + if (funcflags & VARF_Method) + { + // implied self pointer + if (args != nullptr) args->Push(NewPointer(cls, !!(funcflags & VARF_ReadOnly))); + if (argflags != nullptr) argflags->Push(VARF_Implicit | VARF_ReadOnly); + if (argnames != nullptr) argnames->Push(NAME_self); + } +} + diff --git a/source/common/scripting/core/symbols.h b/source/common/scripting/core/symbols.h index 2ae194e89..38412c078 100644 --- a/source/common/scripting/core/symbols.h +++ b/source/common/scripting/core/symbols.h @@ -267,3 +267,9 @@ struct FNamespaceManager extern FNamespaceManager Namespaces; void RemoveUnusedSymbols(); + +struct AFuncDesc; +struct FieldDesc; +AFuncDesc *FindFunction(PContainerType *cls, const char * string); +FieldDesc *FindField(PContainerType *cls, const char * string); +void SetImplicitArgs(TArray* args, TArray* argflags, TArray* argnames, PContainerType* cls, uint32_t funcflags, int useflags); diff --git a/source/common/scripting/frontend/ast.cpp b/source/common/scripting/frontend/ast.cpp new file mode 100644 index 000000000..ae198dc84 --- /dev/null +++ b/source/common/scripting/frontend/ast.cpp @@ -0,0 +1,982 @@ +/* +** ast.cpp +** +**--------------------------------------------------------------------------- +** Copyright -2016 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "dobject.h" +#include "vmintern.h" +#include "types.h" +#include "zcc_parser.h" +#include "zcc-parse.h" +#include "printf.h" + +class FLispString; +extern void (* const TreeNodePrinter[NUM_AST_NODE_TYPES])(FLispString &, ZCC_TreeNode *); + +static const char *BuiltInTypeNames[] = +{ + "sint8", "uint8", + "sint16", "uint16", + "sint32", "uint32_t", + "intauto", + + "bool", + "float64", "floatauto", + "string", + "vector2", + "vector3", + "name", + + "color", + "state", + "sound", + + "usertype", + "nativetype", + "let", +}; + +class FLispString +{ +public: + operator FString &() { return Str; } + + FLispString() + { + NestDepth = Column = 0; + WrapWidth = 200; + NeedSpace = false; + ConsecOpens = 0; + } + + void Open(const char *label) + { + size_t labellen = label != NULL ? strlen(label) : 0; + CheckWrap(labellen + 1 + NeedSpace); + if (NeedSpace) + { + Str << ' '; + ConsecOpens = 0; + } + Str << '('; + ConsecOpens++; + if (label != NULL) + { + Str.AppendCStrPart(label, labellen); + } + Column += labellen + 1 + NeedSpace; + NestDepth++; + NeedSpace = (label != NULL); + } + void Close() + { + assert(NestDepth != 0); + Str << ')'; + Column++; + NestDepth--; + NeedSpace = true; + } + void Break() + { + // Don't break if not needed. + if (Column != NestDepth) + { + if (NeedSpace) + { + ConsecOpens = 0; + } + else + { // Move hanging ( characters to the new line + Str.Truncate(Str.Len() - ConsecOpens); + NestDepth -= ConsecOpens; + } + Str << '\n'; + Column = NestDepth; + NeedSpace = false; + if (NestDepth > 0) + { + Str.AppendFormat("%*s", (int)NestDepth, ""); + } + if (ConsecOpens > 0) + { + for (size_t i = 0; i < ConsecOpens; ++i) + { + Str << '('; + } + NestDepth += ConsecOpens; + } + } + } + bool CheckWrap(size_t len) + { + if (len + Column > WrapWidth) + { + Break(); + return true; + } + return false; + } + void Add(const char *str, size_t len) + { + CheckWrap(len + NeedSpace); + if (NeedSpace) + { + Str << ' '; + } + Str.AppendCStrPart(str, len); + Column += len + NeedSpace; + NeedSpace = true; + } + void Add(const char *str) + { + Add(str, strlen(str)); + } + void Add(FString &str) + { + Add(str.GetChars(), str.Len()); + } + void AddName(FName name) + { + size_t namelen = strlen(name.GetChars()); + CheckWrap(namelen + 2 + NeedSpace); + if (NeedSpace) + { + NeedSpace = false; + Str << ' '; + } + Str << '\'' << name.GetChars() << '\''; + Column += namelen + 2 + NeedSpace; + NeedSpace = true; + } + void AddChar(char c) + { + Add(&c, 1); + } + void AddInt(int i, bool un=false) + { + char buf[16]; + size_t len; + if (!un) + { + len = mysnprintf(buf, countof(buf), "%d", i); + } + else + { + len = mysnprintf(buf, countof(buf), "%uu", i); + } + Add(buf, len); + } + void AddHex(unsigned x) + { + char buf[10]; + size_t len = mysnprintf(buf, countof(buf), "%08x", x); + Add(buf, len); + } + void AddFloat(double f, bool single) + { + char buf[32]; + size_t len = mysnprintf(buf, countof(buf), "%.4f", f); + if (single) + { + buf[len++] = 'f'; + buf[len] = '\0'; + } + Add(buf, len); + } +private: + FString Str; + size_t NestDepth; + size_t Column; + size_t WrapWidth; + size_t ConsecOpens; + bool NeedSpace; +}; + +static void PrintNode(FLispString &out, ZCC_TreeNode *node) +{ + assert(TreeNodePrinter[NUM_AST_NODE_TYPES-1] != NULL); + if (node->NodeType >= 0 && node->NodeType < NUM_AST_NODE_TYPES) + { + TreeNodePrinter[node->NodeType](out, node); + } + else + { + out.Open("unknown-node-type"); + out.AddInt(node->NodeType); + out.Close(); + } +} + +static void PrintNodes(FLispString &out, ZCC_TreeNode *node, bool newlist=true, bool addbreaks=false) +{ + ZCC_TreeNode *p; + + if (node == NULL) + { + out.Add("nil", 3); + } + else + { + if (newlist) + { + out.Open(NULL); + } + p = node; + do + { + if (addbreaks) + { + out.Break(); + } + PrintNode(out, p); + p = p->SiblingNext; + } while (p != node); + if (newlist) + { + out.Close(); + } + } +} + +static void PrintBuiltInType(FLispString &out, EZCCBuiltinType type) +{ + assert(ZCC_NUM_BUILT_IN_TYPES == countof(BuiltInTypeNames)); + if (unsigned(type) >= unsigned(ZCC_NUM_BUILT_IN_TYPES)) + { + char buf[30]; + size_t len = mysnprintf(buf, countof(buf), "bad-type-%u", type); + out.Add(buf, len); + } + else + { + out.Add(BuiltInTypeNames[type]); + } +} + +static void PrintIdentifier(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_Identifier *inode = (ZCC_Identifier *)node; + out.Open("identifier"); + out.AddName(inode->Id); + out.Close(); +} + +static void PrintStringConst(FLispString &out, FString str) +{ + FString outstr; + outstr << '"'; + for (size_t i = 0; i < str.Len(); ++i) + { + if (str[i] == '"') + { + outstr << "\""; + } + else if (str[i] == '\\') + { + outstr << "\\\\"; + } + else if (str[i] >= 32) + { + outstr << str[i]; + } + else + { + outstr.AppendFormat("\\x%02X", str[i]); + } + } + outstr << '"'; + out.Add(outstr); +} + +static void PrintClass(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_Class *cnode = (ZCC_Class *)node; + out.Break(); + out.Open("class"); + out.AddName(cnode->NodeName); + PrintNodes(out, cnode->ParentName); + PrintNodes(out, cnode->Replaces); + out.AddHex(cnode->Flags); + PrintNodes(out, cnode->Body, false, true); + out.Close(); +} + +static void PrintStruct(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_Struct *snode = (ZCC_Struct *)node; + out.Break(); + out.Open("struct"); + out.AddName(snode->NodeName); + PrintNodes(out, snode->Body, false, true); + out.Close(); +} + +static void PrintProperty(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_Property *snode = (ZCC_Property *)node; + out.Break(); + out.Open("property"); + out.AddName(snode->NodeName); + PrintNodes(out, snode->Body, false, true); + out.Close(); +} + +static void PrintFlagDef(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_FlagDef *snode = (ZCC_FlagDef *)node; + out.Break(); + out.Open("flagdef"); + out.AddName(snode->NodeName); + out.AddName(snode->RefName); + out.AddInt(snode->BitValue); + out.Close(); +} + +static void PrintStaticArrayState(FLispString &out, ZCC_TreeNode *node) +{ + auto *snode = (ZCC_StaticArrayStatement *)node; + out.Break(); + out.Open("static-array"); + out.AddName(snode->Id); + PrintNodes(out, snode->Values, false, true); + out.Close(); +} + +static void PrintEnum(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_Enum *enode = (ZCC_Enum *)node; + out.Break(); + out.Open("enum"); + out.AddName(enode->NodeName); + PrintBuiltInType(out, enode->EnumType); + out.Add(enode->Elements == NULL ? "nil" : "...", 3); + out.Close(); +} + +static void PrintEnumTerminator(FLispString &out, ZCC_TreeNode *node) +{ + out.Open("enum-term"); + out.Close(); +} + +static void PrintStates(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_States *snode = (ZCC_States *)node; + out.Break(); + out.Open("states"); + PrintNodes(out, snode->Flags, false, true); + PrintNodes(out, snode->Body, false, true); + out.Close(); +} + +static void PrintStatePart(FLispString &out, ZCC_TreeNode *node) +{ + out.Open("state-part"); + out.Close(); +} + +static void PrintStateLabel(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_StateLabel *snode = (ZCC_StateLabel *)node; + out.Open("state-label"); + out.AddName(snode->Label); + out.Close(); +} + +static void PrintStateStop(FLispString &out, ZCC_TreeNode *node) +{ + out.Open("state-stop"); + out.Close(); +} + +static void PrintStateWait(FLispString &out, ZCC_TreeNode *node) +{ + out.Open("state-wait"); + out.Close(); +} + +static void PrintStateFail(FLispString &out, ZCC_TreeNode *node) +{ + out.Open("state-fail"); + out.Close(); +} + +static void PrintStateLoop(FLispString &out, ZCC_TreeNode *node) +{ + out.Open("state-loop"); + out.Close(); +} + +static void PrintStateGoto(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_StateGoto *snode = (ZCC_StateGoto *)node; + out.Open("state-goto"); + PrintNodes(out, snode->Qualifier); + PrintNodes(out, snode->Label); + PrintNodes(out, snode->Offset); + out.Close(); +} + +static void PrintStateLine(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_StateLine *snode = (ZCC_StateLine *)node; + out.Open("state-line"); + out.Add(*(snode->Sprite)); + PrintNodes(out, snode->Duration); + if (snode->bNoDelay) out.Add("nodelay", 7); + if (snode->bBright) out.Add("bright", 6); + if (snode->bFast) out.Add("fast", 4); + if (snode->bSlow) out.Add("slow", 4); + if (snode->bCanRaise) out.Add("canraise", 8); + out.Add(*(snode->Frames)); + PrintNodes(out, snode->Offset); + PrintNodes(out, snode->Action, false); + out.Close(); +} + +static void PrintVarName(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_VarName *vnode = (ZCC_VarName *)node; + out.Open("var-name"); + PrintNodes(out, vnode->ArraySize); + out.AddName(vnode->Name); + out.Close(); +} + +static void PrintVarInit(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_VarInit *vnode = (ZCC_VarInit *)node; + out.Open("var-init"); + PrintNodes(out, vnode->ArraySize); + PrintNodes(out, vnode->Init); + if (vnode->InitIsArray) out.Add("array", 5); + out.AddName(vnode->Name); + out.Close(); +} + +static void PrintType(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_Type *tnode = (ZCC_Type *)node; + out.Open("bad-type"); + PrintNodes(out, tnode->ArraySize); + out.Close(); +} + +static void PrintBasicType(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_BasicType *tnode = (ZCC_BasicType *)node; + out.Open("basic-type"); + PrintNodes(out, tnode->ArraySize); + PrintBuiltInType(out, tnode->Type); + if (tnode->Type == ZCC_UserType || tnode->Type == ZCC_NativeType) + { + if (tnode->Type == ZCC_NativeType) out.Add("@", 1); + PrintNodes(out, tnode->UserType, false); + } + out.Close(); +} + +static void PrintMapType(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_MapType *tnode = (ZCC_MapType *)node; + out.Open("map-type"); + PrintNodes(out, tnode->ArraySize); + PrintNodes(out, tnode->KeyType); + PrintNodes(out, tnode->ValueType); + out.Close(); +} + +static void PrintDynArrayType(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_DynArrayType *tnode = (ZCC_DynArrayType *)node; + out.Open("dyn-array-type"); + PrintNodes(out, tnode->ArraySize); + PrintNodes(out, tnode->ElementType); + out.Close(); +} + +static void PrintClassType(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_ClassType *tnode = (ZCC_ClassType *)node; + out.Open("class-type"); + PrintNodes(out, tnode->ArraySize); + PrintNodes(out, tnode->Restriction); + out.Close(); +} + +static void OpenExprType(FLispString &out, EZCCExprType type) +{ + char buf[32]; + + if (unsigned(type) < PEX_COUNT_OF) + { + mysnprintf(buf, countof(buf), "expr %d", type); + } + else + { + mysnprintf(buf, countof(buf), "bad-pex-%u", type); + } + out.Open(buf); +} + +static void PrintExpression(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_Expression *enode = (ZCC_Expression *)node; + OpenExprType(out, enode->Operation); + out.Close(); +} + +static void PrintExprID(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_ExprID *enode = (ZCC_ExprID *)node; + assert(enode->Operation == PEX_ID); + out.Open("expr-id"); + out.AddName(enode->Identifier); + out.Close(); +} + +static void PrintExprTypeRef(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_ExprTypeRef *enode = (ZCC_ExprTypeRef *)node; + assert(enode->Operation == PEX_TypeRef); + out.Open("expr-type-ref"); + if (enode->RefType == TypeSInt8) { out.Add("sint8"); } + else if (enode->RefType == TypeUInt8) { out.Add("uint8"); } + else if (enode->RefType == TypeSInt16) { out.Add("sint16"); } + else if (enode->RefType == TypeSInt32) { out.Add("sint32"); } + else if (enode->RefType == TypeFloat32) { out.Add("float32"); } + else if (enode->RefType == TypeFloat64) { out.Add("float64"); } + else if (enode->RefType == TypeString) { out.Add("string"); } + else if (enode->RefType == TypeName) { out.Add("name"); } + else if (enode->RefType == TypeColor) { out.Add("color"); } + else if (enode->RefType == TypeSound) { out.Add("sound"); } + else { out.Add("other"); } + out.Close(); +} + +static void PrintExprConstant(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_ExprConstant *enode = (ZCC_ExprConstant *)node; + assert(enode->Operation == PEX_ConstValue); + out.Open("expr-const"); + if (enode->Type == TypeString) + { + PrintStringConst(out, *enode->StringVal); + } + else if (enode->Type == TypeFloat64) + { + out.AddFloat(enode->DoubleVal, false); + } + else if (enode->Type == TypeFloat32) + { + out.AddFloat(enode->DoubleVal, true); + } + else if (enode->Type == TypeName) + { + out.AddName(ENamedName(enode->IntVal)); + } + else if (enode->Type->isIntCompatible()) + { + out.AddInt(enode->IntVal, static_cast(enode->Type)->Unsigned); + } + out.Close(); +} + +static void PrintExprFuncCall(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_ExprFuncCall *enode = (ZCC_ExprFuncCall *)node; + assert(enode->Operation == PEX_FuncCall); + out.Open("expr-func-call"); + PrintNodes(out, enode->Function); + PrintNodes(out, enode->Parameters, false); + out.Close(); +} + +static void PrintExprClassCast(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_ClassCast *enode = (ZCC_ClassCast *)node; + assert(enode->Operation == PEX_ClassCast); + out.Open("expr-class-cast"); + out.AddName(enode->ClassName); + PrintNodes(out, enode->Parameters, false); + out.Close(); +} + +static void PrintStaticArray(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_StaticArrayStatement *enode = (ZCC_StaticArrayStatement *)node; + out.Open("static-array-stmt"); + PrintNodes(out, enode->Type, false); + out.AddName(enode->Id); + PrintNodes(out, enode->Values, false); + out.Close(); +} + +static void PrintExprMemberAccess(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_ExprMemberAccess *enode = (ZCC_ExprMemberAccess *)node; + assert(enode->Operation == PEX_MemberAccess); + out.Open("expr-member-access"); + PrintNodes(out, enode->Left); + out.AddName(enode->Right); + out.Close(); +} + +static void PrintExprUnary(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_ExprUnary *enode = (ZCC_ExprUnary *)node; + OpenExprType(out, enode->Operation); + PrintNodes(out, enode->Operand, false); + out.Close(); +} + +static void PrintExprBinary(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_ExprBinary *enode = (ZCC_ExprBinary *)node; + OpenExprType(out, enode->Operation); + PrintNodes(out, enode->Left); + PrintNodes(out, enode->Right); + out.Close(); +} + +static void PrintExprTrinary(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_ExprTrinary *enode = (ZCC_ExprTrinary *)node; + OpenExprType(out, enode->Operation); + PrintNodes(out, enode->Test); + PrintNodes(out, enode->Left); + PrintNodes(out, enode->Right); + out.Close(); +} + +static void PrintVectorInitializer(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_VectorValue *enode = (ZCC_VectorValue *)node; + OpenExprType(out, enode->Operation); + PrintNodes(out, enode->X); + PrintNodes(out, enode->Y); + PrintNodes(out, enode->Z); + out.Close(); +} + +static void PrintFuncParam(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_FuncParm *pnode = (ZCC_FuncParm *)node; + out.Break(); + out.Open("func-parm"); + out.AddName(pnode->Label); + PrintNodes(out, pnode->Value, false); + out.Close(); +} + +static void PrintStatement(FLispString &out, ZCC_TreeNode *node) +{ + out.Open("statement"); + out.Close(); +} + +static void PrintCompoundStmt(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_CompoundStmt *snode = (ZCC_CompoundStmt *)node; + out.Break(); + out.Open("compound-stmt"); + PrintNodes(out, snode->Content, false, true); + out.Close(); +} + +static void PrintDefault(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_Default *snode = (ZCC_Default *)node; + out.Break(); + out.Open("default"); + PrintNodes(out, snode->Content, false, true); + out.Close(); +} + +static void PrintContinueStmt(FLispString &out, ZCC_TreeNode *node) +{ + out.Break(); + out.Open("continue-stmt"); + out.Close(); +} + +static void PrintBreakStmt(FLispString &out, ZCC_TreeNode *node) +{ + out.Break(); + out.Open("break-stmt"); + out.Close(); +} + +static void PrintReturnStmt(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_ReturnStmt *snode = (ZCC_ReturnStmt *)node; + out.Break(); + out.Open("return-stmt"); + PrintNodes(out, snode->Values, false); + out.Close(); +} + +static void PrintExpressionStmt(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_ExpressionStmt *snode = (ZCC_ExpressionStmt *)node; + out.Break(); + out.Open("expression-stmt"); + PrintNodes(out, snode->Expression, false); + out.Close(); +} + +static void PrintIterationStmt(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_IterationStmt *snode = (ZCC_IterationStmt *)node; + out.Break(); + out.Open("iteration-stmt"); + out.Add((snode->CheckAt == ZCC_IterationStmt::Start) ? "start" : "end"); + out.Break(); + PrintNodes(out, snode->LoopCondition); + out.Break(); + PrintNodes(out, snode->LoopBumper); + out.Break(); + PrintNodes(out, snode->LoopStatement); + out.Close(); +} + +static void PrintIfStmt(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_IfStmt *snode = (ZCC_IfStmt *)node; + out.Break(); + out.Open("if-stmt"); + PrintNodes(out, snode->Condition); + out.Break(); + PrintNodes(out, snode->TruePath); + out.Break(); + PrintNodes(out, snode->FalsePath); + out.Close(); +} + +static void PrintSwitchStmt(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_SwitchStmt *snode = (ZCC_SwitchStmt *)node; + out.Break(); + out.Open("switch-stmt"); + PrintNodes(out, snode->Condition); + out.Break(); + PrintNodes(out, snode->Content, false); + out.Close(); +} + +static void PrintCaseStmt(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_CaseStmt *snode = (ZCC_CaseStmt *)node; + out.Break(); + out.Open("case-stmt"); + PrintNodes(out, snode->Condition, false); + out.Close(); +} + +static void BadAssignOp(FLispString &out, int op) +{ + char buf[32]; + size_t len = mysnprintf(buf, countof(buf), "assign-op-%d", op); + out.Add(buf, len); +} + +static void PrintAssignStmt(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_AssignStmt *snode = (ZCC_AssignStmt *)node; + out.Open("assign-stmt"); + PrintNodes(out, snode->Dests); + PrintNodes(out, snode->Sources); + out.Close(); +} + +static void PrintLocalVarStmt(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_LocalVarStmt *snode = (ZCC_LocalVarStmt *)node; + out.Open("local-var-stmt"); + PrintNodes(out, snode->Type); + PrintNodes(out, snode->Vars); + out.Close(); +} + +static void PrintFuncParamDecl(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_FuncParamDecl *dnode = (ZCC_FuncParamDecl *)node; + out.Break(); + out.Open("func-param-decl"); + PrintNodes(out, dnode->Type); + out.AddName(dnode->Name); + out.AddHex(dnode->Flags); + PrintNodes(out, dnode->Default); + out.Close(); +} + +static void PrintConstantDef(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_ConstantDef *dnode = (ZCC_ConstantDef *)node; + out.Break(); + out.Open("constant-def"); + out.AddName(dnode->NodeName); + PrintNodes(out, dnode->Value, false); + out.Close(); +} + +static void PrintDeclarator(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_Declarator *dnode = (ZCC_Declarator *)node; + out.Break(); + out.Open("declarator"); + out.AddHex(dnode->Flags); + PrintNodes(out, dnode->Type); + out.Close(); +} + +static void PrintVarDeclarator(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_VarDeclarator *dnode = (ZCC_VarDeclarator *)node; + out.Break(); + out.Open("var-declarator"); + out.AddHex(dnode->Flags); + PrintNodes(out, dnode->Type); + PrintNodes(out, dnode->Names); + out.Close(); +} + +static void PrintFuncDeclarator(FLispString &out, ZCC_TreeNode *node) +{ + ZCC_FuncDeclarator *dnode = (ZCC_FuncDeclarator *)node; + out.Break(); + out.Open("func-declarator"); + out.AddHex(dnode->Flags); + PrintNodes(out, dnode->UseFlags); + PrintNodes(out, dnode->Type); + out.AddName(dnode->Name); + PrintNodes(out, dnode->Params); + PrintNodes(out, dnode->Body, false); + out.Close(); +} + +static void PrintDeclFlags(FLispString &out, ZCC_TreeNode *node) +{ + auto dnode = (ZCC_DeclFlags *)node; + out.Break(); + out.Open("decl-flags"); + out.AddHex(dnode->Flags); + PrintNodes(out, dnode->Id); + out.Close(); +} + +static void PrintFlagStmt(FLispString &out, ZCC_TreeNode *node) +{ + auto dnode = (ZCC_FlagStmt *)node; + out.Break(); + out.Open("flag-stmt"); + PrintNodes(out, dnode->name, false); + out.AddInt(dnode->set); + out.Close(); +} + +static void PrintPropertyStmt(FLispString &out, ZCC_TreeNode *node) +{ + auto dnode = (ZCC_PropertyStmt *)node; + out.Break(); + out.Open("property-stmt"); + PrintNodes(out, dnode->Prop, false); + PrintNodes(out, dnode->Values, false); + out.Close(); +} + +void (* const TreeNodePrinter[NUM_AST_NODE_TYPES])(FLispString &, ZCC_TreeNode *) = +{ + PrintIdentifier, + PrintClass, + PrintStruct, + PrintEnum, + PrintEnumTerminator, + PrintStates, + PrintStatePart, + PrintStateLabel, + PrintStateStop, + PrintStateWait, + PrintStateFail, + PrintStateLoop, + PrintStateGoto, + PrintStateLine, + PrintVarName, + PrintVarInit, + PrintType, + PrintBasicType, + PrintMapType, + PrintDynArrayType, + PrintClassType, + PrintExpression, + PrintExprID, + PrintExprTypeRef, + PrintExprConstant, + PrintExprFuncCall, + PrintExprMemberAccess, + PrintExprUnary, + PrintExprBinary, + PrintExprTrinary, + PrintFuncParam, + PrintStatement, + PrintCompoundStmt, + PrintContinueStmt, + PrintBreakStmt, + PrintReturnStmt, + PrintExpressionStmt, + PrintIterationStmt, + PrintIfStmt, + PrintSwitchStmt, + PrintCaseStmt, + PrintAssignStmt, + PrintLocalVarStmt, + PrintFuncParamDecl, + PrintConstantDef, + PrintDeclarator, + PrintVarDeclarator, + PrintFuncDeclarator, + PrintDefault, + PrintFlagStmt, + PrintPropertyStmt, + PrintVectorInitializer, + PrintDeclFlags, + PrintExprClassCast, + PrintStaticArrayState, + PrintProperty, + PrintFlagDef, +}; + +FString ZCC_PrintAST(ZCC_TreeNode *root) +{ + FLispString out; + PrintNodes(out, root); + return out; +} diff --git a/source/common/scripting/frontend/zcc-parse.lemon b/source/common/scripting/frontend/zcc-parse.lemon new file mode 100644 index 000000000..9bf5932cf --- /dev/null +++ b/source/common/scripting/frontend/zcc-parse.lemon @@ -0,0 +1,2129 @@ +/* +** zcc-parse.lemon +** ZScript parser grammar +** +**--------------------------------------------------------------------------- +** Copyright -2016 Randy Heit +** Copyright 2016-2017 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + + +%stack_size 0 + +%include +{ +// Allocates a new AST node off the parse state's arena. +#define NEW_AST_NODE(type,name,tok) \ + ZCC_##type *name = static_cast(stat->InitNode(sizeof(ZCC_##type), AST_##type)); \ + SetNodeLine(name, tok) + +static void SetNodeLine(ZCC_TreeNode *name, ZCCToken &tok) +{ + name->SourceLoc = tok.SourceLoc; +} + +static void SetNodeLine(ZCC_TreeNode *name, ZCC_TreeNode *node) +{ + if (name == nullptr || node == nullptr) + { + I_Error("Fatal parse error"); + } + name->SourceLoc = node->SourceLoc; +} + +static void SetNodeLine(ZCC_TreeNode *name, int line) +{ + name->SourceLoc = line; +} + +// If a is non-null, appends b to a. Otherwise, sets a to b. +#define SAFE_APPEND(a,b) \ + if (a == NULL) a = b; else AppendTreeNodeSibling(a, b); + +#define UNARY_EXPR(X,T) NEW_AST_NODE(ExprUnary, expr1, X); expr1->Operation = T; expr1->Operand = X; expr1->Type = NULL +#define BINARY_EXPR(X,Y,T) NEW_AST_NODE(ExprBinary, expr2, X); expr2->Operation = T; expr2->Type = NULL; expr2->Left = X; expr2->Right = Y + +#define NEW_INTCONST_NODE(name,type,val,tok) \ + NEW_AST_NODE(ExprConstant, name, tok); \ + name->Operation = PEX_ConstValue; \ + name->Type = type; \ + name->IntVal = val + + struct ClassFlagsBlock { + VM_UWORD Flags; + ZCC_Identifier *Replaces; + VersionInfo Version; + }; + + struct StateOpts { + ZCC_Expression *Offset; + ZCC_ExprConstant *Lights; + bool Bright; + bool Fast; + bool Slow; + bool NoDelay; + bool CanRaise; + + void Zero() { + Offset = nullptr; + Lights = nullptr; + Bright = false; + Fast = false; + Slow = false; + NoDelay = false; + CanRaise = false; + } + }; + + struct VarOrFun + { + ZCC_VarName *VarNames; + ZCC_FuncParamDecl *FuncParams; + ZCC_CompoundStmt *FuncBody; + ENamedName FuncName; + int FuncFlags; + int SourceLoc; + }; +} + +%token_prefix ZCC_ +%token_type { ZCCToken } +%token_destructor {} // just to avoid a compiler warning +%name ZCCParse +%extra_argument { ZCCParseState *stat } +%syntax_error +{ + FString unexpected, expecting; + + int i; + int stateno = yypParser->yytos->stateno; + + unexpected << "Unexpected " << ZCCTokenName(yymajor); + + // Determine all the terminals that the parser would have accepted at this point + // (see yy_find_shift_action). This list can get quite long. Is it worthwhile to + // print it when not debugging the grammar, or would that be too confusing to + // the average user? + if (stateno < YY_SHIFT_MAX && (i = yy_shift_ofst[stateno])!=YY_SHIFT_USE_DFLT) + { + for (int j = 1; j < YYERRORSYMBOL; ++j) + { + int k = i + j; + if (k >= 0 && k < YY_ACTTAB_COUNT && yy_lookahead[k] == j) + { + expecting << (expecting.IsEmpty() ? "Expecting " : " or ") << ZCCTokenName(j); + } + } + } + stat->sc->ScriptMessage("%s\n%s\n", unexpected.GetChars(), expecting.GetChars()); + FScriptPosition::ErrorCounter++; +} +%parse_accept { DPrintf(DMSG_SPAMMY, "Input accepted\n"); } +%parse_failure { /**failed = true;*/ } + +%right EQ MULEQ DIVEQ MODEQ ADDEQ SUBEQ LSHEQ RSHEQ ANDEQ OREQ XOREQ URSHEQ. +%right QUESTION COLON. +%left OROR. +%left ANDAND. +%left EQEQ NEQ APPROXEQ. +%left LT GT LTEQ GTEQ LTGTEQ IS. +%left DOTDOT. +%left OR. /* Note that this is like the Ruby precedence for these */ +%left XOR. /* three operators and not the C precedence, since */ +%left AND. /* they are higher priority than the comparisons. */ +%left LSH RSH URSH. +%left SUB ADD. +%left MUL DIV MOD CROSSPROD DOTPROD. +%left POW. +%right UNARY ADDADD SUBSUB. +%left DOT LPAREN LBRACKET. +%left SCOPE. + +%type declarator {ZCC_Declarator *} +%type declarator_no_fun {ZCC_Declarator *} +%type opt_func_body {ZCC_CompoundStmt *} +%type function_body {ZCC_CompoundStmt *} + +main ::= translation_unit(A). { stat->TopNode = A; DPrintf(DMSG_SPAMMY, "Parse complete\n"); } + +%type translation_unit {ZCC_TreeNode *} +translation_unit(X) ::= . { X = NULL; } +translation_unit(X) ::= translation_unit(X) external_declaration(B). { SAFE_APPEND(X,B); } +translation_unit(X) ::= translation_unit(X) EOF. +translation_unit(X) ::= error. { X = NULL; } + +%type external_declaration {ZCC_TreeNode *} +external_declaration(X) ::= mixin_definition(A). { X = A; /*X-overwrites-A*/ } +external_declaration(X) ::= class_definition(A). { X = A; /*X-overwrites-A*/ } +external_declaration(X) ::= struct_def(A). { X = A; /*X-overwrites-A*/ } +external_declaration(X) ::= enum_def(A). { X = A; /*X-overwrites-A*/ } +external_declaration(X) ::= const_def(A). { X = A; /*X-overwrites-A*/ } +external_declaration(X) ::= include_def. { X = nullptr; } + +/* Optional bits. */ +opt_semicolon ::= . +opt_semicolon ::= SEMICOLON. + +opt_comma ::= . +opt_comma ::= COMMA. + +%type opt_expr{ZCC_Expression *} +opt_expr(X) ::= . +{ + X = NULL; +} +opt_expr(X) ::= expr(X). + + +include_def ::= INCLUDE string_constant(A). +{ + AddInclude(A); +} + +/************ Class Definition ************/ +/* Can only occur at global scope. */ + +%type class_definition{ZCC_Class *} +%type class_head{ZCC_Class *} +%type class_innards{ZCC_TreeNode *} +%type class_member{ZCC_TreeNode *} +%type class_body{ZCC_TreeNode *} + +class_definition(X) ::= class_head(A) class_body(B). +{ + A->Body = B; + X = A; /*X-overwrites-A*/ +} + +class_head(X) ::= EXTEND CLASS(T) IDENTIFIER(A). +{ + NEW_AST_NODE(Class,head,T); + head->NodeName = A.Name(); + head->ParentName = nullptr; + head->Flags = ZCC_Extension; + head->Replaces = nullptr; + head->Version = {0, 0}; + head->Type = nullptr; + head->Symbol = nullptr; + X = head; +} + +class_head(X) ::= CLASS(T) IDENTIFIER(A) class_ancestry(B) class_flags(C). +{ + NEW_AST_NODE(Class,head,T); + head->NodeName = A.Name(); + head->ParentName = B; + head->Flags = C.Flags; + head->Replaces = C.Replaces; + head->Version = C.Version; + head->Type = nullptr; + head->Symbol = nullptr; + X = head; +} + +%type class_ancestry{ZCC_Identifier *} +class_ancestry(X) ::= . { X = NULL; } +class_ancestry(X) ::= COLON dottable_id(A). { X = A; /*X-overwrites-A*/ } + +%type class_flags{ClassFlagsBlock} +class_flags(X) ::= . { X.Flags = 0; X.Replaces = NULL; X.Version = {0,0}; } +class_flags(X) ::= class_flags(A) ABSTRACT. { X.Flags = A.Flags | ZCC_Abstract; X.Replaces = A.Replaces; } +class_flags(X) ::= class_flags(A) NATIVE. { X.Flags = A.Flags | ZCC_Native; X.Replaces = A.Replaces; } +class_flags(X) ::= class_flags(A) UI. { X.Flags = A.Flags | ZCC_UIFlag; X.Replaces = A.Replaces; } +class_flags(X) ::= class_flags(A) PLAY. { X.Flags = A.Flags | ZCC_Play; X.Replaces = A.Replaces; } +class_flags(X) ::= class_flags(A) REPLACES dottable_id(B). { X.Flags = A.Flags; X.Replaces = B; } +class_flags(X) ::= class_flags(A) VERSION LPAREN STRCONST(C) RPAREN. { X.Flags = A.Flags | ZCC_Version; X.Replaces = A.Replaces; X.Version = C.String->GetChars(); } + +/*----- Dottable Identifier -----*/ +// This can be either a single identifier or two identifiers connected by a . + +%type dottable_id{ZCC_Identifier *} + +dottable_id(X) ::= IDENTIFIER(A). +{ + NEW_AST_NODE(Identifier,id,A); + id->Id = A.Name(); + X = id; +} +dottable_id(X) ::= dottable_id(A) DOT IDENTIFIER(B). +{ + NEW_AST_NODE(Identifier,id2,A); + id2->Id = B.Name(); + AppendTreeNodeSibling(A, id2); + X = A; /*X-overwrites-A*/ +} +dottable_id(X) ::= dottable_id(A) DOT DEFAULT. +{ + NEW_AST_NODE(Identifier,id2,A); + id2->Id = NAME_Default; + AppendTreeNodeSibling(A, id2); + X = A; /*X-overwrites-A*/ +} + +// a bit of a hack to allow the 'color' token to be used inside default properties. +// as a variable name it is practically meaningless because it cannot defined +// as such anywhere so it will always produce an error during processing. +dottable_id(X) ::= dottable_id(A) DOT COLOR. +{ + NEW_AST_NODE(Identifier,id2,A); + id2->Id = NAME_Color; + AppendTreeNodeSibling(A, id2); + X = A; /*X-overwrites-A*/ +} + +/*------ Class Body ------*/ +// Body is a list of: +// * variable definitions +// * function definitions +// * enum definitions +// * struct definitions +// * state definitions +// * constants +// * defaults + +class_body(X) ::= SEMICOLON class_innards(A) EOF. { X = A; /*X-overwrites-A*/ } +class_body(X) ::= LBRACE class_innards(A) RBRACE. { X = A; /*X-overwrites-A*/ } + +class_innards(X) ::= . { X = NULL; } +class_innards(X) ::= class_innards(X) class_member(B). { SAFE_APPEND(X,B); } + +%type mixin_statement{ZCC_MixinStmt *} +%type property_def{ZCC_Property *} +%type flag_def{ZCC_FlagDef *} +%type struct_def{ZCC_Struct *} +%type enum_def {ZCC_Enum *} +%type states_def {ZCC_States *} +%type const_def {ZCC_ConstantDef *} + +class_member(X) ::= declarator(A). { X = A; /*X-overwrites-A*/ } +class_member(X) ::= mixin_statement(A). { X = A; /*X-overwrites-A*/ } +class_member(X) ::= enum_def(A). { X = A; /*X-overwrites-A*/ } +class_member(X) ::= struct_def(A). { X = A; /*X-overwrites-A*/ } +class_member(X) ::= states_def(A). { X = A; /*X-overwrites-A*/ } +class_member(X) ::= default_def(A). { X = A; /*X-overwrites-A*/ } +class_member(X) ::= const_def(A). { X = A; /*X-overwrites-A*/ } +class_member(X) ::= property_def(A). { X = A; /*X-overwrites-A*/ } +class_member(X) ::= flag_def(A). { X = A; /*X-overwrites-A*/ } +class_member(X) ::= staticarray_statement(A). { X = A; /*X-overwrites-A*/ } + + +/*----- Mixin statement -----*/ +mixin_statement(X) ::= MIXIN(T) IDENTIFIER(A) SEMICOLON. +{ + NEW_AST_NODE(MixinStmt,stmt,T); + stmt->MixinName = A.Name(); + X = stmt; +} + +/*----- Struct Definition -----*/ +/* Structs can define variables and enums. */ + +%type opt_struct_body{ZCC_TreeNode *} +%type struct_body{ZCC_TreeNode *} +%type struct_member{ZCC_TreeNode *} +%type identifier_list{ZCC_Identifier *} + +property_def(X) ::= PROPERTY(T) IDENTIFIER(A) COLON identifier_list(B) SEMICOLON. +{ + NEW_AST_NODE(Property,def,T); + def->NodeName = A.Name(); + def->Body = B; + X = def; +} + +flag_def(X) ::= FLAGDEF(T) IDENTIFIER(A) COLON IDENTIFIER(B) COMMA INTCONST(C) SEMICOLON. +{ + NEW_AST_NODE(FlagDef,def,T); + def->NodeName = A.Name(); + def->RefName = B.Name(); + def->BitValue = C.Int; + X = def; +} + + +identifier_list(X) ::= IDENTIFIER(A). +{ + NEW_AST_NODE(Identifier,id,A); + id->Id = A.Name(); + X = id; +} + +identifier_list(X) ::= states_opt(A) COMMA IDENTIFIER(B). +{ + NEW_AST_NODE(Identifier,id,B); + id->Id = B.Name(); + X = A; /*X-overwrites-A*/ + AppendTreeNodeSibling(X, id); +} + +struct_def(X) ::= STRUCT(T) IDENTIFIER(A) struct_flags(S) LBRACE opt_struct_body(B) RBRACE opt_semicolon. +{ + NEW_AST_NODE(Struct,def,T); + def->NodeName = A.Name(); + def->Body = B; + def->Type = nullptr; + def->Symbol = nullptr; + def->Version = S.Version; + def->Flags = S.Flags; + X = def; +} + +%type struct_flags{ClassFlagsBlock} +struct_flags(X) ::= . { X.Flags = 0; X.Version = {0, 0}; } +struct_flags(X) ::= struct_flags(A) UI. { X.Flags = A.Flags | ZCC_UIFlag; } +struct_flags(X) ::= struct_flags(A) PLAY. { X.Flags = A.Flags | ZCC_Play; } +struct_flags(X) ::= struct_flags(A) CLEARSCOPE. { X.Flags = A.Flags | ZCC_ClearScope; } +struct_flags(X) ::= struct_flags(A) NATIVE. { X.Flags = A.Flags | ZCC_Native; } +struct_flags(X) ::= struct_flags(A) VERSION LPAREN STRCONST(C) RPAREN. { X.Flags = A.Flags | ZCC_Version; X.Version = C.String->GetChars(); } + +opt_struct_body(X) ::= . { X = NULL; } +opt_struct_body(X) ::= struct_body(X). +opt_struct_body(X) ::= error. { X = NULL; } + + +struct_body(X) ::= struct_member(X). +struct_body(X) ::= struct_body(A) struct_member(B). { X = A; /*X-overwrites-A*/ AppendTreeNodeSibling(X, B); } + +struct_member(X) ::= declarator(A). { X = A; /*X-overwrites-A*/ } +struct_member(X) ::= enum_def(A). { X = A; /*X-overwrites-A*/ } +struct_member(X) ::= const_def(A). { X = A; /*X-overwrites-A*/ } +struct_member(X) ::= staticarray_statement(A). { X = A; /*X-overwrites-A*/ } + +/*----- Constant Definition ------*/ +/* Like UnrealScript, a constant's type is implied by its value's type. */ +const_def(X) ::= CONST(T) IDENTIFIER(A) EQ expr(B) SEMICOLON. +{ + NEW_AST_NODE(ConstantDef,def,T); + def->NodeName = A.Name(); + def->Value = B; + def->Symbol = NULL; + X = def; +} + + +/*----- Enum Definition -----*/ +/* Enumerators are lists of named integers. */ + +%type enum_list {ZCC_ConstantDef *} +%type opt_enum_list {ZCC_ConstantDef *} +%type enumerator {ZCC_ConstantDef *} + +enum_def(X) ::= ENUM(T) IDENTIFIER(A) enum_type(B) LBRACE opt_enum_list(C) RBRACE(U) opt_semicolon. +{ + NEW_AST_NODE(Enum,def,T); + def->NodeName = A.Name(); + def->EnumType = (EZCCBuiltinType)B.Int; + def->Elements = C; + def->Symbol = nullptr; + + // If the first element does not have an explicit value, make it 0. + if (C != NULL) + { + ZCC_ConstantDef *node = C, *prev = node; + + if (node->Value == NULL) + { + NEW_INTCONST_NODE(zero, TypeSInt32, 0, C); + node->Value = zero; + } + for (node = static_cast(node->SiblingNext); + node != C; + prev = node, node = static_cast(node->SiblingNext)) + { + assert(node->NodeType == AST_ConstantDef); + // Leave explicit values alone. + if (node->Value != NULL) + { + continue; + } + // Compute implicit values by adding one to the preceding value. + assert(prev->Value != NULL); + // If the preceding node is a constant, then we can do this now. + if (prev->Value->Operation == PEX_ConstValue && prev->Value->Type->isInt()) + { + NEW_INTCONST_NODE(cval, prev->Value->Type, static_cast(prev->Value)->IntVal + 1, node); + node->Value = cval; + } + // Otherwise, create a new addition expression to add 1. + else + { + NEW_INTCONST_NODE(one, TypeSInt32, 1, T); + NEW_AST_NODE(ExprID, label, node); + label->Operation = PEX_ID; + label->Identifier = prev->NodeName; + label->Type = NULL; + + BINARY_EXPR(label, one, PEX_Add); + node->Value = expr2; + } + } + // Add a new terminating node, to indicate that the ConstantDefs for this enum are done. + NEW_AST_NODE(EnumTerminator,term,U); + AppendTreeNodeSibling(C, term); + } + if (C != NULL) + { + AppendTreeNodeSibling(def, C); + } + X = def; +} + +enum_type(X) ::= . { X.Int = ZCC_IntAuto; X.SourceLoc = stat->sc->GetMessageLine(); } +enum_type(X) ::= COLON int_type(A). { X = A; /*X-overwrites-A*/ } + +enum_list(X) ::= error. { X = NULL; } +enum_list(X) ::= enumerator(X). +enum_list(X) ::= enum_list(A) COMMA enumerator(B). { X = A; /*X-overwrites-A*/ AppendTreeNodeSibling(X, B); } + +opt_enum_list(X) ::= . { X = NULL; } +opt_enum_list(X) ::= enum_list(X) opt_comma. + +enumerator(X) ::= IDENTIFIER(A). +{ + NEW_AST_NODE(ConstantDef,node,A); + node->NodeName = A.Name(); + node->Value = NULL; + node->Symbol = NULL; + X = node; +} +enumerator(X) ::= IDENTIFIER(A) EQ expr(B). /* Expression must be constant. */ +{ + NEW_AST_NODE(ConstantDef,node,A); + node->NodeName = A.Name(); + node->Value = B; + node->Symbol = NULL; + X = node; +} + +/************ Mixin Definition ************/ +/* Can only occur at global scope. */ + +%type mixin_definition{ZCC_MixinDef *} +%type mixin_class_definition{ZCC_MixinDef *} + +mixin_definition(X) ::= mixin_class_definition(A). { X = A; /*X-overwrites-A*/ } + +/*------ Mixin Class Definition ------*/ +%type mixin_class_body{ZCC_TreeNode *} +%type mixin_class_member{ZCC_TreeNode *} + +mixin_class_definition(X) ::= MIXIN(T) CLASS IDENTIFIER(A) LBRACE mixin_class_body(B) RBRACE. +{ + NEW_AST_NODE(MixinDef,def,T); + def->Body = B; + def->NodeName = A.Name(); + def->MixinType = ZCC_Mixin_Class; + def->Symbol = nullptr; + X = def; +} + +/*------ Mixin Class Body ------*/ +// Body is a list of: +// * variable definitions +// * function definitions +// * enum definitions +// * struct definitions +// * state definitions +// * constants +// * defaults + +mixin_class_body(X) ::= . { X = NULL; } +mixin_class_body(X) ::= mixin_class_body(X) mixin_class_member(B). { SAFE_APPEND(X,B); } + +mixin_class_member(X) ::= declarator(A). { X = A; /*X-overwrites-A*/ } +mixin_class_member(X) ::= enum_def(A). { X = A; /*X-overwrites-A*/ } +mixin_class_member(X) ::= struct_def(A). { X = A; /*X-overwrites-A*/ } +mixin_class_member(X) ::= states_def(A). { X = A; /*X-overwrites-A*/ } +mixin_class_member(X) ::= default_def(A). { X = A; /*X-overwrites-A*/ } +mixin_class_member(X) ::= const_def(A). { X = A; /*X-overwrites-A*/ } +mixin_class_member(X) ::= property_def(A). { X = A; /*X-overwrites-A*/ } +mixin_class_member(X) ::= flag_def(A). { X = A; /*X-overwrites-A*/ } +mixin_class_member(X) ::= staticarray_statement(A). { X = A; /*X-overwrites-A*/ } + +/************ States ************/ + +%type states_body {ZCC_StatePart *} +%type state_line {ZCC_StatePart *} +%type state_label {ZCC_StatePart *} +%type state_flow {ZCC_StatePart *} +%type state_flow_type {ZCC_StatePart *} +%type state_goto_offset {ZCC_Expression *} +%type state_action {ZCC_TreeNode *} +%type state_call {ZCC_ExprFuncCall *} +%type state_call_params {ZCC_FuncParm *} + +%type state_opts {StateOpts} +%type states_opts { ZCC_Identifier *} +%type states_opt { ZCC_Identifier *} + +states_def(X) ::= STATES(T) states_opts(B) scanner_mode LBRACE states_body(A) RBRACE. +{ + NEW_AST_NODE(States,def,T); + def->Flags = B; + def->Body = A; + X = def; +} + +states_opts(X) ::= . { X = nullptr; } +states_opts(X) ::= LPAREN states_opt(A) RPAREN. { X = A; /*X-overwrites-A*/ } + +states_opt(X) ::= IDENTIFIER(A). +{ + NEW_AST_NODE(Identifier,id,A); + id->Id = A.Name(); + X = id; +} + +states_opt(X) ::= states_opt(A) COMMA IDENTIFIER(B). +{ + NEW_AST_NODE(Identifier,id,B); + id->Id = B.Name(); + X = A; /*X-overwrites-A*/ + AppendTreeNodeSibling(X, id); +} + + +/* We use a special scanner mode to allow for sprite names and frame characters + * to not be quoted even if they contain special characters. The scanner_mode + * nonterminal is used to enter this mode. The scanner automatically leaves it + * upon pre-defined conditions. See the comments by FScanner::SetStateMode(). + * + * Note that rules are reduced *after* one token of lookahead has been + * consumed, so this nonterminal must be placed one token before we want it to + * take effect. For example, in states_def above, the scanner mode will be + * set immediately after LBRACE is consumed, rather than immediately after + * STATES is consumed. + */ +scanner_mode ::= . { stat->sc->SetStateMode(true); } + +states_body(X) ::= . { X = NULL; } +states_body(X) ::= error. { X = NULL; } +states_body(X) ::= states_body(X) state_line(B). { SAFE_APPEND(X,B); } +states_body(X) ::= states_body(X) state_label(B). { SAFE_APPEND(X,B); } +states_body(X) ::= states_body(X) state_flow(B). { SAFE_APPEND(X,B); } + +state_label(X) ::= NWS(A) COLON. +{ + NEW_AST_NODE(StateLabel, label, A); + label->Label = A.Name(); + X = label; +} + +state_flow(X) ::= state_flow_type(X) scanner_mode SEMICOLON. + +state_flow_type(X) ::= STOP(A). { NEW_AST_NODE(StateStop, flow, A); X = flow; } +state_flow_type(X) ::= WAIT(A). { NEW_AST_NODE(StateWait, flow, A); X = flow; } +state_flow_type(X) ::= FAIL(A). { NEW_AST_NODE(StateFail, flow, A); X = flow; } +state_flow_type(X) ::= LOOP(A). { NEW_AST_NODE(StateLoop, flow, A); X = flow; } +state_flow_type(X) ::= GOTO(T) dottable_id(A) state_goto_offset(B). +{ + NEW_AST_NODE(StateGoto, flow, T); + flow->Label = A; + flow->Offset = B; + flow->Qualifier = nullptr; + X = flow; +} + +state_flow_type(X) ::= GOTO(T) IDENTIFIER(C) SCOPE dottable_id(A) state_goto_offset(B). +{ + NEW_AST_NODE(StateGoto, flow, T); + flow->Label = A; + flow->Offset = B; + + NEW_AST_NODE(Identifier,id,C); + id->Id = C.Name(); + flow->Qualifier =id; + X = flow; +} + +state_flow_type(X) ::= GOTO(T) SUPER(C) SCOPE dottable_id(A) state_goto_offset(B). +{ + NEW_AST_NODE(StateGoto, flow, T); + flow->Label = A; + flow->Offset = B; + + NEW_AST_NODE(Identifier,id,C); + id->Id = NAME_Super; + flow->Qualifier =id; + X = flow; +} + +state_goto_offset(X) ::= . { X = NULL; } +state_goto_offset(X) ::= ADD expr(A). { X = A; /*X-overwrites-A*/ } /* Must evaluate to a non-negative integer constant. */ + +state_line(X) ::= NWS(A) NWS(B) expr(E) state_opts(C) state_action(D). +{ + NEW_AST_NODE(StateLine, line, A); + line->Sprite = stat->Strings.Alloc(FName(A.Name()).GetChars()); + line->Frames = stat->Strings.Alloc(FName(B.Name()).GetChars()); + line->Duration = E; + line->bBright = C.Bright; + line->bFast = C.Fast; + line->bSlow = C.Slow; + line->bNoDelay = C.NoDelay; + line->bCanRaise = C.CanRaise; + line->Offset = C.Offset; + line->Lights = C.Lights; + line->Action = D; + X = line; +} + +state_opts(X) ::= . { StateOpts opts; opts.Zero(); X = opts; } +state_opts(X) ::= state_opts(A) BRIGHT. { A.Bright = true; X = A; /*X-overwrites-A*/ } +state_opts(X) ::= state_opts(A) FAST. { A.Fast = true; X = A; /*X-overwrites-A*/ } +state_opts(X) ::= state_opts(A) SLOW. { A.Slow = true; X = A; /*X-overwrites-A*/ } +state_opts(X) ::= state_opts(A) NODELAY. { A.NoDelay = true; X = A; /*X-overwrites-A*/ } +state_opts(X) ::= state_opts(A) CANRAISE. { A.CanRaise = true; X = A; /*X-overwrites-A*/ } +state_opts(X) ::= state_opts(A) OFFSET LPAREN expr(B) COMMA expr(C) RPAREN. { A.Offset = B; AppendTreeNodeSibling(B, C); X = A; /*X-overwrites-A*/ } +state_opts(X) ::= state_opts(A) LIGHT LPAREN light_list(B) RPAREN. { X = A; /*X-overwrites-A*/ X.Lights = B; } + +%type light_list {ZCC_ExprConstant *} + +light_list(X) ::= STRCONST(A). +{ + NEW_AST_NODE(ExprConstant, strconst, A); + strconst->Operation = PEX_ConstValue; + strconst->Type = TypeString; + strconst->StringVal = A.String; + X = strconst; +} + +light_list(X) ::= light_list(A) COMMA STRCONST(B). +{ + NEW_AST_NODE(ExprConstant, strconst, B); + strconst->Operation = PEX_ConstValue; + strconst->Type = TypeString; + strconst->StringVal = B.String; + AppendTreeNodeSibling(A, strconst); + X = A; /*X-overwrites-A*/ +} + +/* A state action can be either a compound statement or a single action function call. */ +state_action(X) ::= LBRACE(T) statement_list(A) scanner_mode RBRACE. +{ + NEW_AST_NODE(CompoundStmt,stmt,T); + stmt->Content = A; + X = stmt; +} +state_action(X) ::= LBRACE scanner_mode RBRACE. +{ + X = NULL; +} +state_action(X) ::= LBRACE error scanner_mode RBRACE. { X = NULL; } +state_action(X) ::= state_call(A) scanner_mode SEMICOLON. { X = A; /*X-overwrites-A*/ } + +state_call(X) ::= . { X = NULL; } +state_call(X) ::= IDENTIFIER(A) state_call_params(B). +{ + NEW_AST_NODE(ExprFuncCall, expr, A); + NEW_AST_NODE(ExprID, func, A); + + func->Operation = PEX_ID; + func->Identifier = A.Name(); + expr->Operation = PEX_FuncCall; + expr->Function = func; + expr->Parameters = B; + X = expr; +} + +state_call_params(X) ::= . { X = NULL; } +state_call_params(X) ::= LPAREN func_expr_list(A) RPAREN. { X = A; /*X-overwrites-A*/ } + +/* Definition of a default class instance. */ +%type default_def {ZCC_Default *} +%type default_statement_list{ZCC_Statement *} +%type default_statement{ZCC_Statement *} + +default_def(X) ::= DEFAULT LBRACE(T) RBRACE. +{ + NEW_AST_NODE(Default,stmt,T); + stmt->Content = NULL; + X = stmt; +} +default_def(X) ::= DEFAULT LBRACE(T) default_statement_list(A) RBRACE. +{ + NEW_AST_NODE(Default,stmt,T); + stmt->Content = A; + X = stmt; +} +default_def(X) ::= DEFAULT LBRACE(T) error RBRACE. +{ + NEW_AST_NODE(Default,stmt,T); + stmt->Content = NULL; + X = stmt; +} + +default_statement_list(X) ::= default_statement(A). +{ + X = A; /*X-overwrites-A*/ +} +default_statement_list(X) ::= default_statement_list(X) default_statement(B). +{ + SAFE_APPEND(X,B); +} + + +default_statement(X) ::= SEMICOLON. { X = NULL; } +default_statement(X) ::= error SEMICOLON. { X = NULL; } +//default_statement(X) ::= assign_statement(A) SEMICOLON. { X = A; /*X-overwrites-A*/ } +default_statement(X) ::= property_statement(A). { X = A; /*X-overwrites-A*/ } +default_statement(X) ::= flag_statement(A). { X = A; /*X-overwrites-A*/ } + +%type flag_statement { ZCC_FlagStmt *} + +flag_statement(X) ::= ADD dottable_id(A). +{ + NEW_AST_NODE(FlagStmt, type, A); + type->set = true; + type->name = A; + X = type; +} +flag_statement(X) ::= SUB dottable_id(A). +{ + NEW_AST_NODE(FlagStmt, type, A); + type->set = false; + type->name = A; + X = type; +} + +%type property_statement{ZCC_PropertyStmt *} + +property_statement(X) ::= dottable_id(A) expr_list(B) SEMICOLON. +{ + NEW_AST_NODE(PropertyStmt,stmt,A); + stmt->Prop = A; + stmt->Values = B; + X = stmt; +} + +property_statement(X) ::= dottable_id(A) SEMICOLON. +{ + NEW_AST_NODE(PropertyStmt,stmt,A); + stmt->Prop = A; + stmt->Values = nullptr; + X = stmt; +} + + + +/* Type names */ +%type type_name {ZCC_BasicType *} + +int_type(X) ::= SBYTE(T). { X.Int = ZCC_SInt8; X.SourceLoc = T.SourceLoc; } +int_type(X) ::= BYTE(T). { X.Int = ZCC_UInt8; X.SourceLoc = T.SourceLoc; } +int_type(X) ::= SHORT(T). { X.Int = ZCC_SInt16; X.SourceLoc = T.SourceLoc; } +int_type(X) ::= USHORT(T). { X.Int = ZCC_UInt16; X.SourceLoc = T.SourceLoc; } +int_type(X) ::= INT(T). { X.Int = ZCC_SInt32; X.SourceLoc = T.SourceLoc; } +int_type(X) ::= UINT(T). { X.Int = ZCC_UInt32; X.SourceLoc = T.SourceLoc; } + +type_name1(X) ::= BOOL(T). { X.Int = ZCC_Bool; X.SourceLoc = T.SourceLoc; } +type_name1(X) ::= int_type(X). +type_name1(X) ::= FLOAT(T). { X.Int = ZCC_FloatAuto; X.SourceLoc = T.SourceLoc; } +type_name1(X) ::= DOUBLE(T). { X.Int = ZCC_Float64; X.SourceLoc = T.SourceLoc; } +//type_name1(X) ::= STRING(T). { X.Int = ZCC_String; X.SourceLoc = T.SourceLoc; } // [ZZ] it's handled elsewhere. this particular line only causes troubles in the form of String.Format being invalid. +type_name1(X) ::= VECTOR2(T). { X.Int = ZCC_Vector2; X.SourceLoc = T.SourceLoc; } +type_name1(X) ::= VECTOR3(T). { X.Int = ZCC_Vector3; X.SourceLoc = T.SourceLoc; } +type_name1(X) ::= NAME(T). { X.Int = ZCC_Name; X.SourceLoc = T.SourceLoc; } +type_name1(X) ::= SOUND(T). { X.Int = ZCC_Sound; X.SourceLoc = T.SourceLoc; } +type_name1(X) ::= STATE(T). { X.Int = ZCC_State; X.SourceLoc = T.SourceLoc; } +type_name1(X) ::= COLOR(T). { X.Int = ZCC_Color; X.SourceLoc = T.SourceLoc; } +type_name1(X) ::= LET(T). { X.Int = ZCC_Let; X.SourceLoc = T.SourceLoc; } + +type_name(X) ::= type_name1(A). +{ + NEW_AST_NODE(BasicType, type, A); + type->Type = (EZCCBuiltinType)A.Int; + type->UserType = NULL; + type->isconst = false; + X = type; +} +type_name(X) ::= IDENTIFIER(A). /* User-defined type (struct, enum, or class) */ +{ + NEW_AST_NODE(BasicType, type, A); + NEW_AST_NODE(Identifier, id, A); + type->Type = ZCC_UserType; + type->UserType = id; + type->isconst = false; + id->Id = A.Name(); + X = type; +} + +type_name(X) ::= ATSIGN IDENTIFIER(A). +{ + NEW_AST_NODE(BasicType, type, A); + NEW_AST_NODE(Identifier, id, A); + type->Type = ZCC_NativeType; + type->UserType = id; + type->isconst = false; + id->Id = A.Name(); + X = type; +} + +type_name(X) ::= READONLY LT IDENTIFIER(A) GT. +{ + NEW_AST_NODE(BasicType, type, A); + NEW_AST_NODE(Identifier, id, A); + type->Type = ZCC_UserType; + type->UserType = id; + type->isconst = true; + id->Id = A.Name(); + X = type; +} + +type_name(X) ::= READONLY LT ATSIGN IDENTIFIER(A) GT. +{ + NEW_AST_NODE(BasicType, type, A); + NEW_AST_NODE(Identifier, id, A); + type->Type = ZCC_NativeType; + type->UserType = id; + type->isconst = true; + id->Id = A.Name(); + X = type; +} + +type_name(X) ::= DOT dottable_id(A). +{ + NEW_AST_NODE(BasicType, type, A); + type->Type = ZCC_UserType; + type->UserType = A; + type->isconst = false; + X = type; +} + +/* Type names can also be used as identifiers in contexts where type names + * are not normally allowed. */ +%fallback IDENTIFIER + SBYTE BYTE SHORT USHORT INT UINT BOOL FLOAT DOUBLE STRING VECTOR2 VECTOR3 NAME MAP ARRAY VOID STATE COLOR SOUND UINT8 INT8 UINT16 INT16 PROPERTY. + +/* Aggregate types */ +%type aggregate_type {ZCC_Type *} +%type type {ZCC_Type *} +%type type_list {ZCC_Type *} +%type type_list_or_void {ZCC_Type *} +%type type_or_array {ZCC_Type *} + %type class_restrictor {ZCC_Identifier *} +%type array_size{ZCC_Expression *} +%type array_size_expr{ZCC_Expression *} + +aggregate_type(X) ::= MAP(T) LT type_or_array(A) COMMA type_or_array(B) GT. /* Hash table */ +{ + NEW_AST_NODE(MapType,map,T); + map->KeyType = A; + map->ValueType = B; + X = map; +} + +aggregate_type(X) ::= ARRAY(T) LT type_or_array(A) GT. /* TArray */ +{ + NEW_AST_NODE(DynArrayType,arr,T); + arr->ElementType = A; + X = arr; +} + +aggregate_type(X) ::= CLASS(T) class_restrictor(A). /* class */ +{ + NEW_AST_NODE(ClassType,cls,T); + cls->Restriction = A; + X = cls; +} +class_restrictor(X) ::= . { X = NULL; } +class_restrictor(X) ::= LT dottable_id(A) GT. { X = A; /*X-overwrites-A*/ } + +type(X) ::= type_name(A). { X = A; /*X-overwrites-A*/ X->ArraySize = NULL; } +type(X) ::= aggregate_type(A). { X = A; /*X-overwrites-A*/ X->ArraySize = NULL; } + +type_or_array(X) ::= type(X). +type_or_array(X) ::= type(A) array_size(B). { X = A; /*X-overwrites-A*/ X->ArraySize = B; } + +type_list(X) ::= type_or_array(X). /* A comma-separated list of types */ +type_list(X) ::= type_list(A) COMMA type_or_array(B). { X = A; /*X-overwrites-A*/ AppendTreeNodeSibling(X, B); } + +type_list_or_void(X) ::= VOID. { X = NULL; } +type_list_or_void(X) ::= type_list(X). + +array_size_expr(X) ::= LBRACKET(L) opt_expr(A) RBRACKET. +{ + if (A == NULL) + { + NEW_AST_NODE(Expression,nil,L.SourceLoc); + nil->Operation = PEX_Nil; + nil->Type = NULL; + X = nil; + } + else + { + X = A; + } +} +array_size(X) ::= array_size_expr(X). +array_size(X) ::= array_size(A) array_size_expr(B). +{ + AppendTreeNodeSibling(A, B); + X = A; /*X-overwrites-A*/ +} + +%type variables_or_function {VarOrFun} + +/* Multiple type names are only valid for functions. */ +declarator(X) ::= decl_flags(A) type_list_or_void(B) variables_or_function(C). +{ + if (C.FuncName == NAME_None && C.VarNames == NULL) + { // An error. A message was already printed. + X = NULL; + } + else if (C.FuncName != NAME_None) + { // A function + NEW_AST_NODE(FuncDeclarator, decl, A == nullptr? C.SourceLoc : A->SourceLoc); + decl->Type = B; + decl->Params = C.FuncParams; + decl->Name = C.FuncName; + decl->UseFlags = A == nullptr? nullptr : A->Id; + decl->Flags = (A == nullptr? 0 : A->Flags) | C.FuncFlags; + decl->DeprecationMessage = A == nullptr ? nullptr : A->DeprecationMessage; + if (A == nullptr) decl->Version = {0,0,0}; + else decl->Version = A->Version; + + decl->Body = C.FuncBody; + X = decl; + } + else if (B != NULL && B->SiblingNext == B) + { // A variable + NEW_AST_NODE(VarDeclarator, decl, A == nullptr? B->SourceLoc : A->SourceLoc); + decl->Type = B; + decl->Names = C.VarNames; + if (A == nullptr) + { + decl->Flags = 0; + decl->Version = {0,0,0}; + decl->DeprecationMessage = nullptr; + } + else + { + decl->Flags = A->Flags; + decl->Version = A->Version; + decl->DeprecationMessage = A->DeprecationMessage; + } + X = decl; + } + else + { // An invalid + if (B == NULL) + { + stat->sc->ScriptMessage("Variables may not be of type void.\n"); + } + else + { + stat->sc->ScriptMessage("Variables may be of only one type.\n"); + } + X = NULL; + } +} + +// Need to split it up like this to avoid parsing conflicts. +variables_or_function(X) ::= IDENTIFIER(A) LPAREN func_params(B) RPAREN func_const(C) opt_func_body(D). /* Function */ +{ + VarOrFun fun; + + fun.VarNames = NULL; + fun.FuncParams = B; + fun.FuncFlags = C.Int; + fun.FuncName = A.Name(); + fun.FuncBody = D; + fun.SourceLoc = A.SourceLoc; + X = fun; +} +variables_or_function(X) ::= variable_list(A) SEMICOLON. +{ + VarOrFun var; + + var.VarNames = A; + var.FuncParams = NULL; + var.FuncFlags = 0; + var.FuncName = NAME_None; + var.FuncBody = NULL; + var.SourceLoc = A->SourceLoc; + X = var; +} +variables_or_function(X) ::= error SEMICOLON(T). +{ + VarOrFun bad; + bad.VarNames = NULL; + bad.FuncParams = NULL; + bad.FuncFlags = 0; + bad.FuncName = NAME_None; + bad.FuncBody = NULL; + bad.SourceLoc = T.SourceLoc; + X = bad; +} + +/*----- Variable Names -----*/ + +%type variable_name{ZCC_VarName *} +%type variable_list{ZCC_VarName *} + +variable_name(X) ::= IDENTIFIER(A). +{ + NEW_AST_NODE(VarName,var,A); + var->Name = ENamedName(A.Int); + var->ArraySize = NULL; + X = var; +} +variable_name(X) ::= IDENTIFIER(A) array_size(B). +{ + NEW_AST_NODE(VarName,var,A); + var->Name = ENamedName(A.Int); + var->ArraySize = B; + X = var; +} + +variable_list(X) ::= variable_name(X). +variable_list(X) ::= variable_list(A) COMMA variable_name(B). +{ + AppendTreeNodeSibling(A, B); + X = A; /*X-overwrites-A*/ +} + +%type decl_flags { ZCC_DeclFlags * } +decl_flags(X) ::= . { X = NULL; } +decl_flags(X) ::= decl_flags(F) decl_flag(A). +{ + if (F == nullptr) + { + NEW_AST_NODE(DeclFlags,nil_f,A); + X = nil_f; + X->Id = nullptr; + X->Flags = A.Int; + X->Version = { 0, 0 }; + X->DeprecationMessage = nullptr; + } + else + { + X = F; + X->Flags |= A.Int; + } +} + + +decl_flags(X) ::= decl_flags(F) ACTION(B) states_opts(A). +{ + if (F == nullptr) + { + NEW_AST_NODE(DeclFlags,nil_f,B.SourceLoc); + X = nil_f; + X->Flags = ZCC_Action; + X->Id = nullptr; + X->Version = { 0, 0 }; + X->DeprecationMessage = nullptr; + } + else + { + X = F; + X->Flags |= ZCC_Action; + } + X->Id = A; +} + +opt_deprecation_message(X) ::= . { X.String = nullptr; X.SourceLoc = stat->sc->GetMessageLine(); } +opt_deprecation_message(X) ::= COMMA STRCONST(C). { X = C; } + +decl_flags(X) ::= decl_flags(F) DEPRECATED(B) LPAREN STRCONST(A) opt_deprecation_message(C) RPAREN. +{ + if (F == nullptr) + { + NEW_AST_NODE(DeclFlags,nil_f,B.SourceLoc); + X = nil_f; + X->Flags = ZCC_Deprecated; + X->Id = nullptr; + X->Version = { 0, 0 }; + } + else + { + X = F; + X->Flags |= ZCC_Deprecated; + } + X->Version = A.String->GetChars(); + X->DeprecationMessage = C.String; +} + +decl_flags(X) ::= decl_flags(F) VERSION(B) LPAREN STRCONST(A) RPAREN. +{ + if (F == nullptr) + { + NEW_AST_NODE(DeclFlags,nil_f,B.SourceLoc); + X = nil_f; + X->Flags = ZCC_Version; + X->Id = nullptr; + X->Version = { 0, 0 }; + X->DeprecationMessage = nullptr; + } + else + { + X = F; + X->Flags |= ZCC_Version; + } + X->Version = A.String->GetChars(); +} + +decl_flag(X) ::= NATIVE(T). { X.Int = ZCC_Native; X.SourceLoc = T.SourceLoc; } +decl_flag(X) ::= STATIC(T). { X.Int = ZCC_Static; X.SourceLoc = T.SourceLoc; } +decl_flag(X) ::= PRIVATE(T). { X.Int = ZCC_Private; X.SourceLoc = T.SourceLoc; } +decl_flag(X) ::= PROTECTED(T). { X.Int = ZCC_Protected; X.SourceLoc = T.SourceLoc; } +decl_flag(X) ::= LATENT(T). { X.Int = ZCC_Latent; X.SourceLoc = T.SourceLoc; } +decl_flag(X) ::= FINAL(T). { X.Int = ZCC_Final; X.SourceLoc = T.SourceLoc; } +decl_flag(X) ::= META(T). { X.Int = ZCC_Meta; X.SourceLoc = T.SourceLoc; } +decl_flag(X) ::= TRANSIENT(T). { X.Int = ZCC_Transient; X.SourceLoc = T.SourceLoc; } +decl_flag(X) ::= READONLY(T). { X.Int = ZCC_ReadOnly; X.SourceLoc = T.SourceLoc; } +decl_flag(X) ::= INTERNAL(T). { X.Int = ZCC_Internal; X.SourceLoc = T.SourceLoc; } +decl_flag(X) ::= VIRTUAL(T). { X.Int = ZCC_Virtual; X.SourceLoc = T.SourceLoc; } +decl_flag(X) ::= OVERRIDE(T). { X.Int = ZCC_Override; X.SourceLoc = T.SourceLoc; } +decl_flag(X) ::= VARARG(T). { X.Int = ZCC_VarArg; X.SourceLoc = T.SourceLoc; } +decl_flag(X) ::= UI(T). { X.Int = ZCC_UIFlag; X.SourceLoc = T.SourceLoc; } +decl_flag(X) ::= PLAY(T). { X.Int = ZCC_Play; X.SourceLoc = T.SourceLoc; } +decl_flag(X) ::= CLEARSCOPE(T). { X.Int = ZCC_ClearScope; X.SourceLoc = T.SourceLoc; } +decl_flag(X) ::= VIRTUALSCOPE(T). { X.Int = ZCC_VirtualScope; X.SourceLoc = T.SourceLoc; } + +func_const(X) ::= . { X.Int = 0; X.SourceLoc = stat->sc->GetMessageLine(); } +func_const(X) ::= CONST(T). { X.Int = ZCC_FuncConst; X.SourceLoc = T.SourceLoc; } + +opt_func_body(X) ::= SEMICOLON. { X = NULL; } +opt_func_body(X) ::= function_body(X). + +%type func_params {ZCC_FuncParamDecl *} +%type func_param_list {ZCC_FuncParamDecl *} +%type func_param {ZCC_FuncParamDecl *} + +func_params(X) ::= . /* empty */ { X = NULL; } +func_params(X) ::= VOID. { X = NULL; } +func_params(X) ::= func_param_list(X). + +func_params(X) ::= func_param_list(A) COMMA ELLIPSIS. +{ + NEW_AST_NODE(FuncParamDecl,parm,stat->sc->GetMessageLine()); + parm->Type = nullptr; + parm->Name = NAME_None; + parm->Flags = 0; + parm->Default = nullptr; + X = A; /*X-overwrites-A*/ + AppendTreeNodeSibling(X, parm); +} + +func_param_list(X) ::= func_param(X). +func_param_list(X) ::= func_param_list(A) COMMA func_param(B). { X = A; /*X-overwrites-A*/ AppendTreeNodeSibling(X, B); } + +func_param(X) ::= func_param_flags(A) type(B) IDENTIFIER(C). +{ + NEW_AST_NODE(FuncParamDecl,parm,A.SourceLoc ? A.SourceLoc : B->SourceLoc); + parm->Type = B; + parm->Name = C.Name(); + parm->Flags = A.Int; + parm->Default = nullptr; + X = parm; +} + +func_param(X) ::= func_param_flags(A) type(B) IDENTIFIER(C) EQ expr(D). +{ + NEW_AST_NODE(FuncParamDecl,parm,A.SourceLoc ? A.SourceLoc : B->SourceLoc); + parm->Type = B; + parm->Name = C.Name(); + parm->Flags = A.Int; + parm->Default = D; + X = parm; +} + +func_param_flags(X) ::= . { X.Int = 0; X.SourceLoc = 0; } +func_param_flags(X) ::= func_param_flags(A) IN(T). { X.Int = A.Int | ZCC_In; X.SourceLoc = T.SourceLoc; } +func_param_flags(X) ::= func_param_flags(A) OUT(T). { X.Int = A.Int | ZCC_Out; X.SourceLoc = T.SourceLoc; } +func_param_flags(X) ::= func_param_flags(A) OPTIONAL(T). { X.Int = A.Int | ZCC_Optional; X.SourceLoc = T.SourceLoc; } + +/************ Expressions ************/ + +/* We use default to access a class's default instance. */ +%fallback IDENTIFIER + DEFAULT. + +%type expr{ZCC_Expression *} +%type primary{ZCC_Expression *} +%type unary_expr{ZCC_Expression *} +%type constant{ZCC_ExprConstant *} + +/*----- Primary Expressions -----*/ + +primary(X) ::= IDENTIFIER(A). +{ + NEW_AST_NODE(ExprID, expr, A); + expr->Operation = PEX_ID; + expr->Identifier = A.Name(); + expr->Type = NULL; + X = expr; +} +primary(X) ::= SUPER(T). +{ + NEW_AST_NODE(Expression, expr, T); + expr->Operation = PEX_Super; + expr->Type = NULL; + X = expr; +} +primary(X) ::= constant(A). { X = A; /*X-overwrites-A*/ } +primary(XX) ::= LPAREN expr(A) COMMA expr(B) COMMA expr(C) RPAREN. [DOT] +{ + NEW_AST_NODE(VectorValue, expr, A); + expr->Operation = PEX_Vector; + expr->Type = TypeVector3; + expr->X = A; + expr->Y = B; + expr->Z = C; + XX = expr; +} +primary(XX) ::= LPAREN expr(A) COMMA expr(B) RPAREN. [DOT] +{ + NEW_AST_NODE(VectorValue, expr, A); + expr->Operation = PEX_Vector; + expr->Type = TypeVector2; + expr->X = A; + expr->Y = B; + expr->Z = nullptr; + XX = expr; +} +primary(X) ::= LPAREN expr(A) RPAREN. +{ + X = A; /*X-overwrites-A*/ +} +primary ::= LPAREN error RPAREN. +primary(X) ::= primary(A) LPAREN func_expr_list(B) RPAREN. [DOT] // Function call +{ + NEW_AST_NODE(ExprFuncCall, expr, A); + expr->Operation = PEX_FuncCall; + expr->Type = NULL; + expr->Function = A; + expr->Parameters = B; + X = expr; +} +primary(X) ::= LPAREN CLASS LT IDENTIFIER(A) GT RPAREN LPAREN func_expr_list(B) RPAREN. [DOT] // class type cast +{ + NEW_AST_NODE(ClassCast, expr, A); + expr->Operation = PEX_ClassCast; + expr->ClassName = ENamedName(A.Int); + expr->Parameters = B; + X = expr; +} +primary(X) ::= primary(A) LBRACKET expr(B) RBRACKET. [DOT] // Array access +{ + NEW_AST_NODE(ExprBinary, expr, B); + expr->Operation = PEX_ArrayAccess; + expr->Type = NULL; + expr->Left = A; + expr->Right = B; + X = expr; +} +primary(X) ::= primary(A) DOT IDENTIFIER(B). // Member access +{ + NEW_AST_NODE(ExprMemberAccess, expr, B); + expr->Operation = PEX_MemberAccess; + expr->Type = NULL; + expr->Left = A; + expr->Right = ENamedName(B.Int); + X = expr; +} +primary(X) ::= primary(A) ADDADD. /* postfix++ */ +{ + UNARY_EXPR(A,PEX_PostInc); + X = expr1; +} +primary(X) ::= primary(A) SUBSUB. /* postfix-- */ +{ + UNARY_EXPR(A,PEX_PostDec); + X = expr1; +} +/* +primary(X) ::= SCOPE primary(B). +{ + BINARY_EXPR(NULL,B,PEX_Scope); + X = expr2; +} +*/ + +/*----- Unary Expressions -----*/ + +unary_expr(X) ::= primary(X). +unary_expr(X) ::= SUB unary_expr(A). [UNARY] +{ + ZCC_ExprConstant *con = static_cast(A); + if (A->Operation == PEX_ConstValue && (con->Type->isInt() || con->Type->isFloat())) + { // For constants, manipulate the child node directly, and don't create a new node. + if (con->Type->isInt()) + { + con->IntVal = -con->IntVal; + } + else + { + con->DoubleVal = -con->DoubleVal; + } + X = A; + } + else + { // For everything else, create a new node and do the negation later. + UNARY_EXPR(A,PEX_Negate); + X = expr1; + } +} +unary_expr(X) ::= ADD unary_expr(A). [UNARY] +{ + // Even though this is really a no-op, we still need to make a node for + // it so we can type check that it is being applied to something numeric. + // But we can do that right now for constant numerals. + ZCC_ExprConstant *con = static_cast(A); + if (A->Operation != PEX_ConstValue || (!con->Type->isInt() && !con->Type->isFloat())) + { + UNARY_EXPR(A,PEX_AntiNegate); + X = expr1; + } + else + { + X = A; + } +} +unary_expr(X) ::= SUBSUB unary_expr(A). [UNARY] +{ + UNARY_EXPR(A,PEX_PreDec); + X = expr1; +} +unary_expr(X) ::= ADDADD unary_expr(A). [UNARY] +{ + UNARY_EXPR(A,PEX_PreInc); + X = expr1; +} +unary_expr(X) ::= TILDE unary_expr(A). [UNARY] +{ + UNARY_EXPR(A,PEX_BitNot); + X = expr1; +} +unary_expr(X) ::= BANG unary_expr(A). [UNARY] +{ + UNARY_EXPR(A,PEX_BoolNot); + X = expr1; +} +unary_expr(X) ::= SIZEOF unary_expr(A). [UNARY] +{ + UNARY_EXPR(A,PEX_SizeOf); + X = expr1; +} +unary_expr(X) ::= ALIGNOF unary_expr(A). [UNARY] +{ + UNARY_EXPR(A,PEX_AlignOf); + X = expr1; +} + +/* Due to parsing conflicts, C-style casting is not supported. You + * must use C++ function call-style casting instead. + */ + +/*----- Binary Expressions -----*/ + +expr(X) ::= unary_expr(X). +expr(X) ::= expr(A) ADD expr(B). /* a + b */ +{ + BINARY_EXPR(A,B,PEX_Add); + X = expr2; +} +expr(X) ::= expr(A) SUB expr(B). /* a - b */ +{ + BINARY_EXPR(A,B,PEX_Sub); + X = expr2; +} +expr(X) ::= expr(A) MUL expr(B). /* a * b */ +{ + BINARY_EXPR(A,B,PEX_Mul); + X = expr2; +} +expr(X) ::= expr(A) DIV expr(B). /* a / b */ +{ + BINARY_EXPR(A,B,PEX_Div); + X = expr2; +} +expr(X) ::= expr(A) MOD expr(B). /* a % b */ +{ + BINARY_EXPR(A,B,PEX_Mod); + X = expr2; +} +expr(X) ::= expr(A) POW expr(B). /* a ** b */ +{ + BINARY_EXPR(A,B,PEX_Pow); + X = expr2; +} +expr(X) ::= expr(A) CROSSPROD expr(B). /* a cross b */ +{ + BINARY_EXPR(A,B,PEX_CrossProduct); + X = expr2; +} +expr(X) ::= expr(A) DOTPROD expr(B). /* a dot b */ +{ + BINARY_EXPR(A,B,PEX_DotProduct); + X = expr2; +} +expr(X) ::= expr(A) LSH expr(B). /* a << b */ +{ + BINARY_EXPR(A,B,PEX_LeftShift); + X = expr2; +} +expr(X) ::= expr(A) RSH expr(B). /* a >> b */ +{ + BINARY_EXPR(A,B,PEX_RightShift); + X = expr2; +} +expr(X) ::= expr(A) URSH expr(B). /* a >>> b */ +{ + BINARY_EXPR(A,B,PEX_URightShift); + X = expr2; +} +expr(X) ::= expr(A) DOTDOT expr(B). /* a .. b */ +{ + BINARY_EXPR(A,B,PEX_Concat); + X = expr2; +} + +expr(X) ::= expr(A) LT expr(B). /* a < b */ +{ + BINARY_EXPR(A,B,PEX_LT); + X = expr2; +} +expr(X) ::= expr(A) GT expr(B). /* a > b */ +{ + BINARY_EXPR(A,B,PEX_GT); + X = expr2; +} +expr(X) ::= expr(A) LTEQ expr(B). /* a <= b */ +{ + BINARY_EXPR(A,B,PEX_LTEQ); + X = expr2; +} +expr(X) ::= expr(A) GTEQ expr(B). /* a >= b */ +{ + BINARY_EXPR(A,B,PEX_GTEQ); + X = expr2; +} +expr(X) ::= expr(A) LTGTEQ expr(B). /* a <>= b */ +{ + BINARY_EXPR(A,B,PEX_LTGTEQ); + X = expr2; +} +expr(X) ::= expr(A) IS expr(B). /* a is b */ +{ + BINARY_EXPR(A,B,PEX_Is); + X = expr2; +} + +expr(X) ::= expr(A) EQEQ expr(B). /* a == b */ +{ + BINARY_EXPR(A,B,PEX_EQEQ); + X = expr2; +} +expr(X) ::= expr(A) NEQ expr(B). /* a != b */ +{ + BINARY_EXPR(A,B,PEX_NEQ); + X = expr2; +} +expr(X) ::= expr(A) APPROXEQ expr(B). /* a ~== b */ +{ + BINARY_EXPR(A,B,PEX_APREQ); + X = expr2; +} + +expr(X) ::= expr(A) AND expr(B). /* a & b */ +{ + BINARY_EXPR(A,B,PEX_BitAnd); + X = expr2; +} +expr(X) ::= expr(A) XOR expr(B). /* a ^ b */ +{ + BINARY_EXPR(A,B,PEX_BitXor); + X = expr2; +} +expr(X) ::= expr(A) OR expr(B). /* a | b */ +{ + BINARY_EXPR(A,B,PEX_BitOr); + X = expr2; +} +expr(X) ::= expr(A) ANDAND expr(B). /* a && b */ +{ + BINARY_EXPR(A,B,PEX_BoolAnd); + X = expr2; +} +expr(X) ::= expr(A) OROR expr(B). /* a || b */ +{ + BINARY_EXPR(A,B,PEX_BoolOr); + X = expr2; +} +expr(X) ::= expr(A) EQ expr(B). /* a = b */ +{ + BINARY_EXPR(A,B,PEX_Assign); + X = expr2; +} +expr(X) ::= expr(A) ADDEQ expr(B). /* a += b */ +{ + BINARY_EXPR(A,B,PEX_AddAssign); + X = expr2; +} +expr(X) ::= expr(A) SUBEQ expr(B). /* a -= b */ +{ + BINARY_EXPR(A,B,PEX_SubAssign); + X = expr2; +} +expr(X) ::= expr(A) MULEQ expr(B). /* a *= b */ +{ + BINARY_EXPR(A,B,PEX_MulAssign); + X = expr2; +} +expr(X) ::= expr(A) DIVEQ expr(B). /* a /= b */ +{ + BINARY_EXPR(A,B,PEX_DivAssign); + X = expr2; +} +expr(X) ::= expr(A) MODEQ expr(B). /* a %= b */ +{ + BINARY_EXPR(A,B,PEX_ModAssign); + X = expr2; +} +expr(X) ::= expr(A) LSHEQ expr(B). /* a <<= b */ +{ + BINARY_EXPR(A,B,PEX_LshAssign); + X = expr2; +} +expr(X) ::= expr(A) RSHEQ expr(B). /* a >>= b */ +{ + BINARY_EXPR(A,B,PEX_RshAssign); + X = expr2; +} +expr(X) ::= expr(A) URSHEQ expr(B). /* a >>>= b */ +{ + BINARY_EXPR(A,B,PEX_URshAssign); + X = expr2; +} +expr(X) ::= expr(A) ANDEQ expr(B). /* a &= b */ +{ + BINARY_EXPR(A,B,PEX_AndAssign); + X = expr2; +} +expr(X) ::= expr(A) OREQ expr(B). /* a |= b */ +{ + BINARY_EXPR(A,B,PEX_OrAssign); + X = expr2; +} +expr(X) ::= expr(A) XOREQ expr(B). /* a ^= b */ +{ + BINARY_EXPR(A,B,PEX_XorAssign); + X = expr2; +} + + +expr(X) ::= expr(A) SCOPE expr(B). +{ + BINARY_EXPR(A,B,PEX_Scope); + X = expr2; +} + +/*----- Trinary Expression -----*/ + +expr(X) ::= expr(A) QUESTION expr(B) COLON expr(C). +{ + NEW_AST_NODE(ExprTrinary, expr, A); + expr->Operation = PEX_Trinary; + expr->Type = NULL; + expr->Test = A; + expr->Left = B; + expr->Right = C; + X = expr; +} + +/************ Expression Lists ***********/ + +%type expr_list{ZCC_Expression *} + +expr_list(X) ::= expr(X). +expr_list(X) ::= expr_list(A) COMMA expr(B). +{ + X = A; /*X-overwrites-A*/ + AppendTreeNodeSibling(X, B); +} + +/*----- Function argument lists -----*/ + +/* A function expression list can also specify a parameter's name, + * but once you do that, all remaining parameters must also be named. + * We let higher-level code handle this to keep this file simpler. */ +%type func_expr_list{ZCC_FuncParm *} +%type func_expr_item{ZCC_FuncParm *} +%type named_expr{ZCC_FuncParm *} + +func_expr_list(X) ::= func_expr_item(X). +func_expr_list(X) ::= func_expr_list(A) COMMA(T) func_expr_item(B). +{ + // Omitted parameters still need to appear as nodes in the list. + if (A == NULL) + { + NEW_AST_NODE(FuncParm,nil_a,T); + nil_a->Value = NULL; + nil_a->Label = NAME_None; + A = nil_a; + } + if (B == NULL) + { + NEW_AST_NODE(FuncParm,nil_b,T); + nil_b->Value = NULL; + nil_b->Label = NAME_None; + B = nil_b; + } + X = A; /*X-overwrites-A*/ + AppendTreeNodeSibling(X, B); +} + +func_expr_item(X) ::= . +{ + X = NULL; +} +func_expr_item(X) ::= named_expr(X). + +named_expr(X) ::= IDENTIFIER(A) COLON expr(B). +{ + NEW_AST_NODE(FuncParm, parm, A); + parm->Value = B; + parm->Label = ENamedName(A.Int); + X = parm; +} +named_expr(X) ::= expr(B). +{ + NEW_AST_NODE(FuncParm, parm, B); + parm->Value = B; + parm->Label = NAME_None; + X = parm; +} + +/************ Constants ************/ + +/* Allow C-like concatenation of adjacent string constants. */ +%type string_constant{ZCC_ExprConstant *} + +string_constant(X) ::= STRCONST(A). +{ + NEW_AST_NODE(ExprConstant, strconst, A); + strconst->Operation = PEX_ConstValue; + strconst->Type = TypeString; + strconst->StringVal = A.String; + X = strconst; +} +string_constant(X) ::= string_constant(A) STRCONST(B). +{ + NEW_AST_NODE(ExprConstant, strconst, A); + strconst->Operation = PEX_ConstValue; + strconst->Type = TypeString; + strconst->StringVal = stat->Strings.Alloc(*(A->StringVal) + *(B.String)); + X = strconst; +} + +constant(X) ::= string_constant(X). +constant(X) ::= INTCONST(A). +{ + NEW_INTCONST_NODE(intconst, TypeSInt32, A.Int, A); + X = intconst; +} +constant(X) ::= UINTCONST(A). +{ + NEW_INTCONST_NODE(intconst, TypeUInt32, A.Int, A); + X = intconst; +} +constant(X) ::= FLOATCONST(A). +{ + NEW_AST_NODE(ExprConstant, floatconst, A); + floatconst->Operation = PEX_ConstValue; + floatconst->Type = TypeFloat64; + floatconst->DoubleVal = A.Float; + X = floatconst; +} +constant(X) ::= NAMECONST(A). +{ + NEW_AST_NODE(ExprConstant, floatconst, A); + floatconst->Operation = PEX_ConstValue; + floatconst->Type = TypeName; + floatconst->IntVal = A.Int; + X = floatconst; +} +constant(X) ::= FALSE(A). +{ + NEW_INTCONST_NODE(boolconst, TypeBool, false, A); + X = boolconst; +} +constant(X) ::= TRUE(A). +{ + NEW_INTCONST_NODE(boolconst, TypeBool, true, A); + X = boolconst; +} +constant(X) ::= NULLPTR(A). +{ + NEW_AST_NODE(ExprConstant, nullptrconst, A); + nullptrconst->Operation = PEX_ConstValue; + nullptrconst->Type = TypeNullPtr; + nullptrconst->StringVal = nullptr; + X = nullptrconst; +} + +/************ Statements ************/ + +function_body(X) ::= compound_statement(X). + +%type statement{ZCC_Statement *} +statement(X) ::= SEMICOLON. { X = NULL; } +statement(X) ::= labeled_statement(A). { X = A; /*X-overwrites-A*/ } +statement(X) ::= compound_statement(A). { X = A; /*X-overwrites-A*/ } +statement(X) ::= expression_statement(A) SEMICOLON. { X = A; /*X-overwrites-A*/ } +statement(X) ::= selection_statement(X). +statement(X) ::= iteration_statement(X). +statement(X) ::= jump_statement(X). +statement(X) ::= assign_statement(A) SEMICOLON. { X = A; /*X-overwrites-A*/ } +statement(X) ::= local_var(A) SEMICOLON. { X = A; /*X-overwrites-A*/ } +statement(X) ::= error SEMICOLON. { X = NULL; } +statement(X) ::= staticarray_statement(A). { X = A; /*X-overwrites-A*/ } + +/*----- Static array Statements -----*/ + +%type staticarray_statement{ZCC_StaticArrayStatement *} + +staticarray_statement(X) ::= STATICCONST type(A) IDENTIFIER(B) LBRACKET RBRACKET EQ LBRACE expr_list(C) RBRACE SEMICOLON. +{ + NEW_AST_NODE(StaticArrayStatement, stmt, A); + stmt->Type = A; + stmt->Id = ENamedName(B.Int); + stmt->Values = C; + X = stmt; +} + +staticarray_statement(X) ::= STATICCONST type(A) LBRACKET RBRACKET IDENTIFIER(B) EQ LBRACE expr_list(C) RBRACE SEMICOLON. +{ + NEW_AST_NODE(StaticArrayStatement, stmt, A); + stmt->Type = A; + stmt->Id = ENamedName(B.Int); + stmt->Values = C; + X = stmt; +} + +/*----- Jump Statements -----*/ + +%type jump_statement{ZCC_Statement *} + +jump_statement(A) ::= CONTINUE(T) SEMICOLON. +{ + NEW_AST_NODE(ContinueStmt, stmt, T); + A = stmt; +} +jump_statement(A) ::= BREAK(T) SEMICOLON. +{ + NEW_AST_NODE(BreakStmt, stmt, T); + A = stmt; +} +jump_statement(A) ::= RETURN(T) SEMICOLON. +{ + NEW_AST_NODE(ReturnStmt, stmt, T); + stmt->Values = NULL; + A = stmt; +} +jump_statement(A) ::= RETURN(T) expr_list(X) SEMICOLON. +{ + NEW_AST_NODE(ReturnStmt, stmt, T); + stmt->Values = X; + A = stmt; +} + +/*----- Compound Statements -----*/ + +%type compound_statement{ZCC_CompoundStmt *} +%type statement_list{ZCC_Statement *} + +compound_statement(X) ::= LBRACE(T) RBRACE. +{ + NEW_AST_NODE(CompoundStmt,stmt,T); + stmt->Content = NULL; + X = stmt; +} +compound_statement(X) ::= LBRACE(T) statement_list(A) RBRACE. +{ + NEW_AST_NODE(CompoundStmt,stmt,T); + stmt->Content = A; + X = stmt; +} +compound_statement(X) ::= LBRACE(T) error RBRACE. +{ + NEW_AST_NODE(CompoundStmt,stmt,T); + stmt->Content = NULL; + X = stmt; +} + +statement_list(X) ::= statement(A). +{ + X = A; /*X-overwrites-A*/ +} +statement_list(X) ::= statement_list(X) statement(B). +{ + SAFE_APPEND(X,B); +} + +/*----- Expression Statements -----*/ + +%type expression_statement{ZCC_ExpressionStmt *} + +expression_statement(X) ::= expr(A). +{ + NEW_AST_NODE(ExpressionStmt, stmt, A); + stmt->Expression = A; + X = stmt; +} + +/*----- Iteration Statements -----*/ + +%type iteration_statement{ZCC_Statement *} + +// while/until (expr) statement +iteration_statement(X) ::= while_or_until(TY) LPAREN expr(EX) RPAREN statement(ST). +{ + NEW_AST_NODE(IterationStmt, iter, TY); + if (TY.Int == ZCC_UNTIL) + { // Negate the loop condition + UNARY_EXPR(EX,PEX_BoolNot); + iter->LoopCondition = expr1; + } + else + { + iter->LoopCondition = EX; + } + iter->LoopStatement = ST; + iter->LoopBumper = NULL; + iter->CheckAt = ZCC_IterationStmt::Start; + X = iter; +} +// do statement while/until (expr) +iteration_statement(X) ::= DO(T) statement(ST) while_or_until(TY) LPAREN expr(EX) RPAREN. +{ + NEW_AST_NODE(IterationStmt, iter, T); + if (TY.Int == ZCC_UNTIL) + { // Negate the loop condition + UNARY_EXPR(EX,PEX_BoolNot); + iter->LoopCondition = expr1; + } + else + { + iter->LoopCondition = EX; + } + iter->LoopStatement = ST; + iter->LoopBumper = NULL; + iter->CheckAt = ZCC_IterationStmt::End; + X = iter; +} +// for (init; cond; bump) statement +iteration_statement(X) ::= FOR(T) LPAREN for_init(IN) SEMICOLON opt_expr(EX) SEMICOLON for_bump(DO) RPAREN statement(ST). +{ + NEW_AST_NODE(IterationStmt, iter, T); + iter->LoopCondition = EX; + iter->LoopStatement = ST; + iter->LoopBumper = DO; + iter->CheckAt = ZCC_IterationStmt::Start; + // The initialization expression appears outside the loop + // for_init may be NULL if there is no initialization. + SAFE_APPEND(IN, iter); + // And the whole thing gets wrapped inside a compound statement in case the loop + // initializer defined any variables. + NEW_AST_NODE(CompoundStmt, wrap, T); + wrap->Content = IN; + X = wrap; +} + +while_or_until(X) ::= WHILE(T). +{ + X.Int = ZCC_WHILE; + X.SourceLoc = T.SourceLoc; +} +while_or_until(X) ::= UNTIL(T). +{ + X.Int = ZCC_UNTIL; + X.SourceLoc = T.SourceLoc; +} + +%type for_init{ZCC_Statement *} +for_init(X) ::= local_var(A). { X = A /*X-overwrites-A*/; } +for_init(X) ::= for_bump(A). { X = A /*X-overwrites-A*/; } + +%type for_bump{ZCC_Statement *} +for_bump(X) ::= . { X = NULL; } +for_bump(X) ::= expression_statement(A). { X = A; /*X-overwrites-A*/ } +for_bump(X) ::= for_bump(A) COMMA expression_statement(B). { X = A; /*X-overwrites-A*/ AppendTreeNodeSibling(X, B); } + +/*----- If Statements -----*/ + +/* Resolve the shift-reduce conflict here in favor of the shift. + * This is the default behavior, but using precedence symbols + * lets us do it without warnings. + */ +%left IF. +%left ELSE. +%type selection_statement{ZCC_Statement *} +%type if_front{ZCC_IfStmt *} + +selection_statement(X) ::= if_front(A). [IF] +{ + X = A; /*X-overwrites-A*/ +} +selection_statement(X) ::= if_front(A) ELSE statement(B). [ELSE] +{ + A->FalsePath = B; + X = A; /*X-overwrites-A*/ +} + +if_front(X) ::= IF(T) LPAREN expr(A) RPAREN statement(B). +{ + NEW_AST_NODE(IfStmt,stmt,T); + stmt->Condition = A; + stmt->TruePath = B; + stmt->FalsePath = NULL; + X = stmt; +} + +/*----- Switch Statements -----*/ + +selection_statement(X) ::= SWITCH(T) LPAREN expr(A) RPAREN statement(B). +{ + NEW_AST_NODE(SwitchStmt,stmt,T); + stmt->Condition = A; + stmt->Content = B; + X = stmt; +} + +/*----- Case Label "Statements" -----*/ + +%type labeled_statement{ZCC_CaseStmt *} + +labeled_statement(X) ::= CASE(T) expr(A) COLON. +{ + NEW_AST_NODE(CaseStmt,stmt,T); + stmt->Condition = A; + X = stmt; +} +labeled_statement(X) ::= DEFAULT(T) COLON. +{ + NEW_AST_NODE(CaseStmt,stmt,T); + stmt->Condition = NULL; + X = stmt; +} + +/*----- Assignment Statements -----*/ + +%type assign_statement{ZCC_AssignStmt *} + +// The grammar won't let this pass without some syntactic help. +// Parentheses and braces aren't accepted either so brackets are the only way to get this through the parser without a conflict. +assign_statement(X) ::= LBRACKET expr_list(A) RBRACKET EQ expr(B). [EQ] +{ + NEW_AST_NODE(AssignStmt,stmt,A); + stmt->AssignOp = ZCC_EQ; + stmt->Dests = A; + stmt->Sources = B; + X = stmt; +} + +/*----- Local Variable Definition "Statements" -----*/ + +%type local_var{ZCC_LocalVarStmt *} + +local_var(X) ::= type(A) variable_list_with_init(B). +{ + NEW_AST_NODE(LocalVarStmt,vardef,A); + vardef->Type = A; + vardef->Vars = B; + X = vardef; +} + +%type var_init{ZCC_VarInit *} +var_init(X) ::= IDENTIFIER(A). +{ + NEW_AST_NODE(VarInit,var,A); + var->Name = ENamedName(A.Int); + var->ArraySize = NULL; + var->Init = NULL; + var->InitIsArray = false; + X = var; +} + +var_init(X) ::= IDENTIFIER(A) array_size(B). +{ + NEW_AST_NODE(VarInit,var,A); + var->Name = ENamedName(A.Int); + var->ArraySize = B; + var->Init = NULL; + var->InitIsArray = false; + X = var; +} + +var_init(X) ::= IDENTIFIER(A) EQ expr(B). +{ + NEW_AST_NODE(VarInit,var,A); + var->Name = ENamedName(A.Int); + var->ArraySize = NULL; + var->Init = B; + var->InitIsArray = false; + X = var; +} + +var_init(X) ::= IDENTIFIER(A) EQ LBRACE expr_list(C) RBRACE. // this is for arrays which declare the size with the type +{ + NEW_AST_NODE(VarInit,var,A); + var->Name = ENamedName(A.Int); + var->ArraySize = NULL; + var->Init = C; + var->InitIsArray = true; + X = var; +} + +var_init(X) ::= IDENTIFIER(A) array_size(B) EQ LBRACE expr_list(C) RBRACE. +{ + NEW_AST_NODE(VarInit,var,A); + var->Name = ENamedName(A.Int); + var->ArraySize = B; + var->Init = C; + var->InitIsArray = true; + X = var; +} + +var_init(X) ::= IDENTIFIER EQ LBRACE error RBRACE. +{ + X = NULL; +} + +%type variable_list_with_init{ZCC_VarInit *} + +variable_list_with_init(X) ::= var_init(X). +variable_list_with_init(X) ::= variable_list_with_init(A) COMMA var_init(B). +{ + AppendTreeNodeSibling(A, B); + X = A; /*X-overwrites-A*/ +} diff --git a/source/common/scripting/frontend/zcc_compile.cpp b/source/common/scripting/frontend/zcc_compile.cpp new file mode 100644 index 000000000..344566391 --- /dev/null +++ b/source/common/scripting/frontend/zcc_compile.cpp @@ -0,0 +1,3039 @@ +/* +** zcc_compile.cpp +** +**--------------------------------------------------------------------------- +** Copyright -2016 Randy Heit +** Copyright 2016 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "c_console.h" +#include "filesystem.h" +#include "zcc_parser.h" +#include "zcc-parse.h" +#include "zcc_compile.h" +#include "printf.h" +#include "symbols.h" +//#include "v_video.h" + +FSharedStringArena VMStringConstants; + + +static int GetIntConst(FxExpression *ex, FCompileContext &ctx) +{ + ex = new FxIntCast(ex, false); + ex = ex->Resolve(ctx); + return ex ? static_cast(ex)->GetValue().GetInt() : 0; +} + +static double GetFloatConst(FxExpression *ex, FCompileContext &ctx) +{ + ex = new FxFloatCast(ex); + ex = ex->Resolve(ctx); + return ex ? static_cast(ex)->GetValue().GetFloat() : 0; +} + +const char * ZCCCompiler::GetStringConst(FxExpression *ex, FCompileContext &ctx) +{ + ex = new FxStringCast(ex); + ex = ex->Resolve(ctx); + if (!ex) return ""; + // The string here must be stored in a persistent place that lasts long enough to have it processed. + return AST.Strings.Alloc(static_cast(ex)->GetValue().GetString())->GetChars(); +} + +int ZCCCompiler::IntConstFromNode(ZCC_TreeNode *node, PContainerType *cls) +{ + FCompileContext ctx(OutNamespace, cls, false); + FxExpression *ex = new FxIntCast(ConvertNode(node), false); + ex = ex->Resolve(ctx); + if (ex == nullptr) return 0; + if (!ex->isConstant()) + { + ex->ScriptPosition.Message(MSG_ERROR, "Expression is not constant"); + return 0; + } + return static_cast(ex)->GetValue().GetInt(); +} + +FString ZCCCompiler::StringConstFromNode(ZCC_TreeNode *node, PContainerType *cls) +{ + FCompileContext ctx(OutNamespace, cls, false); + FxExpression *ex = new FxStringCast(ConvertNode(node)); + ex = ex->Resolve(ctx); + if (ex == nullptr) return ""; + if (!ex->isConstant()) + { + ex->ScriptPosition.Message(MSG_ERROR, "Expression is not constant"); + return ""; + } + return static_cast(ex)->GetValue().GetString(); +} + +ZCC_MixinDef *ZCCCompiler::ResolveMixinStmt(ZCC_MixinStmt *mixinStmt, EZCCMixinType type) +{ + for (auto mx : Mixins) + { + if (mx->mixin->NodeName == mixinStmt->MixinName) + { + if (mx->mixin->MixinType != type) + { + Error(mixinStmt, "Mixin %s is a %s mixin cannot be used here.", FName(mixinStmt->MixinName).GetChars(), GetMixinTypeString(type)); + return nullptr; + } + + return mx->mixin; + } + } + + Error(mixinStmt, "Mixin %s does not exist.", FName(mixinStmt->MixinName).GetChars()); + + return nullptr; +} + + +//========================================================================== +// +// ZCCCompiler :: ProcessClass +// +//========================================================================== + +void ZCCCompiler::ProcessClass(ZCC_Class *cnode, PSymbolTreeNode *treenode) +{ + ZCC_ClassWork *cls = nullptr; + // If this is a class extension, put the new node directly into the existing class. + if (cnode->Flags == ZCC_Extension) + { + for (auto clss : Classes) + { + if (clss->NodeName() == cnode->NodeName) + { + cls = clss; + break; + } + } + if (cls == nullptr) + { + Error(cnode, "Class %s cannot be found in the current translation unit.", FName(cnode->NodeName).GetChars()); + return; + } + } + else + { + Classes.Push(new ZCC_ClassWork(static_cast(cnode), treenode)); + cls = Classes.Last(); + } + + auto node = cnode->Body; + PSymbolTreeNode *childnode; + ZCC_Enum *enumType = nullptr; + + // [pbeta] Handle mixins here for the sake of simplifying things. + if (node != nullptr) + { + bool mixinError = false; + TArray mixinStmts; + mixinStmts.Clear(); + + // Gather all mixin statement nodes. + do + { + if (node->NodeType == AST_MixinStmt) + { + mixinStmts.Push(static_cast(node)); + } + + node = node->SiblingNext; + } + while (node != cnode->Body); + + for (auto mixinStmt : mixinStmts) + { + ZCC_MixinDef *mixinDef = ResolveMixinStmt(mixinStmt, ZCC_Mixin_Class); + + if (mixinDef == nullptr) + { + mixinError = true; + continue; + } + + // Insert the mixin if there's a body. If not, just remove this node. + if (mixinDef->Body != nullptr) + { + auto newNode = TreeNodeDeepCopy(&AST, mixinDef->Body, true); + + if (mixinStmt->SiblingNext != mixinStmt && mixinStmt->SiblingPrev != mixinStmt) + { + auto prevSibling = mixinStmt->SiblingPrev; + auto nextSibling = mixinStmt->SiblingNext; + + auto newFirst = newNode; + auto newLast = newNode->SiblingPrev; + + newFirst->SiblingPrev = prevSibling; + newLast->SiblingNext = nextSibling; + + prevSibling->SiblingNext = newFirst; + nextSibling->SiblingPrev = newLast; + } + + if (cnode->Body == mixinStmt) + { + cnode->Body = newNode; + } + } + else + { + if (mixinStmt->SiblingNext != mixinStmt && mixinStmt->SiblingPrev != mixinStmt) + { + auto prevSibling = mixinStmt->SiblingPrev; + auto nextSibling = mixinStmt->SiblingNext; + + prevSibling->SiblingNext = nextSibling; + nextSibling->SiblingPrev = prevSibling; + + if (cnode->Body == mixinStmt) + { + cnode->Body = nextSibling; + } + } + else if (cnode->Body == mixinStmt) + { + cnode->Body = nullptr; + } + } + } + + mixinStmts.Clear(); + + if (mixinError) + { + return; + } + } + + node = cnode->Body; + + // Need to check if the class actually has a body. + if (node != nullptr) do + { + switch (node->NodeType) + { + case AST_MixinStmt: + assert(0 && "Unhandled mixin statement in class parsing loop. If this has been reached, something is seriously wrong"); + Error(node, "Internal mixin error."); + break; + + case AST_Struct: + case AST_ConstantDef: + case AST_Enum: + if ((childnode = AddTreeNode(static_cast(node)->NodeName, node, &cls->TreeNodes))) + { + switch (node->NodeType) + { + case AST_Enum: + enumType = static_cast(node); + cls->Enums.Push(enumType); + break; + + case AST_Struct: + if (static_cast(node)->Flags & VARF_Native) + { + Error(node, "Cannot define native structs inside classes"); + static_cast(node)->Flags &= ~VARF_Native; + } + ProcessStruct(static_cast(node), childnode, cls->cls); + break; + + case AST_ConstantDef: + cls->Constants.Push(static_cast(node)); + cls->Constants.Last()->Type = enumType; + break; + + default: + assert(0 && "Default case is just here to make GCC happy. It should never be reached"); + } + } + break; + + case AST_Property: + cls->Properties.Push(static_cast(node)); + break; + + case AST_FlagDef: + cls->FlagDefs.Push(static_cast(node)); + break; + + case AST_VarDeclarator: + cls->Fields.Push(static_cast(node)); + break; + + case AST_EnumTerminator: + enumType = nullptr; + break; + + case AST_States: + cls->States.Push(static_cast(node)); + break; + + case AST_FuncDeclarator: + cls->Functions.Push(static_cast(node)); + break; + + case AST_Default: + cls->Defaults.Push(static_cast(node)); + break; + + case AST_StaticArrayStatement: + if (AddTreeNode(static_cast(node)->Id, node, &cls->TreeNodes)) + { + cls->Arrays.Push(static_cast(node)); + } + break; + + default: + assert(0 && "Unhandled AST node type"); + break; + } + + node = node->SiblingNext; + } + while (node != cnode->Body); +} + +//========================================================================== +// +// ZCCCompiler :: ProcessMixin +// +//========================================================================== + +void ZCCCompiler::ProcessMixin(ZCC_MixinDef *cnode, PSymbolTreeNode *treenode) +{ + ZCC_MixinWork *cls = new ZCC_MixinWork(cnode, treenode); + + auto node = cnode->Body; + + // Need to check if the mixin actually has a body. + if (node != nullptr) do + { + if (cnode->MixinType == ZCC_Mixin_Class) + { + switch (node->NodeType) + { + case AST_Struct: + case AST_ConstantDef: + case AST_Enum: + case AST_Property: + case AST_FlagDef: + case AST_VarDeclarator: + case AST_EnumTerminator: + case AST_States: + case AST_FuncDeclarator: + case AST_Default: + case AST_StaticArrayStatement: + break; + + default: + assert(0 && "Unhandled AST node type"); + break; + } + } + + node = node->SiblingNext; + } while (node != cnode->Body); + + Mixins.Push(cls); +} + +//========================================================================== +// +// ZCCCompiler :: ProcessStruct +// +//========================================================================== + +void ZCCCompiler::ProcessStruct(ZCC_Struct *cnode, PSymbolTreeNode *treenode, ZCC_Class *outer) +{ + Structs.Push(new ZCC_StructWork(static_cast(cnode), treenode, outer)); + ZCC_StructWork *cls = Structs.Last(); + + auto node = cnode->Body; + PSymbolTreeNode *childnode; + ZCC_Enum *enumType = nullptr; + + // Need to check if the struct actually has a body. + if (node != nullptr) do + { + switch (node->NodeType) + { + case AST_ConstantDef: + case AST_Enum: + if ((childnode = AddTreeNode(static_cast(node)->NodeName, node, &cls->TreeNodes))) + { + switch (node->NodeType) + { + case AST_Enum: + enumType = static_cast(node); + cls->Enums.Push(enumType); + break; + + case AST_ConstantDef: + cls->Constants.Push(static_cast(node)); + cls->Constants.Last()->Type = enumType; + break; + + default: + assert(0 && "Default case is just here to make GCC happy. It should never be reached"); + } + } + break; + + case AST_VarDeclarator: + cls->Fields.Push(static_cast(node)); + break; + + case AST_FuncDeclarator: + cls->Functions.Push(static_cast(node)); + break; + + case AST_EnumTerminator: + enumType = nullptr; + break; + + case AST_StaticArrayStatement: + if (AddTreeNode(static_cast(node)->Id, node, &cls->TreeNodes)) + { + cls->Arrays.Push(static_cast(node)); + } + break; + + default: + assert(0 && "Unhandled AST node type"); + break; + } + node = node->SiblingNext; + } + while (node != cnode->Body); +} + +//========================================================================== +// +// ZCCCompiler Constructor +// +//========================================================================== + +ZCCCompiler::ZCCCompiler(ZCC_AST &ast, DObject *_outer, PSymbolTable &_symbols, PNamespace *_outnamespc, int lumpnum, const VersionInfo &ver) + : mVersion(ver), Outer(_outer), ConvertClass(nullptr), GlobalTreeNodes(&_symbols), OutNamespace(_outnamespc), AST(ast), Lump(lumpnum) +{ + FScriptPosition::ResetErrorCounter(); + // Group top-level nodes by type + if (ast.TopNode != NULL) + { + ZCC_TreeNode *node = ast.TopNode; + PSymbolTreeNode *tnode; + + // [pbeta] Anything that must be processed before classes, structs, etc. should go here. + do + { + switch (node->NodeType) + { + // [pbeta] Mixins must be processed before everything else. + case AST_MixinDef: + if ((tnode = AddTreeNode(static_cast(node)->NodeName, node, GlobalTreeNodes))) + { + ProcessMixin(static_cast(node), tnode); + break; + } + break; + + default: + break; // Shut GCC up. + } + node = node->SiblingNext; + } while (node != ast.TopNode); + + node = ast.TopNode; + PType *enumType = nullptr; + ZCC_Enum *zenumType = nullptr; + + do + { + switch (node->NodeType) + { + case AST_MixinDef: + // [pbeta] We already processed mixins, ignore them here. + break; + + case AST_Class: + // a class extension should not check the tree node symbols. + if (static_cast(node)->Flags == ZCC_Extension) + { + ProcessClass(static_cast(node), tnode); + break; + } + case AST_Struct: + case AST_ConstantDef: + case AST_Enum: + if ((tnode = AddTreeNode(static_cast(node)->NodeName, node, GlobalTreeNodes))) + { + switch (node->NodeType) + { + case AST_Enum: + zenumType = static_cast(node); + enumType = NewEnum(zenumType->NodeName, OutNamespace); + OutNamespace->Symbols.AddSymbol(Create(zenumType->NodeName, enumType)); + break; + + case AST_Class: + ProcessClass(static_cast(node), tnode); + break; + + case AST_Struct: + ProcessStruct(static_cast(node), tnode, nullptr); + break; + + case AST_ConstantDef: + Constants.Push(static_cast(node)); + Constants.Last()->Type = zenumType; + break; + + default: + assert(0 && "Default case is just here to make GCC happy. It should never be reached"); + } + } + break; + + case AST_EnumTerminator: + zenumType = nullptr; + break; + + default: + assert(0 && "Unhandled AST node type"); + break; + } + node = node->SiblingNext; + } while (node != ast.TopNode); + } +} + +ZCCCompiler::~ZCCCompiler() +{ + for (auto s : Structs) + { + delete s; + } + for (auto c : Classes) + { + delete c; + } + Structs.Clear(); + Classes.Clear(); +} + +//========================================================================== +// +// ZCCCompiler :: AddTreeNode +// +// Keeps track of definition nodes by their names. Ensures that all names +// in this scope are unique. +// +//========================================================================== + +PSymbolTreeNode *ZCCCompiler::AddTreeNode(FName name, ZCC_TreeNode *node, PSymbolTable *treenodes, bool searchparents) +{ + PSymbol *check = treenodes->FindSymbol(name, searchparents); + if (check != NULL) + { + assert(check->IsA(RUNTIME_CLASS(PSymbolTreeNode))); + Error(node, "Attempt to redefine '%s'", name.GetChars()); + Error(static_cast(check)->Node, " Original definition is here"); + return nullptr; + } + else + { + auto sy = Create(name, node); + FString name; + treenodes->AddSymbol(sy); + return sy; + } +} + +//========================================================================== +// +// ZCCCompiler :: Warn +// +// Prints a warning message, and increments WarnCount. +// +//========================================================================== + +void ZCCCompiler::Warn(ZCC_TreeNode *node, const char *msg, ...) +{ + va_list argptr; + va_start(argptr, msg); + MessageV(node, TEXTCOLOR_ORANGE, msg, argptr); + va_end(argptr); + + FScriptPosition::WarnCounter++; +} + +//========================================================================== +// +// ZCCCompiler :: Error +// +// Prints an error message, and increments ErrorCount. +// +//========================================================================== + +void ZCCCompiler::Error(ZCC_TreeNode *node, const char *msg, ...) +{ + va_list argptr; + va_start(argptr, msg); + MessageV(node, TEXTCOLOR_RED, msg, argptr); + va_end(argptr); + + FScriptPosition::ErrorCounter++; +} + +//========================================================================== +// +// ZCCCompiler :: MessageV +// +// Prints a message, annotated with the source location for the tree node. +// +//========================================================================== + +void ZCCCompiler::MessageV(ZCC_TreeNode *node, const char *txtcolor, const char *msg, va_list argptr) +{ + FString composed; + + composed.Format("%s%s, line %d: ", txtcolor, node->SourceName->GetChars(), node->SourceLoc); + composed.VAppendFormat(msg, argptr); + composed += '\n'; + PrintString(PRINT_HIGH, composed); +} + +//========================================================================== +// +// ZCCCompiler :: Compile +// +// Compile everything defined at this level. +// +//========================================================================== + +int ZCCCompiler::Compile() +{ + CreateClassTypes(); + CreateStructTypes(); + CompileAllConstants(); + CompileAllFields(); + InitFunctions(); + return FScriptPosition::ErrorCounter; +} + +//========================================================================== +// +// ZCCCompiler :: CreateStructTypes +// +// Creates a PStruct for every struct. +// +//========================================================================== + +void ZCCCompiler::CreateStructTypes() +{ + for(auto s : Structs) + { + PTypeBase *outer; + PSymbolTable *syms; + + s->Outer = s->OuterDef == nullptr? nullptr : s->OuterDef->CType(); + if (s->Outer) + { + outer = s->Outer->VMType; + syms = &s->Outer->VMType->Symbols; + } + else + { + outer = OutNamespace; + syms = &OutNamespace->Symbols; + } + + if (s->NodeName() == NAME__ && fileSystem.GetFileContainer(Lump) == 0) + { + // This is just a container for syntactic purposes. + s->strct->Type = nullptr; + continue; + } + else if (s->strct->Flags & ZCC_Native) + { + s->strct->Type = NewStruct(s->NodeName(), outer, true); + } + else + { + s->strct->Type = NewStruct(s->NodeName(), outer); + } + if (s->strct->Flags & ZCC_Version) + { + s->strct->Type->mVersion = s->strct->Version; + } + + auto &sf = s->Type()->ScopeFlags; + if (mVersion >= MakeVersion(2, 4, 0)) + { + if ((s->strct->Flags & (ZCC_UIFlag | ZCC_Play)) == (ZCC_UIFlag | ZCC_Play)) + { + Error(s->strct, "Struct %s has incompatible flags", s->NodeName().GetChars()); + } + + if (outer != OutNamespace) sf = FScopeBarrier::ChangeSideInObjectFlags(sf, FScopeBarrier::SideFromObjectFlags(static_cast(outer)->ScopeFlags)); + else if (s->strct->Flags & ZCC_ClearScope) Warn(s->strct, "Useless 'ClearScope' on struct %s not inside a class", s->NodeName().GetChars()); + if (s->strct->Flags & ZCC_UIFlag) + sf = FScopeBarrier::ChangeSideInObjectFlags(sf, FScopeBarrier::Side_UI); + if (s->strct->Flags & ZCC_Play) + sf = FScopeBarrier::ChangeSideInObjectFlags(sf, FScopeBarrier::Side_Play); + if (s->strct->Flags & ZCC_ClearScope) + sf = FScopeBarrier::ChangeSideInObjectFlags(sf, FScopeBarrier::Side_PlainData); // don't inherit the scope from the outer class + } + else + { + // old versions force 'play'. + sf = FScopeBarrier::ChangeSideInObjectFlags(sf, FScopeBarrier::Side_Play); + } + s->strct->Symbol = Create(s->NodeName(), s->Type()); + syms->AddSymbol(s->strct->Symbol); + + for (auto e : s->Enums) + { + auto etype = NewEnum(e->NodeName, s->Type()); + s->Type()->Symbols.AddSymbol(Create(e->NodeName, etype)); + } + } +} + +//========================================================================== +// +// ZCCCompiler :: CreateClassTypes +// +// Creates a PClass for every class so that we get access to the symbol table +// These will be created with unknown size because for that we need to +// process all fields first, but to do that we need the PClass and some +// other info depending on the PClass. +// +//========================================================================== + +void ZCCCompiler::CreateClassTypes() +{ + // we are going to sort the classes array so that entries are sorted in order of inheritance. + + auto OrigClasses = std::move(Classes); + Classes.Clear(); + bool donesomething = true; + while (donesomething) + { + donesomething = false; + for (unsigned i = 0; icls->ParentName; + + if (ParentName != nullptr && ParentName->SiblingNext == ParentName) parent = PClass::FindClass(ParentName->Id); + else if (ParentName == nullptr) parent = RUNTIME_CLASS(DObject); + else + { + // The parent is a dotted name which the type system currently does not handle. + // Once it does this needs to be implemented here. + auto p = ParentName; + FString build; + + do + { + if (build.IsNotEmpty()) build += '.'; + build += FName(p->Id).GetChars(); + p = static_cast(p->SiblingNext); + } while (p != ParentName); + Error(c->cls, "Qualified name '%s' for base class not supported in '%s'", build.GetChars(), FName(c->NodeName()).GetChars()); + parent = RUNTIME_CLASS(DObject); + } + + if (parent != nullptr && (parent->VMType != nullptr || c->NodeName() == NAME_Object)) + { + // The parent exists, we may create a type for this class + if (c->cls->Flags & ZCC_Native) + { + // If this is a native class, its own type must also already exist and not be a runtime class. + auto me = PClass::FindClass(c->NodeName()); + if (me == nullptr) + { + Error(c->cls, "Unknown native class %s", c->NodeName().GetChars()); + // Create a placeholder so that the compiler can continue looking for errors. + me = parent->FindClassTentative(c->NodeName()); + } + else if (me->bRuntimeClass) + { + Error(c->cls, "%s is not a native class", c->NodeName().GetChars()); + } + else + { + DPrintf(DMSG_SPAMMY, "Registered %s as native with parent %s\n", me->TypeName.GetChars(), parent->TypeName.GetChars()); + } + c->cls->Type = NewClassType(me); + me->SourceLumpName = *c->cls->SourceName; + } + else + { + // We will never get here if the name is a duplicate, so we can just do the assignment. + try + { + if (parent->VMType->mVersion > mVersion) + { + Error(c->cls, "Parent class %s of %s not accessible to ZScript version %d.%d.%d", parent->TypeName.GetChars(), c->NodeName().GetChars(), mVersion.major, mVersion.minor, mVersion.revision); + } + auto newclass = parent->CreateDerivedClass(c->NodeName(), TentativeClass); + if (newclass == nullptr) + { + Error(c->cls, "Class name %s already exists", c->NodeName().GetChars()); + } + else + { + c->cls->Type = NewClassType(newclass); + DPrintf(DMSG_SPAMMY, "Created class %s with parent %s\n", c->Type()->TypeName.GetChars(), c->ClassType()->ParentClass->TypeName.GetChars()); + } + } + catch (CRecoverableError &err) + { + Error(c->cls, "%s", err.GetMessage()); + c->cls->Type = nullptr; + } + } + if (c->Type() == nullptr) + { + // create a placeholder so that the compiler can continue looking for errors. + c->cls->Type = NewClassType(parent->FindClassTentative(c->NodeName())); + } + + if (c->cls->Flags & ZCC_Abstract) + c->ClassType()->bAbstract = true; + + if (c->cls->Flags & ZCC_Version) + { + c->Type()->mVersion = c->cls->Version; + } + // + if (mVersion >= MakeVersion(2, 4, 0)) + { + static int incompatible[] = { ZCC_UIFlag, ZCC_Play, ZCC_ClearScope }; + int incompatiblecnt = 0; + for (size_t k = 0; k < countof(incompatible); k++) + if (incompatible[k] & c->cls->Flags) incompatiblecnt++; + + if (incompatiblecnt > 1) + { + Error(c->cls, "Class %s has incompatible flags", c->NodeName().GetChars()); + } + + if (c->cls->Flags & ZCC_UIFlag) + c->Type()->ScopeFlags = EScopeFlags((c->Type()->ScopeFlags&~Scope_Play) | Scope_UI); + if (c->cls->Flags & ZCC_Play) + c->Type()->ScopeFlags = EScopeFlags((c->Type()->ScopeFlags&~Scope_UI) | Scope_Play); + if (parent->VMType->ScopeFlags & (Scope_UI | Scope_Play)) // parent is either ui or play + { + if (c->cls->Flags & (ZCC_UIFlag | ZCC_Play)) + { + Error(c->cls, "Can't change class scope in class %s", c->NodeName().GetChars()); + } + c->Type()->ScopeFlags = FScopeBarrier::ChangeSideInObjectFlags(c->Type()->ScopeFlags, FScopeBarrier::SideFromObjectFlags(parent->VMType->ScopeFlags)); + } + } + else + { + c->Type()->ScopeFlags = FScopeBarrier::ChangeSideInObjectFlags(c->Type()->ScopeFlags, FScopeBarrier::Side_Play); + } + + c->cls->Symbol = Create(c->NodeName(), c->Type()); + OutNamespace->Symbols.AddSymbol(c->cls->Symbol); + Classes.Push(c); + OrigClasses.Delete(i--); + donesomething = true; + } + else if (c->cls->ParentName != nullptr) + { + // No base class found. Now check if something in the unprocessed classes matches. + // If not, print an error. If something is found let's retry again in the next iteration. + bool found = false; + for (auto d : OrigClasses) + { + if (d->NodeName() == c->cls->ParentName->Id) + { + found = true; + break; + } + } + if (!found) + { + Error(c->cls, "Class %s has unknown base class %s", c->NodeName().GetChars(), FName(c->cls->ParentName->Id).GetChars()); + // create a placeholder so that the compiler can continue looking for errors. + c->cls->Type = NewClassType(RUNTIME_CLASS(DObject)->FindClassTentative(c->NodeName())); + c->cls->Symbol = Create(c->NodeName(), c->Type()); + OutNamespace->Symbols.AddSymbol(c->cls->Symbol); + Classes.Push(c); + OrigClasses.Delete(i--); + donesomething = true; + } + } + } + } + + // What's left refers to some other class in the list but could not be resolved. + // This normally means a circular reference. + for (auto c : OrigClasses) + { + Error(c->cls, "Class %s has circular inheritance", FName(c->NodeName()).GetChars()); + c->cls->Type = NewClassType(RUNTIME_CLASS(DObject)->FindClassTentative(c->NodeName())); + c->cls->Symbol = Create(c->NodeName(), c->Type()); + OutNamespace->Symbols.AddSymbol(c->cls->Symbol); + Classes.Push(c); + } + + // Last but not least: Now that all classes have been created, we can create the symbols for the internal enums and link the treenode symbol tables. + for (auto cd : Classes) + { + for (auto e : cd->Enums) + { + auto etype = NewEnum(e->NodeName, cd->Type()); + cd->Type()->Symbols.AddSymbol(Create(e->NodeName, etype)); + } + // Link the tree node tables. We only can do this after we know the class relations. + for (auto cc : Classes) + { + if (cc->ClassType() == cd->ClassType()->ParentClass) + { + cd->TreeNodes.SetParentTable(&cc->TreeNodes); + break; + } + } + } +} + +//========================================================================== +// +// ZCCCompiler :: AddConstants +// +// Helper for CompileAllConstants +// +//========================================================================== + +void ZCCCompiler::CopyConstants(TArray &dest, TArray &Constants, PContainerType *cls, PSymbolTable *ot) +{ + for (auto c : Constants) + { + dest.Push({ c, cls, ot }); + } +} + +//========================================================================== +// +// ZCCCompiler :: CompileAllConstants +// +// Make symbols from every constant defined at all levels. +// Since constants may only depend on other constants this can be done +// without any more involved processing of the AST as a first step. +// +//========================================================================== + +void ZCCCompiler::CompileAllConstants() +{ + // put all constants in one list to make resolving this easier. + TArray constantwork; + + CopyConstants(constantwork, Constants, nullptr, &OutNamespace->Symbols); + for (auto c : Classes) + { + CopyConstants(constantwork, c->Constants, c->Type(), &c->Type()->Symbols); + } + for (auto s : Structs) + { + if (s->Type() != nullptr) + CopyConstants(constantwork, s->Constants, s->Type(), &s->Type()->Symbols); + } + + // Before starting to resolve the list, let's create symbols for all already resolved ones first (i.e. all literal constants), to reduce work. + for (unsigned i = 0; iValue->NodeType == AST_ExprConstant) + { + AddConstant(constantwork[i]); + // Remove the constant from the list + constantwork.Delete(i); + i--; + } + } + bool donesomething = true; + // Now go through this list until no more constants can be resolved. The remaining ones will be non-constant values. + while (donesomething && constantwork.Size() > 0) + { + donesomething = false; + for (unsigned i = 0; i < constantwork.Size(); i++) + { + if (CompileConstant(&constantwork[i])) + { + AddConstant(constantwork[i]); + // Remove the constant from the list + constantwork.Delete(i); + i--; + donesomething = true; + } + } + } + for (unsigned i = 0; i < constantwork.Size(); i++) + { + Error(constantwork[i].node, "%s is not a constant", FName(constantwork[i].node->NodeName).GetChars()); + } + + + for (auto s : Structs) + { + CompileArrays(s); + } + for (auto c : Classes) + { + CompileArrays(c); + } +} + +//========================================================================== +// +// ZCCCompiler :: AddConstant +// +// Adds a constant to its assigned symbol table +// +//========================================================================== + +void ZCCCompiler::AddConstant(ZCC_ConstantWork &constant) +{ + auto def = constant.node; + auto val = def->Value; + ExpVal &c = constant.constval; + + // This is for literal constants. + if (val->NodeType == AST_ExprConstant) + { + ZCC_ExprConstant *cval = static_cast(val); + if (cval->Type == TypeString) + { + def->Symbol = Create(def->NodeName, *(cval->StringVal)); + } + else if (cval->Type->isInt()) + { + // How do we get an Enum type in here without screwing everything up??? + //auto type = def->Type != nullptr ? def->Type : cval->Type; + def->Symbol = Create(def->NodeName, cval->Type, cval->IntVal); + } + else if (cval->Type->isFloat()) + { + if (def->Type != nullptr) + { + Error(def, "Enum members must be integer values"); + } + def->Symbol = Create(def->NodeName, cval->Type, cval->DoubleVal); + } + else + { + Error(def->Value, "Bad type for constant definiton"); + def->Symbol = nullptr; + } + } + else + { + if (c.Type == TypeString) + { + def->Symbol = Create(def->NodeName, c.GetString()); + } + else if (c.Type->isInt()) + { + // How do we get an Enum type in here without screwing everything up??? + //auto type = def->Type != nullptr ? def->Type : cval->Type; + def->Symbol = Create(def->NodeName, c.Type, c.GetInt()); + } + else if (c.Type->isFloat()) + { + if (def->Type != nullptr) + { + Error(def, "Enum members must be integer values"); + } + def->Symbol = Create(def->NodeName, c.Type, c.GetFloat()); + } + else + { + Error(def->Value, "Bad type for constant definiton"); + def->Symbol = nullptr; + } + } + + if (def->Symbol == nullptr) + { + // Create a dummy constant so we don't make any undefined value warnings. + def->Symbol = Create(def->NodeName, TypeError, 0); + } + constant.Outputtable->ReplaceSymbol(def->Symbol); +} + +//========================================================================== +// +// ZCCCompiler :: CompileConstant +// +// For every constant definition, evaluate its value (which should result +// in a constant), and create a symbol for it. +// +//========================================================================== + +bool ZCCCompiler::CompileConstant(ZCC_ConstantWork *work) +{ + FCompileContext ctx(OutNamespace, work->cls, false); + FxExpression *exp = ConvertNode(work->node->Value); + try + { + FScriptPosition::errorout = true; + exp = exp->Resolve(ctx); + if (exp == nullptr) return false; + FScriptPosition::errorout = false; + if (!exp->isConstant()) + { + delete exp; + return false; + } + work->constval = static_cast(exp)->GetValue(); + delete exp; + return true; + } + catch (...) + { + // eat the reported error and treat this as a temorary failure. All unresolved contants will be reported at the end. + FScriptPosition::errorout = false; + return false; + } +} + + +void ZCCCompiler::CompileArrays(ZCC_StructWork *work) +{ + for(auto sas : work->Arrays) + { + PType *ztype = DetermineType(work->Type(), sas, sas->Id, sas->Type, false, true); + PType *ctype = ztype; + FArgumentList values; + + // Don't use narrow typea for casting. + if (ctype->isInt()) ctype = static_cast(ztype)->Unsigned ? TypeUInt32 : TypeSInt32; + else if (ctype == TypeFloat32) ctype = TypeFloat64; + + ConvertNodeList(values, sas->Values); + + bool fail = false; + FCompileContext ctx(OutNamespace, work->Type(), false); + + char *destmem = (char *)ClassDataAllocator.Alloc(values.Size() * ztype->Align); + memset(destmem, 0, values.Size() * ztype->Align); + char *copyp = destmem; + for (unsigned i = 0; i < values.Size(); i++) + { + values[i] = new FxTypeCast(values[i], ctype, false); + values[i] = values[i]->Resolve(ctx); + if (values[i] == nullptr) fail = true; + else if (!values[i]->isConstant()) + { + Error(sas, "Initializer must be constant"); + fail = true; + } + else + { + ExpVal val = static_cast(values[i])->GetValue(); + switch (ztype->GetRegType()) + { + default: + // should never happen + Error(sas, "Non-integral type in constant array"); + return; + + case REGT_INT: + ztype->SetValue(copyp, val.GetInt()); + break; + + case REGT_FLOAT: + ztype->SetValue(copyp, val.GetFloat()); + break; + + case REGT_POINTER: + *(void**)copyp = val.GetPointer(); + break; + + case REGT_STRING: + ::new(copyp) FString(val.GetString()); + break; + } + copyp += ztype->Align; + } + } + work->Type()->Symbols.AddSymbol(Create(sas->Id, NewArray(ztype, values.Size()), VARF_Static | VARF_ReadOnly, (size_t)destmem)); + } +} + +//========================================================================== +// +// ZCCCompiler :: NodeFromSymbol +// +//========================================================================== + +ZCC_Expression *ZCCCompiler::NodeFromSymbol(PSymbol *sym, ZCC_Expression *source, PSymbolTable *table) +{ + assert(sym != nullptr); + + if (sym->IsKindOf(RUNTIME_CLASS(PSymbolConst))) + { + return NodeFromSymbolConst(static_cast(sym), source); + } + else if (sym->IsKindOf(RUNTIME_CLASS(PSymbolType))) + { + return NodeFromSymbolType(static_cast(sym), source); + } + return NULL; +} + +//========================================================================== +// +// ZCCCompiler :: NodeFromSymbolConst +// +// Returns a new AST constant node with the symbol's content. +// +//========================================================================== + +ZCC_ExprConstant *ZCCCompiler::NodeFromSymbolConst(PSymbolConst *sym, ZCC_Expression *idnode) +{ + ZCC_ExprConstant *val = static_cast(AST.InitNode(sizeof(*val), AST_ExprConstant, idnode)); + val->Operation = PEX_ConstValue; + if (sym == NULL) + { + val->Type = TypeError; + val->IntVal = 0; + } + else if (sym->IsKindOf(RUNTIME_CLASS(PSymbolConstString))) + { + val->StringVal = AST.Strings.Alloc(static_cast(sym)->Str); + val->Type = TypeString; + } + else + { + val->Type = sym->ValueType; + if (val->Type != TypeError) + { + assert(sym->IsKindOf(RUNTIME_CLASS(PSymbolConstNumeric))); + if (sym->ValueType->isIntCompatible()) + { + val->IntVal = static_cast(sym)->Value; + } + else + { + assert(sym->ValueType->isFloat()); + val->DoubleVal = static_cast(sym)->Float; + } + } + } + return val; +} + +//========================================================================== +// +// ZCCCompiler :: NodeFromSymbolType +// +// Returns a new AST type ref node with the symbol's content. +// +//========================================================================== + +ZCC_ExprTypeRef *ZCCCompiler::NodeFromSymbolType(PSymbolType *sym, ZCC_Expression *idnode) +{ + ZCC_ExprTypeRef *ref = static_cast(AST.InitNode(sizeof(*ref), AST_ExprTypeRef, idnode)); + ref->Operation = PEX_TypeRef; + ref->RefType = sym->Type; + ref->Type = NewClassPointer(RUNTIME_CLASS(DObject)); + return ref; +} + +//========================================================================== +// +// ZCCCompiler :: CompileAllFields +// +// builds the internal structure of all classes and structs +// +//========================================================================== + +void ZCCCompiler::CompileAllFields() +{ + // Create copies of the arrays which can be altered + auto Classes = this->Classes; + auto Structs = this->Structs; + TMap HasNativeChildren; + + // first step: Look for native classes with native children. + // These may not have any variables added to them because it'd clash with the native definitions. + for (unsigned i = 0; i < Classes.Size(); i++) + { + auto c = Classes[i]; + + if (c->Type()->Size != TentativeClass && c->Fields.Size() > 0) + { + // We need to search the global class table here because not all children may have a scripted definition attached. + for (auto ac : PClass::AllClasses) + { + if (ac->ParentClass != nullptr && ac->ParentClass->VMType == c->Type() && ac->Size != TentativeClass) + { + // Only set a marker here, so that we can print a better message when the actual fields get added. + HasNativeChildren.Insert(c->Type()->TypeName, true); + break; + } + } + } + } + bool donesomething = true; + while (donesomething && (Structs.Size() > 0 || Classes.Size() > 0)) + { + donesomething = false; + for (unsigned i = 0; i < Structs.Size(); i++) + { + if (CompileFields(Structs[i]->Type(), Structs[i]->Fields, Structs[i]->Outer, Structs[i]->Type() == 0? GlobalTreeNodes : &Structs[i]->TreeNodes, true)) + { + // Remove from the list if all fields got compiled. + Structs.Delete(i--); + donesomething = true; + } + } + for (unsigned i = 0; i < Classes.Size(); i++) + { + auto type = Classes[i]->ClassType(); + + if (type->Size == TentativeClass) + { + if (type->ParentClass->Size == TentativeClass) + { + // we do not know the parent class's size yet, so skip this class for now. + continue; + } + else + { + // Inherit the size of the parent class + type->Size = Classes[i]->ClassType()->ParentClass->Size; + } + } + /*if (type->TypeName == NAME_Actor) + { + assert(type->MetaSize == 0); + AddActorInfo(type); // AActor needs the actor info manually added to its meta data before adding any scripted fields. + } + else*/ if (Classes[i]->ClassType()->ParentClass) + type->MetaSize = Classes[i]->ClassType()->ParentClass->MetaSize; + else + type->MetaSize = 0; + + if (CompileFields(type->VMType, Classes[i]->Fields, nullptr, &Classes[i]->TreeNodes, false, !!HasNativeChildren.CheckKey(type->TypeName))) + { + // Remove from the list if all fields got compiled. + Classes.Delete(i--); + donesomething = true; + } + } + } + // This really should never happen, but if it does, let's better print an error. + for (auto s : Structs) + { + Error(s->strct, "Unable to resolve all fields for struct %s", FName(s->NodeName()).GetChars()); + } + for (auto s : Classes) + { + Error(s->cls, "Unable to resolve all fields for class %s", FName(s->NodeName()).GetChars()); + } +} + +//========================================================================== +// +// ZCCCompiler :: CompileFields +// +// builds the internal structure of a single class or struct +// +//========================================================================== + +bool ZCCCompiler::CompileFields(PContainerType *type, TArray &Fields, PClass *Outer, PSymbolTable *TreeNodes, bool forstruct, bool hasnativechildren) +{ + while (Fields.Size() > 0) + { + auto field = Fields[0]; + FieldDesc *fd = nullptr; + + PType *fieldtype = DetermineType(type, field, field->Names->Name, field->Type, true, true); + + // For structs only allow 'deprecated', for classes exclude function qualifiers. + int notallowed = forstruct? + ZCC_Latent | ZCC_Final | ZCC_Action | ZCC_Static | ZCC_FuncConst | ZCC_Abstract | ZCC_Virtual | ZCC_Override | ZCC_Meta | ZCC_Extension | ZCC_VirtualScope | ZCC_ClearScope : + ZCC_Latent | ZCC_Final | ZCC_Action | ZCC_Static | ZCC_FuncConst | ZCC_Abstract | ZCC_Virtual | ZCC_Override | ZCC_Extension | ZCC_VirtualScope | ZCC_ClearScope; + + // Some internal fields need to be set to clearscope. + if (fileSystem.GetFileContainer(Lump) == 0) notallowed &= ~ZCC_ClearScope; + + if (field->Flags & notallowed) + { + Error(field, "Invalid qualifiers for %s (%s not allowed)", FName(field->Names->Name).GetChars(), FlagsToString(field->Flags & notallowed).GetChars()); + field->Flags &= notallowed; + } + uint32_t varflags = 0; + + // These map directly to implementation flags. + if (field->Flags & ZCC_Private) varflags |= VARF_Private; + if (field->Flags & ZCC_Protected) varflags |= VARF_Protected; + if (field->Flags & ZCC_Deprecated) varflags |= VARF_Deprecated; + if (field->Flags & ZCC_ReadOnly) varflags |= VARF_ReadOnly; + if (field->Flags & ZCC_Internal) varflags |= VARF_InternalAccess; + if (field->Flags & ZCC_Transient) varflags |= VARF_Transient; + if (mVersion >= MakeVersion(2, 4, 0)) + { + if (type != nullptr) + { + if (type->ScopeFlags & Scope_UI) + varflags |= VARF_UI; + if (type->ScopeFlags & Scope_Play) + varflags |= VARF_Play; + } + if (field->Flags & ZCC_UIFlag) + varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_UI); + if (field->Flags & ZCC_Play) + varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_Play); + if (field->Flags & ZCC_ClearScope) + varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_PlainData); + } + else + { + varflags |= VARF_Play; + } + + if (field->Flags & ZCC_Native) + { + varflags |= VARF_Native | VARF_Transient; + } + + static int excludescope[] = { ZCC_UIFlag, ZCC_Play, ZCC_ClearScope }; + int excludeflags = 0; + int fc = 0; + for (size_t i = 0; i < countof(excludescope); i++) + { + if (field->Flags & excludescope[i]) + { + fc++; + excludeflags |= excludescope[i]; + } + } + if (fc > 1) + { + Error(field, "Invalid combination of scope qualifiers %s on field %s", FlagsToString(excludeflags).GetChars(), FName(field->Names->Name).GetChars()); + varflags &= ~(VARF_UI | VARF_Play); // make plain data + } + + if (field->Flags & ZCC_Meta) + { + varflags |= VARF_Meta | VARF_Static | VARF_ReadOnly; // metadata implies readonly + } + + if (field->Type->ArraySize != nullptr) + { + bool nosize; + fieldtype = ResolveArraySize(fieldtype, field->Type->ArraySize, type, &nosize); + + if (nosize) + { + Error(field, "Must specify array size"); + } + } + + auto name = field->Names; + do + { + if (fieldtype->Size == 0 && !(varflags & VARF_Native)) // Size not known yet. + { + return false; + } + + if (AddTreeNode(name->Name, name, TreeNodes, !forstruct)) + { + auto thisfieldtype = fieldtype; + if (name->ArraySize != nullptr) + { + bool nosize; + thisfieldtype = ResolveArraySize(thisfieldtype, name->ArraySize, type, &nosize); + + if (nosize) + { + Error(field, "Must specify array size"); + } + } + + PField *f = nullptr; + + if (varflags & VARF_Native) + { + if (varflags & VARF_Meta) + { + Error(field, "Native meta variable %s not allowed", FName(name->Name).GetChars()); + } + else + { + fd = FindField(type, FName(name->Name).GetChars()); + if (fd == nullptr) + { + Error(field, "The member variable '%s.%s' has not been exported from the executable.", type == nullptr? "" : type->TypeName.GetChars(), FName(name->Name).GetChars()); + } + // For native structs a size check cannot be done because they normally have no size. But for a native reference they are still fine. + else if (thisfieldtype->Size != ~0u && fd->FieldSize != ~0u && thisfieldtype->Size != fd->FieldSize && fd->BitValue == 0 && + (!thisfieldtype->isStruct() || !static_cast(thisfieldtype)->isNative)) + { + Error(field, "The member variable '%s.%s' has mismatching sizes in internal and external declaration. (Internal = %d, External = %d)", type == nullptr ? "" : type->TypeName.GetChars(), FName(name->Name).GetChars(), fd->FieldSize, thisfieldtype->Size); + } + // Q: Should we check alignment, too? A mismatch may be an indicator for bad assumptions. + else if (type != nullptr) + { + // for bit fields the type must point to the source variable. + if (fd->BitValue != 0) thisfieldtype = fd->FieldSize == 1 ? TypeUInt8 : fd->FieldSize == 2 ? TypeUInt16 : TypeUInt32; + f = type->AddNativeField(name->Name, thisfieldtype, fd->FieldOffset, varflags, fd->BitValue); + } + else + { + + // This is a global variable. + if (fd->BitValue != 0) thisfieldtype = fd->FieldSize == 1 ? TypeUInt8 : fd->FieldSize == 2 ? TypeUInt16 : TypeUInt32; + f = Create(name->Name, thisfieldtype, varflags | VARF_Native | VARF_Static, fd->FieldOffset, fd->BitValue); + + if (OutNamespace->Symbols.AddSymbol(f) == nullptr) + { // name is already in use + f->Destroy(); + return false; + } + } + } + } + else if (hasnativechildren && !(varflags & VARF_Meta)) + { + Error(field, "Cannot add field %s to %s. %s has native children which means it size may not change", FName(name->Name).GetChars(), type->TypeName.GetChars(), type->TypeName.GetChars()); + } + else if (type != nullptr) + { + f = type->AddField(name->Name, thisfieldtype, varflags); + } + else + { + Error(field, "Cannot declare non-native global variables. Tried to declare %s", FName(name->Name).GetChars()); + } + + if ((field->Flags & (ZCC_Version | ZCC_Deprecated)) && f != nullptr) + { + f->mVersion = field->Version; + + if (field->DeprecationMessage != nullptr) + { + f->DeprecationMessage = *field->DeprecationMessage; + } + } + } + name = static_cast(name->SiblingNext); + } while (name != field->Names); + Fields.Delete(0); + } + return Fields.Size() == 0; +} + +//========================================================================== +// +// ZCCCompiler :: FieldFlagsToString +// +// creates a string for a field's flags +// +//========================================================================== + +FString ZCCCompiler::FlagsToString(uint32_t flags) +{ + + const char *flagnames[] = { "native", "static", "private", "protected", "latent", "final", "meta", "action", "deprecated", "readonly", "const", "abstract", "extend", "virtual", "override", "transient", "vararg", "ui", "play", "clearscope", "virtualscope" }; + FString build; + + for (size_t i = 0; i < countof(flagnames); i++) + { + if (flags & (1 << i)) + { + if (build.IsNotEmpty()) build += ", "; + build += flagnames[i]; + } + } + return build; +} + +//========================================================================== +// +// ZCCCompiler :: DetermineType +// +// retrieves the type for this field, for arrays the type of a single entry. +// +//========================================================================== + +PType *ZCCCompiler::DetermineType(PType *outertype, ZCC_TreeNode *field, FName name, ZCC_Type *ztype, bool allowarraytypes, bool formember) +{ + PType *retval = TypeError; + if (!allowarraytypes && ztype->ArraySize != nullptr) + { + Error(field, "%s: Array type not allowed", name.GetChars()); + return TypeError; + } + switch (ztype->NodeType) + { + case AST_BasicType: + { + auto btype = static_cast(ztype); + switch (btype->Type) + { + case ZCC_SInt8: + retval = formember? TypeSInt8 : (PType*)TypeError; + break; + + case ZCC_UInt8: + retval = formember ? TypeUInt8 : (PType*)TypeError; + break; + + case ZCC_SInt16: + retval = formember ? TypeSInt16 : (PType*)TypeError; + break; + + case ZCC_UInt16: + retval = formember ? TypeUInt16 : (PType*)TypeError; + break; + + case ZCC_SInt32: + case ZCC_IntAuto: // todo: for enums, autoselect appropriately sized int + retval = TypeSInt32; + break; + + case ZCC_UInt32: + retval = TypeUInt32; + break; + + case ZCC_Bool: + retval = TypeBool; + break; + + case ZCC_FloatAuto: + retval = formember ? TypeFloat32 : TypeFloat64; + break; + + case ZCC_Float64: + retval = TypeFloat64; + break; + + case ZCC_String: + retval = TypeString; + break; + + case ZCC_Name: + retval = TypeName; + break; + + case ZCC_Vector2: + retval = TypeVector2; + break; + + case ZCC_Vector3: + retval = TypeVector3; + break; + + case ZCC_State: + retval = TypeState; + break; + + case ZCC_Color: + retval = TypeColor; + break; + + case ZCC_Sound: + retval = TypeSound; + break; + + case ZCC_Let: + retval = TypeAuto; + break; + + case ZCC_NativeType: + + // Creating an instance of a native struct is only allowed for internal definitions of native variables. + if (fileSystem.GetFileContainer(Lump) != 0 || !formember) + { + Error(field, "%s: @ not allowed for user scripts", name.GetChars()); + } + retval = ResolveUserType(btype, outertype? &outertype->Symbols : nullptr, true); + break; + + case ZCC_UserType: + // statelabel et.al. are not tokens - there really is no need to, it works just as well as an identifier. Maybe the same should be done for some other types, too? + switch (btype->UserType->Id) + { + case NAME_Voidptr: + retval = TypeVoidPtr; + break; + + case NAME_StateLabel: + retval = TypeStateLabel; + break; + + case NAME_SpriteID: + retval = TypeSpriteID; + break; + + case NAME_TextureID: + retval = TypeTextureID; + break; + + default: + retval = ResolveUserType(btype, outertype ? &outertype->Symbols : nullptr, false); + break; + } + break; + + default: + break; + } + break; + } + + case AST_MapType: + if (allowarraytypes) + { + Error(field, "%s: Map types not implemented yet", name.GetChars()); + // Todo: Decide what we allow here and if it makes sense to allow more complex constructs. + auto mtype = static_cast(ztype); + retval = NewMap(DetermineType(outertype, field, name, mtype->KeyType, false, false), DetermineType(outertype, field, name, mtype->ValueType, false, false)); + break; + } + break; + + case AST_DynArrayType: + { + auto atype = static_cast(ztype); + auto ftype = DetermineType(outertype, field, name, atype->ElementType, false, true); + if (ftype->GetRegType() == REGT_NIL || ftype->GetRegCount() > 1) + { + if (field->NodeType == AST_VarDeclarator && (static_cast(field)->Flags & ZCC_Native) && fileSystem.GetFileContainer(Lump) == 0) + { + // the internal definitions may declare native arrays to complex types. + // As long as they can be mapped to a static array type the VM can handle them, in a limited but sufficient fashion. + retval = NewPointer(NewStaticArray(ftype), false); + retval->Size = ~0u; // don't check for a size match, it's likely to fail anyway. + retval->Align = ~0u; + } + else + { + Error(field, "%s: Base type for dynamic array types must be integral, but got %s", name.GetChars(), ftype->DescriptiveName()); + } + } + else + { + retval = NewDynArray(ftype); + } + break; + } + case AST_ClassType: + { + auto ctype = static_cast(ztype); + if (ctype->Restriction == nullptr) + { + retval = NewClassPointer(RUNTIME_CLASS(DObject)); + } + else + { + // This doesn't check the class list directly but the current symbol table to ensure that + // this does not reference a type that got shadowed by a more local definition. + // We first look in the current class and its parents, and then in the current namespace and its parents. + auto sym = outertype ? outertype->Symbols.FindSymbol(ctype->Restriction->Id, true) : nullptr; + if (sym == nullptr) sym = OutNamespace->Symbols.FindSymbol(ctype->Restriction->Id, true); + if (sym == nullptr) + { + // A symbol with a given name cannot be reached from this definition point, so + // even if a class with the given name exists, it is not accessible. + Error(field, "%s: Unknown identifier", FName(ctype->Restriction->Id).GetChars()); + return TypeError; + } + auto typesym = dyn_cast(sym); + if (typesym == nullptr || !typesym->Type->isClass()) + { + Error(field, "%s does not represent a class type", FName(ctype->Restriction->Id).GetChars()); + return TypeError; + } + if (typesym->Type->mVersion > mVersion) + { + Error(field, "Class %s not accessible to ZScript version %d.%d.%d", FName(ctype->Restriction->Id).GetChars(), mVersion.major, mVersion.minor, mVersion.revision); + return TypeError; + } + retval = NewClassPointer(static_cast(typesym->Type)->Descriptor); + } + break; + } + + default: + break; + } + if (retval != TypeError && retval->MemberOnly && !formember) + { + Error(field, "Invalid type %s", retval->DescriptiveName()); + return TypeError; + } + return retval; +} + +//========================================================================== +// +// ZCCCompiler :: ResolveUserType +// +// resolves a user type and returns a matching PType +// +//========================================================================== + +PType *ZCCCompiler::ResolveUserType(ZCC_BasicType *type, PSymbolTable *symt, bool nativetype) +{ + // Check the symbol table for the identifier. + PSymbol *sym = nullptr; + + // We first look in the current class and its parents, and then in the current namespace and its parents. + if (symt != nullptr) sym = symt->FindSymbol(type->UserType->Id, true); + if (sym == nullptr) sym = OutNamespace->Symbols.FindSymbol(type->UserType->Id, true); + if (sym != nullptr && sym->IsKindOf(RUNTIME_CLASS(PSymbolType))) + { + auto ptype = static_cast(sym)->Type; + if (ptype->mVersion > mVersion) + { + Error(type, "Type %s not accessible to ZScript version %d.%d.%d", FName(type->UserType->Id).GetChars(), mVersion.major, mVersion.minor, mVersion.revision); + return TypeError; + } + + if (ptype->isEnum()) + { + if (!nativetype) return TypeSInt32; // hack this to an integer until we can resolve the enum mess. + } + else if (ptype->isClass()) // classes cannot be instantiated at all, they always get used as references. + { + return NewPointer(ptype, type->isconst); + } + else if (ptype->isStruct() && static_cast(ptype)->isNative) // native structs and classes cannot be instantiated, they always get used as reference. + { + if (!nativetype) return NewPointer(ptype, type->isconst); + return ptype; // instantiation of native structs. Only for internal use. + } + if (!nativetype) return ptype; + } + Error(type, "Unable to resolve %s%s as type.", nativetype? "@" : "", FName(type->UserType->Id).GetChars()); + return TypeError; +} + + +//========================================================================== +// +// ZCCCompiler :: ResolveArraySize +// +// resolves the array size and returns a matching type. +// +//========================================================================== + +PType *ZCCCompiler::ResolveArraySize(PType *baseType, ZCC_Expression *arraysize, PContainerType *cls, bool *nosize) +{ + TArray indices; + + // Simplify is too broken to resolve this inside the ring list so unravel the list into an array before starting to simplify its components. + auto node = arraysize; + do + { + indices.Push(node); + node = static_cast(node->SiblingNext); + } while (node != arraysize); + + if (indices.Size() == 1 && indices [0]->Operation == PEX_Nil) + { + *nosize = true; + return baseType; + } + + if (mVersion >= MakeVersion(3, 7, 2)) + { + TArray fixedIndices; + for (auto node : indices) + { + fixedIndices.Insert (0, node); + } + + indices = std::move(fixedIndices); + } + + FCompileContext ctx(OutNamespace, cls, false); + for (auto node : indices) + { + // There is no float->int casting here. + FxExpression *ex = ConvertNode(node); + ex = ex->Resolve(ctx); + + if (ex == nullptr) return TypeError; + if (!ex->isConstant() || !ex->ValueType->isInt()) + { + Error(arraysize, "Array index must be an integer constant"); + return TypeError; + } + int size = static_cast(ex)->GetValue().GetInt(); + if (size < 1) + { + Error(arraysize, "Array size must be positive"); + return TypeError; + } + baseType = NewArray(baseType, size); + } + + *nosize = false; + return baseType; +} + +//========================================================================== +// +// +// +//========================================================================== + + + +void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool forclass) +{ + TArray rets(1); + TArray args; + TArray argflags; + TArray argdefaults; + TArray argnames; + + rets.Clear(); + args.Clear(); + argflags.Clear(); + bool hasdefault = false; + // For the time being, let's not allow overloading. This may be reconsidered later but really just adds an unnecessary amount of complexity here. + if (AddTreeNode(f->Name, f, &c->TreeNodes, false)) + { + auto t = f->Type; + if (t != nullptr) + { + do + { + auto type = DetermineType(c->Type(), f, f->Name, t, false, false); + if (type->isContainer() && type != TypeVector2 && type != TypeVector3) + { + // structs and classes only get passed by pointer. + type = NewPointer(type); + } + else if (type->isDynArray()) + { + Error(f, "The return type of a function cannot be a dynamic array"); + break; + } + // TBD: disallow certain types? For now, let everything pass that isn't an array. + rets.Push(type); + t = static_cast(t->SiblingNext); + } while (t != f->Type); + } + + int notallowed = ZCC_Latent | ZCC_Meta | ZCC_ReadOnly | ZCC_Abstract | ZCC_Internal; + + if (f->Flags & notallowed) + { + Error(f, "Invalid qualifiers for %s (%s not allowed)", FName(f->Name).GetChars(), FlagsToString(f->Flags & notallowed).GetChars()); + f->Flags &= notallowed; + } + uint32_t varflags = VARF_Method; + int implicitargs = 1; + AFuncDesc *afd = nullptr; + int useflags = SUF_ACTOR | SUF_OVERLAY | SUF_WEAPON | SUF_ITEM; + if (f->UseFlags != nullptr) + { + useflags = 0; + auto p = f->UseFlags; + do + { + switch (p->Id) + { + case NAME_Actor: + useflags |= SUF_ACTOR; + break; + case NAME_Overlay: + useflags |= SUF_OVERLAY; + break; + case NAME_Weapon: + useflags |= SUF_WEAPON; + break; + case NAME_Item: + useflags |= SUF_ITEM; + break; + default: + Error(p, "Unknown Action qualifier %s", FName(p->Id).GetChars()); + break; + } + + p = static_cast(p->SiblingNext); + } while (p != f->UseFlags); + } + + // map to implementation flags. + if (f->Flags & ZCC_Private) varflags |= VARF_Private; + if (f->Flags & ZCC_Protected) varflags |= VARF_Protected; + if (f->Flags & ZCC_Deprecated) varflags |= VARF_Deprecated; + if (f->Flags & ZCC_Virtual) varflags |= VARF_Virtual; + if (f->Flags & ZCC_Override) varflags |= VARF_Override; + if (f->Flags & ZCC_VarArg) varflags |= VARF_VarArg; + if (f->Flags & ZCC_FuncConst) varflags |= VARF_ReadOnly; // FuncConst method is internally marked as VARF_ReadOnly + if (mVersion >= MakeVersion(2, 4, 0)) + { + if (c->Type()->ScopeFlags & Scope_UI) + varflags |= VARF_UI; + if (c->Type()->ScopeFlags & Scope_Play) + varflags |= VARF_Play; + //if (f->Flags & ZCC_FuncConst) + // varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_PlainData); // const implies clearscope. this is checked a bit later to also not have ZCC_Play/ZCC_UIFlag. + if (f->Flags & ZCC_UIFlag) + varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_UI); + if (f->Flags & ZCC_Play) + varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_Play); + if (f->Flags & ZCC_ClearScope) + varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_Clear); + if (f->Flags & ZCC_VirtualScope) + varflags = FScopeBarrier::ChangeSideInFlags(varflags, FScopeBarrier::Side_Virtual); + } + else + { + varflags |= VARF_Play; + } + + if ((f->Flags & ZCC_VarArg) && !(f->Flags & ZCC_Native)) + { + Error(f, "'VarArg' can only be used with native methods"); + } + if (f->Flags & ZCC_Action) + { + /* + if (compileEnvironment.processActionFunc) + { + + } + else + */ + Error(f, "'Action' not allowed as a function qualifier"); + } + if (f->Flags & ZCC_Static) varflags = (varflags & ~VARF_Method) | VARF_Final, implicitargs = 0; // Static implies Final. + + if (varflags & VARF_Override) varflags &= ~VARF_Virtual; // allow 'virtual override'. + // Only one of these flags may be used. + static int exclude[] = { ZCC_Virtual, ZCC_Override, ZCC_Action, ZCC_Static }; + int excludeflags = 0; + int fc = 0; + for (size_t i = 0; i < countof(exclude); i++) + { + if (f->Flags & exclude[i]) + { + fc++; + excludeflags |= exclude[i]; + } + } + if (fc > 1) + { + Error(f, "Invalid combination of qualifiers %s on function %s", FlagsToString(excludeflags).GetChars(), FName(f->Name).GetChars()); + varflags |= VARF_Method; + } + if (varflags & VARF_Override) varflags |= VARF_Virtual; // Now that the flags are checked, make all override functions virtual as well. + + // [ZZ] this doesn't make sense either. + if ((varflags&(VARF_ReadOnly | VARF_Method)) == VARF_ReadOnly) // non-method const function + { + Error(f, "'Const' on a static method is not supported"); + } + + // [ZZ] neither this + if ((varflags&(VARF_VirtualScope | VARF_Method)) == VARF_VirtualScope) // non-method virtualscope function + { + Error(f, "'VirtualScope' on a static method is not supported"); + } + + static int excludescope[] = { ZCC_UIFlag, ZCC_Play, ZCC_ClearScope, ZCC_VirtualScope }; + excludeflags = 0; + fc = 0; + for (size_t i = 0; i < countof(excludescope); i++) + { + if (f->Flags & excludescope[i]) + { + fc++; + excludeflags |= excludescope[i]; + } + } + if (fc > 1) + { + Error(f, "Invalid combination of scope qualifiers %s on function %s", FlagsToString(excludeflags).GetChars(), FName(f->Name).GetChars()); + varflags &= ~(VARF_UI | VARF_Play); // make plain data + } + + if (f->Flags & ZCC_Native) + { + varflags |= VARF_Native; + afd = FindFunction(c->Type(), FName(f->Name).GetChars()); + if (afd == nullptr) + { + Error(f, "The function '%s.%s' has not been exported from the executable", c->Type()->TypeName.GetChars(), FName(f->Name).GetChars()); + } + else + { + (*afd->VMPointer)->ImplicitArgs = uint8_t(implicitargs); + } + } + SetImplicitArgs(&args, &argflags, &argnames, c->Type(), varflags, useflags); + argdefaults.Resize(argnames.Size()); + auto p = f->Params; + bool hasoptionals = false; + if (p != nullptr) + { + bool overridemsg = false; + do + { + int elementcount = 1; + TypedVMValue vmval[3]; // default is REGT_NIL which means 'no default value' here. + if (p->Type != nullptr) + { + auto type = DetermineType(c->Type(), p, f->Name, p->Type, false, false); + int flags = 0; + if ((type->isStruct() && type != TypeVector2 && type != TypeVector3) || type->isDynArray()) + { + // Structs are being passed by pointer, but unless marked 'out' that pointer must be readonly. + type = NewPointer(type /*, !(p->Flags & ZCC_Out)*/); + flags |= VARF_Ref; + } + else if (type->GetRegType() != REGT_NIL) + { + if (p->Flags & ZCC_Out) flags |= VARF_Out; + if (type == TypeVector2) + { + elementcount = 2; + } + else if (type == TypeVector3) + { + elementcount = 3; + } + } + if (type->GetRegType() == REGT_NIL && type != TypeVector2 && type != TypeVector3) + { + Error(p, "Invalid type %s for function parameter", type->DescriptiveName()); + } + else if (p->Default != nullptr) + { + if (flags & VARF_Out) + { + Error(p, "Out parameters cannot have default values"); + } + + flags |= VARF_Optional; + hasoptionals = true; + + if ((varflags & VARF_Override) && !overridemsg) + { + // This is illegal, but in older compilers wasn't checked, so there it has to be demoted to a warning. + // Virtual calls always need to get their defaults from the base virtual method. + if (mVersion >= MakeVersion(3, 3)) + { + Error(p, "Default values for parameter of virtual override not allowed"); + } + else + { + Warn(p, "Default values for parameter of virtual override will be ignored!"); + } + overridemsg = true; + } + + + FxExpression *x = new FxTypeCast(ConvertNode(p->Default), type, false); + FCompileContext ctx(OutNamespace, c->Type(), false); + x = x->Resolve(ctx); + + if (x != nullptr) + { + // Vectors need special treatment because they use more than one entry in the Defaults and do not report as actual constants + if (type == TypeVector2 && x->ExprType == EFX_VectorValue && static_cast(x)->isConstVector(2)) + { + auto vx = static_cast(x); + vmval[0] = static_cast(vx->xyz[0])->GetValue().GetFloat(); + vmval[1] = static_cast(vx->xyz[1])->GetValue().GetFloat(); + } + else if (type == TypeVector3 && x->ExprType == EFX_VectorValue && static_cast(x)->isConstVector(3)) + { + auto vx = static_cast(x); + vmval[0] = static_cast(vx->xyz[0])->GetValue().GetFloat(); + vmval[1] = static_cast(vx->xyz[1])->GetValue().GetFloat(); + vmval[2] = static_cast(vx->xyz[2])->GetValue().GetFloat(); + } + else if (!x->isConstant()) + { + Error(p, "Default parameter %s is not constant in %s", FName(p->Name).GetChars(), FName(f->Name).GetChars()); + } + else if (x->ValueType != type) + { + Error(p, "Default parameter %s could not be converted to target type %s", FName(p->Name).GetChars(), c->Type()->TypeName.GetChars()); + } + else + { + auto cnst = static_cast(x); + switch (type->GetRegType()) + { + case REGT_INT: + vmval[0] = cnst->GetValue().GetInt(); + break; + + case REGT_FLOAT: + vmval[0] = cnst->GetValue().GetFloat(); + break; + + case REGT_POINTER: + if (type->isClassPointer()) + vmval[0] = (DObject*)cnst->GetValue().GetPointer(); + else + vmval[0] = cnst->GetValue().GetPointer(); + break; + + case REGT_STRING: + // We need a reference to something permanently stored here. + vmval[0] = VMStringConstants.Alloc(cnst->GetValue().GetString()); + break; + + default: + assert(0 && "no valid type for constant"); + break; + } + } + + hasdefault = true; + } + if (x != nullptr) delete x; + } + else if (hasoptionals) + { + Error(p, "All arguments after the first optional one need also be optional"); + } + // TBD: disallow certain types? For now, let everything pass that isn't an array. + args.Push(type); + argflags.Push(flags); + argnames.Push(p->Name); + + } + else + { + args.Push(nullptr); + argflags.Push(0); + argnames.Push(NAME_None); + } + for (int i = 0; i(p->SiblingNext); + } while (p != f->Params); + } + + PFunction *sym = Create(c->Type(), f->Name); + sym->AddVariant(NewPrototype(rets, args), argflags, argnames, afd == nullptr ? nullptr : *(afd->VMPointer), varflags, useflags); + c->Type()->Symbols.ReplaceSymbol(sym); + + if (f->DeprecationMessage != nullptr) + { + sym->Variants[0].DeprecationMessage = *f->DeprecationMessage; + } + + auto vcls = PType::toClass(c->Type()); + auto cls = vcls ? vcls->Descriptor : nullptr; + PFunction *virtsym = nullptr; + if (cls != nullptr && cls->ParentClass != nullptr) virtsym = dyn_cast(cls->ParentClass->VMType->Symbols.FindSymbol(FName(f->Name), true)); + unsigned vindex = ~0u; + if (virtsym != nullptr) + { + auto imp = virtsym->Variants[0].Implementation; + if (imp != nullptr) vindex = imp->VirtualIndex; + else Error(f, "Virtual base function %s not found in %s", FName(f->Name).GetChars(), cls->ParentClass->TypeName.GetChars()); + } + if (f->Flags & (ZCC_Version | ZCC_Deprecated)) + { + sym->mVersion = f->Version; + if (varflags & VARF_Override) + { + Error(f, "Overridden function %s may not alter version restriction in %s", FName(f->Name).GetChars(), cls->ParentClass->TypeName.GetChars()); + } + } + + VMFunction *newfunc = nullptr; + + if (!(f->Flags & ZCC_Native)) + { + if (f->Body == nullptr) + { + Error(f, "Empty function %s", FName(f->Name).GetChars()); + return; + } + else + { + auto code = ConvertAST(c->Type(), f->Body); + if (code != nullptr) + { + newfunc = FunctionBuildList.AddFunction(OutNamespace, mVersion, sym, code, FStringf("%s.%s", c->Type()->TypeName.GetChars(), FName(f->Name).GetChars()), false, -1, 0, Lump); + } + } + } + if (sym->Variants[0].Implementation != nullptr && hasdefault) // do not copy empty default lists, they only waste space and processing time. + { + sym->Variants[0].Implementation->DefaultArgs = std::move(argdefaults); + } + + if (sym->Variants[0].Implementation != nullptr) + { + // [ZZ] unspecified virtual function inherits old scope. virtual function scope can't be changed. + sym->Variants[0].Implementation->VarFlags = sym->Variants[0].Flags; + } + + PClass *clstype = forclass? static_cast(c->Type())->Descriptor : nullptr; + if (varflags & VARF_Virtual) + { + if (sym->Variants[0].Implementation == nullptr) + { + Error(f, "Virtual function %s.%s not present", c->Type()->TypeName.GetChars(), FName(f->Name).GetChars()); + return; + } + + if (forclass) + { + auto parentfunc = clstype->ParentClass? dyn_cast(clstype->ParentClass->VMType->Symbols.FindSymbol(sym->SymbolName, true)) : nullptr; + + int vindex = clstype->FindVirtualIndex(sym->SymbolName, &sym->Variants[0], parentfunc); + // specifying 'override' is necessary to prevent one of the biggest problem spots with virtual inheritance: Mismatching argument types. + if (varflags & VARF_Override) + { + if (vindex == -1) + { + Error(f, "Attempt to override non-existent virtual function %s", FName(f->Name).GetChars()); + } + else + { + auto oldfunc = clstype->Virtuals[vindex]; + if (parentfunc && parentfunc->mVersion > mVersion) + { + Error(f, "Attempt to override function %s which is incompatible with version %d.%d.%d", FName(f->Name).GetChars(), mVersion.major, mVersion.minor, mVersion.revision); + } + if (oldfunc->VarFlags & VARF_Final) + { + Error(f, "Attempt to override final function %s", FName(f->Name).GetChars()); + } + // you can't change ui/play/clearscope for a virtual method. + if (f->Flags & (ZCC_UIFlag|ZCC_Play|ZCC_ClearScope|ZCC_VirtualScope)) + { + Error(f, "Attempt to change scope for virtual function %s", FName(f->Name).GetChars()); + } + // you can't change const qualifier for a virtual method + if ((sym->Variants[0].Implementation->VarFlags & VARF_ReadOnly) && !(oldfunc->VarFlags & VARF_ReadOnly)) + { + Error(f, "Attempt to add const qualifier to virtual function %s", FName(f->Name).GetChars()); + } + // you can't change protected qualifier for a virtual method (i.e. putting private), because this cannot be reliably checked without runtime stuff + if (f->Flags & (ZCC_Private | ZCC_Protected)) + { + Error(f, "Attempt to change private/protected qualifiers for virtual function %s", FName(f->Name).GetChars()); + } + // inherit scope of original function if override not specified + sym->Variants[0].Flags = FScopeBarrier::ChangeSideInFlags(sym->Variants[0].Flags, FScopeBarrier::SideFromFlags(oldfunc->VarFlags)); + // inherit const from original function + if (oldfunc->VarFlags & VARF_ReadOnly) + sym->Variants[0].Flags |= VARF_ReadOnly; + if (oldfunc->VarFlags & VARF_Protected) + sym->Variants[0].Flags |= VARF_Protected; + + clstype->Virtuals[vindex] = sym->Variants[0].Implementation; + sym->Variants[0].Implementation->VirtualIndex = vindex; + sym->Variants[0].Implementation->VarFlags = sym->Variants[0].Flags; + + // Defaults must be identical to parent class + if (parentfunc->Variants[0].Implementation->DefaultArgs.Size() > 0) + { + sym->Variants[0].Implementation->DefaultArgs = parentfunc->Variants[0].Implementation->DefaultArgs; + sym->Variants[0].ArgFlags = parentfunc->Variants[0].ArgFlags; + } + + // Update argument flags for VM function if needed as their list could be incomplete + // At the moment of function creation, arguments with default values were not copied yet from the parent function + if (newfunc != nullptr && sym->Variants[0].ArgFlags.Size() != newfunc->ArgFlags.Size()) + { + for (unsigned i = newfunc->ArgFlags.Size(), count = sym->Variants[0].ArgFlags.Size(); i < count; ++i) + { + newfunc->ArgFlags.Push(sym->Variants[0].ArgFlags[i]); + } + } + } + } + else + { + if (vindex != -1) + { + Error(f, "Function %s attempts to override parent function without 'override' qualifier", FName(f->Name).GetChars()); + } + sym->Variants[0].Implementation->VirtualIndex = clstype->Virtuals.Push(sym->Variants[0].Implementation); + } + } + else + { + Error(p, "Virtual functions can only be defined for classes"); + } + } + else if (forclass) + { + int vindex = clstype->FindVirtualIndex(sym->SymbolName, &sym->Variants[0], nullptr); + if (vindex != -1) + { + Error(f, "Function %s attempts to override parent function without 'override' qualifier", FName(f->Name).GetChars()); + } + } + } +} + +//========================================================================== +// +// Parses the functions list +// +//========================================================================== + +void ZCCCompiler::InitFunctions() +{ + for (auto s : Structs) + { + for (auto f : s->Functions) + { + CompileFunction(s, f, false); + } + } + + for (auto c : Classes) + { + // cannot be done earlier because it requires the parent class to be processed by this code, too. + if (c->ClassType()->ParentClass != nullptr) + { + c->ClassType()->Virtuals = c->ClassType()->ParentClass->Virtuals; + } + for (auto f : c->Functions) + { + CompileFunction(c, f, true); + } + } +} + +//========================================================================== +// +// Convert the AST data for the code generator. +// +//========================================================================== + +FxExpression *ZCCCompiler::ConvertAST(PContainerType *cls, ZCC_TreeNode *ast) +{ + ConvertClass = cls; + // there are two possibilities here: either a single function call or a compound statement. For a compound statement we also need to check if the last thing added was a return. + if (ast->NodeType == AST_ExprFuncCall) + { + auto cp = new FxCompoundStatement(*ast); + cp->Add(new FxReturnStatement(ConvertNode(ast), *ast)); + return cp; + } + else + { + // This must be done here so that we can check for a trailing return statement. + auto x = new FxCompoundStatement(*ast); + auto compound = static_cast(ast); + //bool isreturn = false; + auto node = compound->Content; + if (node != nullptr) do + { + x->Add(ConvertNode(node)); + //isreturn = node->NodeType == AST_ReturnStmt; + node = static_cast(node->SiblingNext); + } while (node != compound->Content); + //if (!isreturn) x->Add(new FxReturnStatement(nullptr, *ast)); + return x; + } +} + + +#define xx(a,z) z, +static int Pex2Tok[] = { +#include "zcc_exprlist.h" +}; + +//========================================================================== +// +// Helper for modify/assign operators +// +//========================================================================== + +static FxExpression *ModifyAssign(FxBinary *operation, FxExpression *left) +{ + auto assignself = static_cast(operation->left); + auto assignment = new FxAssign(left, operation, true); + assignself->Assignment = assignment; + return assignment; +} + + +//========================================================================== +// +// Convert an AST node and its children +// +//========================================================================== + +FxExpression *ZCCCompiler::ConvertNode(ZCC_TreeNode *ast, bool substitute) +{ + if (ast == nullptr) return nullptr; + + switch (ast->NodeType) + { + case AST_ExprFuncCall: + { + auto fcall = static_cast(ast); + + // function names can either be + // - plain identifiers + // - class members + // - array syntax for random() calls. + // Everything else coming here is a syntax error. + FArgumentList args; + switch (fcall->Function->NodeType) + { + case AST_ExprID: + // The function name is a simple identifier. + return new FxFunctionCall(static_cast(fcall->Function)->Identifier, NAME_None, ConvertNodeList(args, fcall->Parameters), *ast); + + case AST_ExprMemberAccess: + { + auto ema = static_cast(fcall->Function); + return new FxMemberFunctionCall(ConvertNode(ema->Left, true), ema->Right, ConvertNodeList(args, fcall->Parameters), *ast); + } + + case AST_ExprBinary: + // Array syntax for randoms. They are internally stored as ExprBinary with both an identifier on the left and right side. + if (fcall->Function->Operation == PEX_ArrayAccess) + { + auto binary = static_cast(fcall->Function); + if (binary->Left->NodeType == AST_ExprID && binary->Right->NodeType == AST_ExprID) + { + return new FxFunctionCall(static_cast(binary->Left)->Identifier, static_cast(binary->Right)->Identifier, ConvertNodeList(args, fcall->Parameters), *ast); + } + } + // fall through if this isn't an array access node. + + default: + Error(fcall, "Invalid function identifier"); + return new FxNop(*ast); // return something so that the compiler can continue. + } + break; + } + + case AST_ClassCast: + { + auto cc = static_cast(ast); + if (cc->Parameters == nullptr || cc->Parameters->SiblingNext != cc->Parameters) + { + Error(cc, "Class type cast requires exactly one parameter"); + return new FxNop(*ast); // return something so that the compiler can continue. + } + auto cls = PClass::FindClass(cc->ClassName); + if (cls == nullptr || cls->VMType == nullptr) + { + Error(cc, "Unknown class %s", FName(cc->ClassName).GetChars()); + return new FxNop(*ast); // return something so that the compiler can continue. + } + return new FxClassPtrCast(cls, ConvertNode(cc->Parameters)); + } + + case AST_StaticArrayStatement: + { + auto sas = static_cast(ast); + PType *ztype = DetermineType(ConvertClass, sas, sas->Id, sas->Type, false, false); + FArgumentList args; + ConvertNodeList(args, sas->Values); + // This has to let the code generator resolve the constants, not the Simplifier, which lacks all the necessary type info. + return new FxStaticArray(ztype, sas->Id, args, *ast); + } + + case AST_ExprMemberAccess: + { + auto memaccess = static_cast(ast); + return new FxMemberIdentifier(ConvertNode(memaccess->Left), memaccess->Right, *ast); + } + + case AST_FuncParm: + { + auto fparm = static_cast(ast); + auto node = ConvertNode(fparm->Value); + if (fparm->Label != NAME_None) node = new FxNamedNode(fparm->Label, node, *ast); + return node; + } + + case AST_ExprID: + { + auto id = static_cast(ast)->Identifier; + if (id == NAME_LevelLocals && substitute) id = NAME_Level; // All static methods of FLevelLocals are now non-static so remap the name right here before passing it to the backend. + return new FxIdentifier(id, *ast); + } + + case AST_ExprConstant: + { + auto cnst = static_cast(ast); + if (cnst->Type == TypeName) + { + return new FxConstant(FName(ENamedName(cnst->IntVal)), *ast); + } + else if (cnst->Type->isInt()) + { + return new FxConstant(cnst->IntVal, *ast); + } + else if (cnst->Type == TypeBool) + { + return new FxConstant(!!cnst->IntVal, *ast); + } + else if (cnst->Type->isFloat()) + { + return new FxConstant(cnst->DoubleVal, *ast); + } + else if (cnst->Type == TypeString) + { + return new FxConstant(*cnst->StringVal, *ast); + } + else if (cnst->Type == TypeNullPtr) + { + return new FxConstant(*ast); + } + else + { + // can there be other types? + Error(cnst, "Unknown constant type %s", cnst->Type->DescriptiveName()); + return new FxConstant(0, *ast); + } + } + + case AST_ExprUnary: + { + auto unary = static_cast(ast); + auto operand = ConvertNode(unary->Operand); + auto op = unary->Operation; + switch (op) + { + case PEX_PostDec: + case PEX_PostInc: + return new FxPostIncrDecr(operand, Pex2Tok[op]); + + case PEX_PreDec: + case PEX_PreInc: + return new FxPreIncrDecr(operand, Pex2Tok[op]); + + case PEX_Negate: + return new FxMinusSign(operand); + + case PEX_AntiNegate: + return new FxPlusSign(operand); + + case PEX_BitNot: + return new FxUnaryNotBitwise(operand); + + case PEX_BoolNot: + return new FxUnaryNotBoolean(operand); + + case PEX_SizeOf: + case PEX_AlignOf: + return new FxSizeAlign(operand, Pex2Tok[op]); + + default: + assert(0 && "Unknown unary operator."); // should never happen + Error(unary, "Unknown unary operator ID #%d", op); + return new FxNop(*ast); + } + break; + } + + + case AST_ExprBinary: + { + auto binary = static_cast(ast); + auto left = ConvertNode(binary->Left); + auto right = ConvertNode(binary->Right); + auto op = binary->Operation; + auto tok = Pex2Tok[op]; + switch (op) + { + case PEX_Add: + case PEX_Sub: + return new FxAddSub(tok, left, right); + + case PEX_Mul: + case PEX_Div: + case PEX_Mod: + return new FxMulDiv(tok, left, right); + + case PEX_Pow: + return new FxPow(left, right); + + case PEX_LeftShift: + case PEX_RightShift: + case PEX_URightShift: + return new FxShift(tok, left, right); + + case PEX_BitAnd: + case PEX_BitOr: + case PEX_BitXor: + return new FxBitOp(tok, left, right); + + case PEX_BoolOr: + case PEX_BoolAnd: + return new FxBinaryLogical(tok, left, right); + + case PEX_LT: + case PEX_LTEQ: + case PEX_GT: + case PEX_GTEQ: + return new FxCompareRel(tok, left, right); + + case PEX_EQEQ: + case PEX_NEQ: + case PEX_APREQ: + return new FxCompareEq(tok, left, right); + + case PEX_Assign: + return new FxAssign(left, right); + + case PEX_AddAssign: + case PEX_SubAssign: + return ModifyAssign(new FxAddSub(tok, new FxAssignSelf(*ast), right), left); + + case PEX_MulAssign: + case PEX_DivAssign: + case PEX_ModAssign: + return ModifyAssign(new FxMulDiv(tok, new FxAssignSelf(*ast), right), left); + + case PEX_LshAssign: + case PEX_RshAssign: + case PEX_URshAssign: + return ModifyAssign(new FxShift(tok, new FxAssignSelf(*ast), right), left); + + case PEX_AndAssign: + case PEX_OrAssign: + case PEX_XorAssign: + return ModifyAssign(new FxBitOp(tok, new FxAssignSelf(*ast), right), left); + + case PEX_LTGTEQ: + return new FxLtGtEq(left, right); + + case PEX_ArrayAccess: + return new FxArrayElement(left, right); + + case PEX_CrossProduct: + case PEX_DotProduct: + return new FxDotCross(tok, left, right); + + case PEX_Is: + return new FxTypeCheck(left, right); + + case PEX_Concat: + return new FxConcat(left, right); + + default: + I_Error("Binary operator %d not implemented yet", op); + } + break; + } + + case AST_ExprTrinary: + { + auto trinary = static_cast(ast); + auto condition = ConvertNode(trinary->Test); + auto left = ConvertNode(trinary->Left); + auto right = ConvertNode(trinary->Right); + + return new FxConditional(condition, left, right); + } + + case AST_VectorValue: + { + auto vecini = static_cast(ast); + auto xx = ConvertNode(vecini->X); + auto yy = ConvertNode(vecini->Y); + auto zz = ConvertNode(vecini->Z); + return new FxVectorValue(xx, yy, zz, *ast); + } + + case AST_LocalVarStmt: + { + auto loc = static_cast(ast); + auto node = loc->Vars; + FxSequence *list = new FxSequence(*ast); + + PType *ztype = DetermineType(ConvertClass, node, node->Name, loc->Type, true, false); + + if (loc->Type->ArraySize != nullptr) + { + bool nosize; + ztype = ResolveArraySize(ztype, loc->Type->ArraySize, ConvertClass, &nosize); + + if (nosize) + { + Error(node, "Must specify array size"); + } + } + + do + { + PType *type; + + bool nosize = false; + if (node->ArraySize != nullptr) + { + type = ResolveArraySize(ztype, node->ArraySize, ConvertClass, &nosize); + + if (nosize && !node->InitIsArray) + { + Error(node, "Must specify array size for non-initialized arrays"); + } + } + else + { + type = ztype; + } + + if (node->InitIsArray && (type->isArray() || type->isDynArray() || nosize)) + { + auto arrtype = static_cast(type); + if (!nosize && (arrtype->ElementType->isArray() || arrtype->ElementType->isDynArray())) + { + Error(node, "Compound initializer not implemented yet for multi-dimensional arrays"); + } + FArgumentList args; + ConvertNodeList(args, node->Init); + + if (nosize) + { + type = NewArray(type, args.Size()); + } + list->Add(new FxLocalArrayDeclaration(type, node->Name, args, 0, *node)); + } + else + { + FxExpression *val = node->Init ? ConvertNode(node->Init) : nullptr; + list->Add(new FxLocalVariableDeclaration(type, node->Name, val, 0, *node)); // todo: Handle flags in the grammar. + } + + node = static_cast(node->SiblingNext); + } while (node != loc->Vars); + return list; + } + + case AST_Expression: + { + auto ret = static_cast(ast); + if (ret->Operation == PEX_Super) + { + return new FxSuper(*ast); + } + break; + } + + case AST_ExpressionStmt: + return ConvertNode(static_cast(ast)->Expression); + + case AST_ReturnStmt: + { + auto ret = static_cast(ast); + FArgumentList args; + ConvertNodeList(args, ret->Values); + if (args.Size() == 0) + { + return new FxReturnStatement(nullptr, *ast); + } + else + { + return new FxReturnStatement(args, *ast); + } + } + + case AST_BreakStmt: + case AST_ContinueStmt: + return new FxJumpStatement(ast->NodeType == AST_BreakStmt ? TK_Break : TK_Continue, *ast); + + case AST_IfStmt: + { + auto iff = static_cast(ast); + FxExpression *const truePath = ConvertImplicitScopeNode(ast, iff->TruePath); + FxExpression *const falsePath = ConvertImplicitScopeNode(ast, iff->FalsePath); + return new FxIfStatement(ConvertNode(iff->Condition), truePath, falsePath, *ast); + } + + case AST_IterationStmt: + { + auto iter = static_cast(ast); + if (iter->CheckAt == ZCC_IterationStmt::End) + { + assert(iter->LoopBumper == nullptr); + FxExpression *const loop = ConvertImplicitScopeNode(ast, iter->LoopStatement); + return new FxDoWhileLoop(ConvertNode(iter->LoopCondition), loop, *ast); + } + else if (iter->LoopBumper != nullptr) + { + FArgumentList bumper; + ConvertNodeList(bumper, iter->LoopBumper); + FxCompoundStatement *bumps = new FxCompoundStatement(*ast); + for (auto &ex : bumper) + { + bumps->Add(ex); + ex = nullptr; + } + return new FxForLoop(nullptr, ConvertNode(iter->LoopCondition), bumps, ConvertNode(iter->LoopStatement), *ast); + } + else + { + FxExpression *const loop = ConvertImplicitScopeNode(ast, iter->LoopStatement); + return new FxWhileLoop(ConvertNode(iter->LoopCondition), loop, *ast); + } + } + + // not yet done + case AST_SwitchStmt: + { + auto swtch = static_cast(ast); + if (swtch->Content->NodeType != AST_CompoundStmt) + { + Error(ast, "Expecting { after 'switch'"); + return new FxNop(*ast); // allow compiler to continue looking for errors. + } + else + { + // The switch content is wrapped into a compound statement which needs to be unraveled here. + auto cmpnd = static_cast(swtch->Content); + FArgumentList args; + return new FxSwitchStatement(ConvertNode(swtch->Condition), ConvertNodeList(args, cmpnd->Content), *ast); + } + } + + case AST_CaseStmt: + { + auto cases = static_cast(ast); + return new FxCaseStatement(ConvertNode(cases->Condition), *ast); + } + + case AST_CompoundStmt: + { + auto x = new FxCompoundStatement(*ast); + auto compound = static_cast(ast); + auto node = compound->Content; + if (node != nullptr) do + { + x->Add(ConvertNode(node)); + node = static_cast(node->SiblingNext); + } while (node != compound->Content); + return x; + } + + case AST_AssignStmt: + { + auto ass = static_cast(ast); + FArgumentList args; + ConvertNodeList(args, ass->Dests); + assert(ass->Sources->SiblingNext == ass->Sources); // right side should be a single function call - nothing else + if (ass->Sources->NodeType != AST_ExprFuncCall) + { + // don't let this through to the code generator. This node is only used to assign multiple returns of a function to more than one variable. + Error(ass, "Right side of multi-assignment must be a function call"); + return new FxNop(*ast); // allow compiler to continue looking for errors. + } + return new FxMultiAssign(args, ConvertNode(ass->Sources), *ast); + } + + default: + break; + } + // only for development. I_Error is more convenient here than a normal error. + I_Error("ConvertNode encountered unsupported node of type %d", ast->NodeType); + return nullptr; +} + +//========================================================================== +// +// Wrapper around ConvertNode() that adds a scope (a compound statement) +// when needed to avoid leaking of variable or contant to an outer scope: +// +// if (true) int i; else bool b[1]; +// while (false) readonly a; +// do static const float f[] = {0}; while (false); +// +// Accessing such variables outside of their statements is now an error +// +//========================================================================== + +FxExpression *ZCCCompiler::ConvertImplicitScopeNode(ZCC_TreeNode *node, ZCC_Statement *nested) +{ + assert(nullptr != node); + + if (nullptr == nested) + { + return nullptr; + } + + FxExpression *nestedExpr = ConvertNode(nested); + assert(nullptr != nestedExpr); + + const EZCCTreeNodeType nestedType = nested->NodeType; + const bool needScope = AST_LocalVarStmt == nestedType || AST_StaticArrayStatement == nestedType; + + if (needScope) + { + FxCompoundStatement *implicitCompound = new FxCompoundStatement(*node); + implicitCompound->Add(nestedExpr); + + nestedExpr = implicitCompound; + } + + return nestedExpr; +} + + +FArgumentList &ZCCCompiler::ConvertNodeList(FArgumentList &args, ZCC_TreeNode *head) +{ + if (head != nullptr) + { + auto node = head; + do + { + args.Push(ConvertNode(node)); + node = node->SiblingNext; + } while (node != head); + } + return args; +} diff --git a/source/common/scripting/frontend/zcc_compile.h b/source/common/scripting/frontend/zcc_compile.h new file mode 100644 index 000000000..01836d3ee --- /dev/null +++ b/source/common/scripting/frontend/zcc_compile.h @@ -0,0 +1,173 @@ +#ifndef ZCC_COMPILE_H +#define ZCC_COMPILE_H + +#include +#include "codegen.h" + +struct Baggage; +struct FPropertyInfo; +class AActor; +class FxExpression; +typedef TDeletingArray FArgumentList; + + +struct ZCC_StructWork +{ + PSymbolTable TreeNodes; + ZCC_Struct *strct; + ZCC_Class *OuterDef; + PClass *Outer; + PSymbolTreeNode *node; + TArray Enums; + TArray Constants; + TArray Fields; + TArray Functions; + TArray Arrays; + + ZCC_StructWork() + { + } + + ZCC_StructWork(ZCC_Struct * s, PSymbolTreeNode *n, ZCC_Class *outer) + { + strct = s; + node = n; + OuterDef = outer; + Outer = nullptr; + }; + + FName NodeName() const + { + return strct->NodeName; + } + + PContainerType *Type() + { + return strct->Type; + } + +}; + +struct ZCC_ClassWork : public ZCC_StructWork +{ + ZCC_Class *cls; + TArray Defaults; + TArray States; + TArray Properties; + TArray FlagDefs; + + ZCC_ClassWork(ZCC_Class * s, PSymbolTreeNode *n) + { + strct = s; + cls = s; + node = n; + OuterDef = nullptr; + Outer = nullptr; + } + + PClass *ClassType() + { + return static_cast(strct->Type)->Descriptor; + } +}; + +struct ZCC_MixinWork +{ + PSymbolTable TreeNodes; + ZCC_MixinDef *mixin; + PSymbolTreeNode *node; + + ZCC_MixinWork() + { + } + + ZCC_MixinWork(ZCC_MixinDef *m, PSymbolTreeNode *n) + { + mixin = m; + node = n; + } +}; + +struct ZCC_PropertyWork +{ + ZCC_Property *prop; + PSymbolTable *outputtable; +}; + +struct ZCC_ConstantWork +{ + ZCC_ConstantDef *node; + PContainerType *cls; + PSymbolTable *Outputtable; + ExpVal constval; +}; + +class ZCCCompiler +{ +public: + ZCCCompiler(ZCC_AST &tree, DObject *outer, PSymbolTable &symbols, PNamespace *outnamespace, int lumpnum, const VersionInfo & ver); + ~ZCCCompiler(); + int Compile(); + +private: + const char * GetStringConst(FxExpression *ex, FCompileContext &ctx); + + int IntConstFromNode(ZCC_TreeNode *node, PContainerType *cls); + FString StringConstFromNode(ZCC_TreeNode *node, PContainerType *cls); + ZCC_MixinDef *ResolveMixinStmt(ZCC_MixinStmt *mixinStmt, EZCCMixinType type); + void ProcessClass(ZCC_Class *node, PSymbolTreeNode *tnode); + void ProcessStruct(ZCC_Struct *node, PSymbolTreeNode *tnode, ZCC_Class *outer); + void ProcessMixin(ZCC_MixinDef *cnode, PSymbolTreeNode *treenode); + void CreateStructTypes(); + void CreateClassTypes(); + void CopyConstants(TArray &dest, TArray &Constants, PContainerType *cls, PSymbolTable *ot); + void CompileAllConstants(); + void AddConstant(ZCC_ConstantWork &constant); + bool CompileConstant(ZCC_ConstantWork *def); + void CompileArrays(ZCC_StructWork *work); + + void CompileAllFields(); + bool CompileFields(PContainerType *type, TArray &Fields, PClass *Outer, PSymbolTable *TreeNodes, bool forstruct, bool hasnativechildren = false); + FString FlagsToString(uint32_t flags); + PType *DetermineType(PType *outertype, ZCC_TreeNode *field, FName name, ZCC_Type *ztype, bool allowarraytypes, bool formember); + PType *ResolveArraySize(PType *baseType, ZCC_Expression *arraysize, PContainerType *cls, bool *nosize); + PType *ResolveUserType(ZCC_BasicType *type, PSymbolTable *sym, bool nativetype); + + void CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool forclass); + + void InitFunctions(); + + TArray Constants; + TArray Structs; + TArray Classes; + TArray Mixins; + TArray Properties; + VersionInfo mVersion; + + PSymbolTreeNode *AddTreeNode(FName name, ZCC_TreeNode *node, PSymbolTable *treenodes, bool searchparents = false); + + ZCC_Expression *NodeFromSymbol(PSymbol *sym, ZCC_Expression *source, PSymbolTable *table); + ZCC_ExprConstant *NodeFromSymbolConst(PSymbolConst *sym, ZCC_Expression *idnode); + ZCC_ExprTypeRef *NodeFromSymbolType(PSymbolType *sym, ZCC_Expression *idnode); + + + void Warn(ZCC_TreeNode *node, const char *msg, ...) GCCPRINTF(3,4); + void Error(ZCC_TreeNode *node, const char *msg, ...) GCCPRINTF(3,4); + void MessageV(ZCC_TreeNode *node, const char *txtcolor, const char *msg, va_list argptr); + + FxExpression *ConvertAST(PContainerType *cclass, ZCC_TreeNode *ast); + FxExpression *ConvertNode(ZCC_TreeNode *node, bool substitute= false); + FxExpression *ConvertImplicitScopeNode(ZCC_TreeNode *node, ZCC_Statement *nested); + FArgumentList &ConvertNodeList(FArgumentList &, ZCC_TreeNode *head); + + DObject *Outer; + PContainerType *ConvertClass; // class type to be used when resoving symbols while converting an AST + PSymbolTable *GlobalTreeNodes; + PNamespace *OutNamespace; + ZCC_AST &AST; + int Lump; +}; + +void ZCC_InitConversions(); + +#endif diff --git a/source/common/scripting/frontend/zcc_exprlist.h b/source/common/scripting/frontend/zcc_exprlist.h new file mode 100644 index 000000000..faf6af6a4 --- /dev/null +++ b/source/common/scripting/frontend/zcc_exprlist.h @@ -0,0 +1,76 @@ +// Name Token used in the code generator +xx(Nil, TK_None) + +xx(ID, TK_Identifier) +xx(Super, TK_Super) +xx(Null, TK_Null) +xx(ConstValue, TK_Const) +xx(FuncCall, '(') +xx(ArrayAccess, TK_Array) +xx(MemberAccess, '.') +xx(ClassCast, TK_Class) +xx(TypeRef, TK_Class) +xx(Vector, TK_Vector2) + +xx(PostInc, TK_Incr) +xx(PostDec, TK_Decr) + +xx(PreInc, TK_Incr) +xx(PreDec, TK_Decr) +xx(Negate, '-') +xx(AntiNegate, '+') +xx(BitNot, '~') +xx(BoolNot, '!') +xx(SizeOf, TK_SizeOf) +xx(AlignOf, TK_AlignOf) + +xx(Add, '+') +xx(Sub, '-') +xx(Mul, '*') +xx(Div, '/') +xx(Mod, '%') +xx(Pow, TK_MulMul) +xx(CrossProduct, TK_Cross) +xx(DotProduct, TK_Dot) +xx(LeftShift, TK_LShift) +xx(RightShift, TK_RShift) +xx(URightShift, TK_URShift) +xx(Concat, TK_DotDot) + +xx(LT, '<') +xx(LTEQ, TK_Leq) +xx(GT, '>') +xx(GTEQ, TK_Geq) +xx(LTGTEQ, TK_LtGtEq) +xx(Is, TK_Is) + +xx(EQEQ, TK_Eq) +xx(NEQ, TK_Neq) +xx(APREQ, TK_ApproxEq) + +xx(BitAnd, '&') +xx(BitOr, '|') +xx(BitXor, '^') +xx(BoolAnd, TK_AndAnd) +xx(BoolOr, TK_OrOr) + +xx(Assign, '=') +xx(AddAssign, '+') // these are what the code generator needs, not what they represent. +xx(SubAssign, '-') +xx(MulAssign, '*') +xx(DivAssign, '/') +xx(ModAssign, '%') +xx(LshAssign, TK_LShift) +xx(RshAssign, TK_RShift) +xx(URshAssign, TK_URShift) +xx(AndAssign, '&') +xx(OrAssign, '|') +xx(XorAssign, '^') + +xx(Scope, TK_ColonColon) + +xx(Trinary, '?') + +xx(Cast, TK_Coerce) + +#undef xx diff --git a/source/common/scripting/frontend/zcc_parser.cpp b/source/common/scripting/frontend/zcc_parser.cpp new file mode 100644 index 000000000..91facfedc --- /dev/null +++ b/source/common/scripting/frontend/zcc_parser.cpp @@ -0,0 +1,1381 @@ +/* +** zcc_expr.cpp +** +**--------------------------------------------------------------------------- +** Copyright -2016 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "dobject.h" +#include "sc_man.h" +#include "filesystem.h" +#include "cmdlib.h" +#include "m_argv.h" +#include "v_text.h" +#include "version.h" +#include "zcc_parser.h" +#include "zcc_compile.h" +#include "templates.h" + +TArray Includes; +TArray IncludeLocs; + +static FString ZCCTokenName(int terminal); +void AddInclude(ZCC_ExprConstant *node) +{ + assert(node->Type == TypeString); + if (Includes.Find(*node->StringVal) >= Includes.Size()) + { + Includes.Push(*node->StringVal); + IncludeLocs.Push(*node); + } +} + +#include "zcc-parse.h" +#include "zcc-parse.c" + +struct TokenMapEntry +{ + int16_t TokenType; + uint16_t TokenName; + TokenMapEntry(int16_t a, uint16_t b) + : TokenType(a), TokenName(b) + { } +}; +static TMap TokenMap; +static int16_t BackTokenMap[YYERRORSYMBOL]; // YYERRORSYMBOL immediately follows the terminals described by the grammar + +#define TOKENDEF2(sc, zcc, name) { TokenMapEntry tme(zcc, name); TokenMap.Insert(sc, tme); } BackTokenMap[zcc] = sc +#define TOKENDEF(sc, zcc) TOKENDEF2(sc, zcc, NAME_None) + +static void InitTokenMap() +{ + TOKENDEF ('=', ZCC_EQ); + TOKENDEF (TK_MulEq, ZCC_MULEQ); + TOKENDEF (TK_DivEq, ZCC_DIVEQ); + TOKENDEF (TK_ModEq, ZCC_MODEQ); + TOKENDEF (TK_AddEq, ZCC_ADDEQ); + TOKENDEF (TK_SubEq, ZCC_SUBEQ); + TOKENDEF (TK_LShiftEq, ZCC_LSHEQ); + TOKENDEF (TK_RShiftEq, ZCC_RSHEQ); + TOKENDEF (TK_URShiftEq, ZCC_URSHEQ); + TOKENDEF (TK_AndEq, ZCC_ANDEQ); + TOKENDEF (TK_OrEq, ZCC_OREQ); + TOKENDEF (TK_XorEq, ZCC_XOREQ); + TOKENDEF ('?', ZCC_QUESTION); + TOKENDEF (':', ZCC_COLON); + TOKENDEF ('@', ZCC_ATSIGN); + TOKENDEF (TK_OrOr, ZCC_OROR); + TOKENDEF (TK_AndAnd, ZCC_ANDAND); + TOKENDEF (TK_Eq, ZCC_EQEQ); + TOKENDEF (TK_Neq, ZCC_NEQ); + TOKENDEF (TK_ApproxEq, ZCC_APPROXEQ); + TOKENDEF ('<', ZCC_LT); + TOKENDEF ('>', ZCC_GT); + TOKENDEF (TK_Leq, ZCC_LTEQ); + TOKENDEF (TK_Geq, ZCC_GTEQ); + TOKENDEF (TK_LtGtEq, ZCC_LTGTEQ); + TOKENDEF (TK_Is, ZCC_IS); + TOKENDEF (TK_DotDot, ZCC_DOTDOT); + TOKENDEF (TK_Ellipsis, ZCC_ELLIPSIS); + TOKENDEF ('|', ZCC_OR); + TOKENDEF ('^', ZCC_XOR); + TOKENDEF ('&', ZCC_AND); + TOKENDEF (TK_LShift, ZCC_LSH); + TOKENDEF (TK_RShift, ZCC_RSH); + TOKENDEF (TK_URShift, ZCC_URSH); + TOKENDEF ('-', ZCC_SUB); + TOKENDEF ('+', ZCC_ADD); + TOKENDEF ('*', ZCC_MUL); + TOKENDEF ('/', ZCC_DIV); + TOKENDEF ('%', ZCC_MOD); + TOKENDEF (TK_Cross, ZCC_CROSSPROD); + TOKENDEF (TK_Dot, ZCC_DOTPROD); + TOKENDEF (TK_MulMul, ZCC_POW); + TOKENDEF (TK_Incr, ZCC_ADDADD); + TOKENDEF (TK_Decr, ZCC_SUBSUB); + TOKENDEF ('.', ZCC_DOT); + TOKENDEF ('(', ZCC_LPAREN); + TOKENDEF (')', ZCC_RPAREN); + TOKENDEF (TK_ColonColon, ZCC_SCOPE); + TOKENDEF (';', ZCC_SEMICOLON); + TOKENDEF (',', ZCC_COMMA); + TOKENDEF (TK_Class, ZCC_CLASS); + TOKENDEF (TK_Abstract, ZCC_ABSTRACT); + TOKENDEF (TK_Native, ZCC_NATIVE); + TOKENDEF (TK_Action, ZCC_ACTION); + TOKENDEF (TK_Replaces, ZCC_REPLACES); + TOKENDEF (TK_Static, ZCC_STATIC); + TOKENDEF (TK_Private, ZCC_PRIVATE); + TOKENDEF (TK_Protected, ZCC_PROTECTED); + TOKENDEF (TK_Latent, ZCC_LATENT); + TOKENDEF (TK_Virtual, ZCC_VIRTUAL); + TOKENDEF (TK_VarArg, ZCC_VARARG); + TOKENDEF (TK_UI, ZCC_UI); + TOKENDEF (TK_Play, ZCC_PLAY); + TOKENDEF (TK_ClearScope, ZCC_CLEARSCOPE); + TOKENDEF (TK_VirtualScope, ZCC_VIRTUALSCOPE); + TOKENDEF (TK_Override, ZCC_OVERRIDE); + TOKENDEF (TK_Final, ZCC_FINAL); + TOKENDEF (TK_Meta, ZCC_META); + TOKENDEF (TK_Deprecated, ZCC_DEPRECATED); + TOKENDEF (TK_Version, ZCC_VERSION); + TOKENDEF (TK_ReadOnly, ZCC_READONLY); + TOKENDEF (TK_Internal, ZCC_INTERNAL); + TOKENDEF ('{', ZCC_LBRACE); + TOKENDEF ('}', ZCC_RBRACE); + TOKENDEF (TK_Struct, ZCC_STRUCT); + TOKENDEF (TK_Property, ZCC_PROPERTY); + TOKENDEF (TK_FlagDef, ZCC_FLAGDEF); + TOKENDEF (TK_Mixin, ZCC_MIXIN); + TOKENDEF (TK_Transient, ZCC_TRANSIENT); + TOKENDEF (TK_Enum, ZCC_ENUM); + TOKENDEF2(TK_SByte, ZCC_SBYTE, NAME_sByte); + TOKENDEF2(TK_Byte, ZCC_BYTE, NAME_Byte); + TOKENDEF2(TK_Short, ZCC_SHORT, NAME_Short); + TOKENDEF2(TK_UShort, ZCC_USHORT, NAME_uShort); + TOKENDEF2(TK_Int8, ZCC_SBYTE, NAME_int8); + TOKENDEF2(TK_UInt8, ZCC_BYTE, NAME_uint8); + TOKENDEF2(TK_Int16, ZCC_SHORT, NAME_int16); + TOKENDEF2(TK_UInt16, ZCC_USHORT, NAME_uint16); + TOKENDEF2(TK_Int, ZCC_INT, NAME_Int); + TOKENDEF2(TK_UInt, ZCC_UINT, NAME_uInt); + TOKENDEF2(TK_Bool, ZCC_BOOL, NAME_Bool); + TOKENDEF2(TK_Float, ZCC_FLOAT, NAME_Float); + TOKENDEF2(TK_Double, ZCC_DOUBLE, NAME_Double); + TOKENDEF2(TK_String, ZCC_STRING, NAME_String); + TOKENDEF2(TK_Vector2, ZCC_VECTOR2, NAME_Vector2); + TOKENDEF2(TK_Vector3, ZCC_VECTOR3, NAME_Vector3); + TOKENDEF2(TK_Name, ZCC_NAME, NAME_Name); + TOKENDEF2(TK_Map, ZCC_MAP, NAME_Map); + TOKENDEF2(TK_Array, ZCC_ARRAY, NAME_Array); + TOKENDEF2(TK_Include, ZCC_INCLUDE, NAME_Include); + TOKENDEF (TK_Void, ZCC_VOID); + TOKENDEF (TK_True, ZCC_TRUE); + TOKENDEF (TK_False, ZCC_FALSE); + TOKENDEF ('[', ZCC_LBRACKET); + TOKENDEF (']', ZCC_RBRACKET); + TOKENDEF (TK_In, ZCC_IN); + TOKENDEF (TK_Out, ZCC_OUT); + TOKENDEF (TK_Super, ZCC_SUPER); + TOKENDEF (TK_Null, ZCC_NULLPTR); + TOKENDEF ('~', ZCC_TILDE); + TOKENDEF ('!', ZCC_BANG); + TOKENDEF (TK_SizeOf, ZCC_SIZEOF); + TOKENDEF (TK_AlignOf, ZCC_ALIGNOF); + TOKENDEF (TK_Continue, ZCC_CONTINUE); + TOKENDEF (TK_Break, ZCC_BREAK); + TOKENDEF (TK_Return, ZCC_RETURN); + TOKENDEF (TK_Do, ZCC_DO); + TOKENDEF (TK_For, ZCC_FOR); + TOKENDEF (TK_While, ZCC_WHILE); + TOKENDEF (TK_Until, ZCC_UNTIL); + TOKENDEF (TK_If, ZCC_IF); + TOKENDEF (TK_Else, ZCC_ELSE); + TOKENDEF (TK_Switch, ZCC_SWITCH); + TOKENDEF (TK_Case, ZCC_CASE); + TOKENDEF2(TK_Default, ZCC_DEFAULT, NAME_Default); + TOKENDEF (TK_Const, ZCC_CONST); + TOKENDEF (TK_Stop, ZCC_STOP); + TOKENDEF (TK_Wait, ZCC_WAIT); + TOKENDEF (TK_Fail, ZCC_FAIL); + TOKENDEF (TK_Loop, ZCC_LOOP); + TOKENDEF (TK_Goto, ZCC_GOTO); + TOKENDEF (TK_States, ZCC_STATES); + TOKENDEF2(TK_State, ZCC_STATE, NAME_State); + TOKENDEF2(TK_Color, ZCC_COLOR, NAME_Color); + TOKENDEF2(TK_Sound, ZCC_SOUND, NAME_Sound); + TOKENDEF2(TK_Let, ZCC_LET, NAME_let); + TOKENDEF2(TK_StaticConst, ZCC_STATICCONST,NAME_Staticconst); + + TOKENDEF (TK_Identifier, ZCC_IDENTIFIER); + TOKENDEF (TK_StringConst, ZCC_STRCONST); + TOKENDEF (TK_NameConst, ZCC_NAMECONST); + TOKENDEF (TK_IntConst, ZCC_INTCONST); + TOKENDEF (TK_UIntConst, ZCC_UINTCONST); + TOKENDEF (TK_FloatConst, ZCC_FLOATCONST); + TOKENDEF (TK_NonWhitespace, ZCC_NWS); + + TOKENDEF (TK_Bright, ZCC_BRIGHT); + TOKENDEF (TK_Slow, ZCC_SLOW); + TOKENDEF (TK_Fast, ZCC_FAST); + TOKENDEF (TK_NoDelay, ZCC_NODELAY); + TOKENDEF (TK_Offset, ZCC_OFFSET); + TOKENDEF (TK_CanRaise, ZCC_CANRAISE); + TOKENDEF (TK_Light, ZCC_LIGHT); + TOKENDEF (TK_Extend, ZCC_EXTEND); +} +#undef TOKENDEF +#undef TOKENDEF2 + +//**-------------------------------------------------------------------------- + +static void ParseSingleFile(FScanner *pSC, const char *filename, int lump, void *parser, ZCCParseState &state) +{ + int tokentype; + //bool failed; + ZCCToken value; + FScanner lsc; + + if (pSC == nullptr) + { + if (filename != nullptr) + { + lump = fileSystem.CheckNumForFullName(filename, true); + if (lump >= 0) + { + lsc.OpenLumpNum(lump); + } + else + { + Printf("Could not find script lump '%s'\n", filename); + return; + } + } + else lsc.OpenLumpNum(lump); + + pSC = &lsc; + } + FScanner &sc = *pSC; + sc.SetParseVersion(state.ParseVersion); + state.sc = ≻ + + while (sc.GetToken()) + { + value.Largest = 0; + value.SourceLoc = sc.GetMessageLine(); + switch (sc.TokenType) + { + case TK_StringConst: + value.String = state.Strings.Alloc(sc.String, sc.StringLen); + tokentype = ZCC_STRCONST; + break; + + case TK_NameConst: + value.Int = FName(sc.String).GetIndex(); + tokentype = ZCC_NAMECONST; + break; + + case TK_IntConst: + value.Int = sc.Number; + tokentype = ZCC_INTCONST; + break; + + case TK_UIntConst: + value.Int = sc.Number; + tokentype = ZCC_UINTCONST; + break; + + case TK_FloatConst: + value.Float = sc.Float; + tokentype = ZCC_FLOATCONST; + break; + + case TK_None: // 'NONE' is a token for SBARINFO but not here. + case TK_Identifier: + value.Int = FName(sc.String).GetIndex(); + tokentype = ZCC_IDENTIFIER; + break; + + case TK_NonWhitespace: + value.Int = FName(sc.String).GetIndex(); + tokentype = ZCC_NWS; + break; + + case TK_Static: + sc.MustGetAnyToken(); + // The oh so wonderful grammar has problems with the 'const' token thanks to the overly broad rule for constants, + // which effectively prevents use of this token nearly anywhere else. So in order to get 'static const' through + // on the class/struct level we have to muck around with the token type here so that both words get combined into + // a single token that doesn't make the grammar throw a fit. + if (sc.TokenType == TK_Const) + { + tokentype = ZCC_STATICCONST; + value.Int = NAME_Staticconst; + } + else + { + tokentype = ZCC_STATIC; + value.Int = NAME_Static; + sc.UnGet(); + } + break; + + default: + TokenMapEntry *zcctoken = TokenMap.CheckKey(sc.TokenType); + if (zcctoken != nullptr) + { + tokentype = zcctoken->TokenType; + value.Int = zcctoken->TokenName; + } + else + { + sc.ScriptMessage("Unexpected token %s.\n", sc.TokenName(sc.TokenType).GetChars()); + goto parse_end; + } + break; + } + ZCCParse(parser, tokentype, value, &state); + } +parse_end: + value.Int = -1; + ZCCParse(parser, ZCC_EOF, value, &state); + state.sc = nullptr; +} + +//**-------------------------------------------------------------------------- + +static void DoParse(int lumpnum) +{ + FScanner sc; + void *parser; + ZCCToken value; + auto baselump = lumpnum; + auto fileno = fileSystem.GetFileContainer(lumpnum); + + parser = ZCCParseAlloc(malloc); + ZCCParseState state; + +#ifndef NDEBUG + FILE *f = nullptr; + const char *tracefile = Args->CheckValue("-tracefile"); + if (tracefile != nullptr) + { + f = fopen(tracefile, "w"); + char prompt = '\0'; + ZCCParseTrace(f, &prompt); + } +#endif + + sc.OpenLumpNum(lumpnum); + sc.SetParseVersion({ 2, 4 }); // To get 'version' we need parse version 2.4 for the initial test + auto saved = sc.SavePos(); + + if (sc.GetToken()) + { + if (sc.TokenType == TK_Version) + { + char *endp; + sc.MustGetString(); + state.ParseVersion.major = (int16_t)clamp(strtoull(sc.String, &endp, 10), 0, USHRT_MAX); + if (*endp != '.') + { + sc.ScriptError("Bad version directive"); + } + state.ParseVersion.minor = (int16_t)clamp(strtoll(endp + 1, &endp, 10), 0, USHRT_MAX); + if (*endp == '.') + { + state.ParseVersion.revision = (int16_t)clamp(strtoll(endp + 1, &endp, 10), 0, USHRT_MAX); + } + else state.ParseVersion.revision = 0; + if (*endp != 0) + { + sc.ScriptError("Bad version directive"); + } + if (state.ParseVersion.major == USHRT_MAX || state.ParseVersion.minor == USHRT_MAX || state.ParseVersion.revision == USHRT_MAX) + { + sc.ScriptError("Bad version directive"); + } + if (state.ParseVersion > MakeVersion(VER_MAJOR, VER_MINOR, VER_REVISION)) + { + sc.ScriptError("The file you are attempting to run requires a newer version of " GAMENAME ".\n\nA version with ZScript version %d.%d.%d is required, but your copy of " GAMENAME " only supports %d.%d.%d. Please upgrade!", state.ParseVersion.major, state.ParseVersion.minor, state.ParseVersion.revision, VER_MAJOR, VER_MINOR, VER_REVISION); + } + } + else + { + state.ParseVersion = MakeVersion(2, 3); // 2.3 is the first version of ZScript. + sc.RestorePos(saved); + } + } + + ParseSingleFile(&sc, nullptr, lumpnum, parser, state); + for (unsigned i = 0; i < Includes.Size(); i++) + { + lumpnum = fileSystem.CheckNumForFullName(Includes[i], true); + if (lumpnum == -1) + { + IncludeLocs[i].Message(MSG_ERROR, "Include script lump %s not found", Includes[i].GetChars()); + } + else + { + auto fileno2 = fileSystem.GetFileContainer(lumpnum); + if (fileno == 0 && fileno2 != 0) + { + I_FatalError("File %s is overriding core lump %s.", + fileSystem.GetResourceFileFullName(fileSystem.GetFileContainer(lumpnum)), Includes[i].GetChars()); + } + + ParseSingleFile(nullptr, nullptr, lumpnum, parser, state); + } + } + Includes.Clear(); + Includes.ShrinkToFit(); + IncludeLocs.Clear(); + IncludeLocs.ShrinkToFit(); + + value.Int = -1; + value.SourceLoc = sc.GetMessageLine(); + ZCCParse(parser, 0, value, &state); + ZCCParseFree(parser, free); + + // If the parser fails, there is no point starting the compiler, because it'd only flood the output with endless errors. + if (FScriptPosition::ErrorCounter > 0) + { + I_Error("%d errors while parsing %s", FScriptPosition::ErrorCounter, fileSystem.GetFileFullPath(baselump).GetChars()); + } + +#ifndef NDEBUG + if (f != nullptr) + { + fclose(f); + } +#endif + + // Make a dump of the AST before running the compiler for diagnostic purposes. + if (Args->CheckParm("-dumpast")) + { + FString ast = ZCC_PrintAST(state.TopNode); + FString filename = fileSystem.GetFileFullPath(baselump); + filename.ReplaceChars(":\\/?|", '.'); + filename << ".ast"; + FileWriter *ff = FileWriter::Open(filename); + if (ff != NULL) + { + ff->Write(ast.GetChars(), ast.Len()); + delete ff; + } + } + + PSymbolTable symtable; + auto newns = fileSystem.GetFileContainer(baselump) == 0 ? Namespaces.GlobalNamespace : Namespaces.NewNamespace(fileSystem.GetFileContainer(baselump)); + ZCCCompiler cc(state, NULL, symtable, newns, baselump, state.ParseVersion); + cc.Compile(); + + if (FScriptPosition::ErrorCounter > 0) + { + // Abort if the compiler produced any errors. Also do not compile further lumps, because they very likely miss some stuff. + I_Error("%d errors, %d warnings while compiling %s", FScriptPosition::ErrorCounter, FScriptPosition::WarnCounter, fileSystem.GetFileFullPath(baselump).GetChars()); + } + else if (FScriptPosition::WarnCounter > 0) + { + // If we got warnings, but no errors, print the information but continue. + Printf(TEXTCOLOR_ORANGE "%d warnings while compiling %s\n", FScriptPosition::WarnCounter, fileSystem.GetFileFullPath(baselump).GetChars()); + } + +} + +void ParseScripts() +{ + if (TokenMap.CountUsed() == 0) + { + InitTokenMap(); + } + int lump, lastlump = 0; + FScriptPosition::ResetErrorCounter(); + + while ((lump = fileSystem.FindLump("ZSCRIPT", &lastlump)) != -1) + { + DoParse(lump); + } +} + +static FString ZCCTokenName(int terminal) +{ + if (terminal == ZCC_EOF) + { + return "end of file"; + } + int sc_token; + if (terminal > 0 && terminal < (int)countof(BackTokenMap)) + { + sc_token = BackTokenMap[terminal]; + if (sc_token == 0) + { // This token was not initialized. Whoops! + sc_token = -terminal; + } + } + else + { // This should never happen. + sc_token = -terminal; + } + return FScanner::TokenName(sc_token); +} + +ZCC_TreeNode *ZCC_AST::InitNode(size_t size, EZCCTreeNodeType type, ZCC_TreeNode *basis) +{ + ZCC_TreeNode *node = (ZCC_TreeNode *)SyntaxArena.Alloc(size); + node->SiblingNext = node; + node->SiblingPrev = node; + node->NodeType = type; + if (basis != NULL) + { + node->SourceName = basis->SourceName; + node->SourceLump = basis->SourceLump; + node->SourceLoc = basis->SourceLoc; + } + return node; +} + +ZCC_TreeNode *ZCCParseState::InitNode(size_t size, EZCCTreeNodeType type) +{ + ZCC_TreeNode *node = ZCC_AST::InitNode(size, type, NULL); + node->SourceName = Strings.Alloc(sc->ScriptName); + node->SourceLump = sc->LumpNum; + return node; +} + +// Appends a sibling to this node's sibling list. +void AppendTreeNodeSibling(ZCC_TreeNode *thisnode, ZCC_TreeNode *sibling) +{ + if (thisnode == nullptr) + { + // Some bad syntax can actually get here, so better abort so that the user can see the error which caused this. + I_FatalError("Internal script compiler error. Execution aborted."); + } + if (sibling == nullptr) + { + return; + } + + ZCC_TreeNode *&SiblingPrev = thisnode->SiblingPrev; + ZCC_TreeNode *&SiblingNext = thisnode->SiblingNext; + + // Check integrity of our sibling list. + assert(SiblingPrev->SiblingNext == thisnode); + assert(SiblingNext->SiblingPrev == thisnode); + + // Check integrity of new sibling list. + assert(sibling->SiblingPrev->SiblingNext == sibling); + assert(sibling->SiblingNext->SiblingPrev == sibling); + + ZCC_TreeNode *siblingend = sibling->SiblingPrev; + SiblingPrev->SiblingNext = sibling; + sibling->SiblingPrev = SiblingPrev; + SiblingPrev = siblingend; + siblingend->SiblingNext = thisnode; +} + +//**-------------------------------------------------------------------------- + +const char *GetMixinTypeString(EZCCMixinType type) { + switch (type) { + case ZCC_Mixin_Class: + return "class"; + + default: + assert(0 && "Unhandled mixin type"); + return "invalid type"; + } +} + +//**-------------------------------------------------------------------------- + +ZCC_TreeNode *TreeNodeDeepCopy_Internal(ZCC_AST *ast, ZCC_TreeNode *orig, bool copySiblings, TMap *copiedNodesList); +void TreeNodeDeepCopy_Base(ZCC_AST *ast, ZCC_TreeNode *orig, ZCC_TreeNode *copy, bool copySiblings, TMap *copiedNodesList) +{ + copy->SourceName = orig->SourceName; + copy->SourceLump = orig->SourceLump; + copy->SourceLoc = orig->SourceLoc; + copy->NodeType = orig->NodeType; + + if (copySiblings) + { + auto node = orig->SiblingNext; + while (node != orig) + { + auto nextNode = TreeNodeDeepCopy_Internal(ast, node, false, copiedNodesList); + auto newLast = nextNode->SiblingPrev; + + auto firstNode = copy; + auto lastNode = firstNode->SiblingPrev; + + lastNode->SiblingNext = nextNode; + firstNode->SiblingPrev = newLast; + + nextNode->SiblingPrev = lastNode; + newLast->SiblingNext = firstNode; + + node = node->SiblingNext; + } + } +} + +#define GetTreeNode(type) static_cast(ast->InitNode(sizeof(ZCC_##type), AST_##type, nullptr)); +#define TreeNodeDeepCopy_Start(type) \ + auto copy = GetTreeNode(type); \ + auto origCasted = static_cast(orig); \ + ret = copy; \ + copiedNodesList->Insert(orig, ret); + +ZCC_TreeNode *TreeNodeDeepCopy(ZCC_AST *ast, ZCC_TreeNode *orig, bool copySiblings) +{ + TMap copiedNodesList; + copiedNodesList.Clear(); + + return TreeNodeDeepCopy_Internal(ast, orig, copySiblings, &copiedNodesList); +} + +ZCC_TreeNode *TreeNodeDeepCopy_Internal(ZCC_AST *ast, ZCC_TreeNode *orig, bool copySiblings, TMap *copiedNodesList) +{ + // [pbeta] This is a legitimate case as otherwise this function would need + // an excessive amount of "if" statements, so just return null. + if (orig == nullptr) + { + return nullptr; + } + + // [pbeta] We need to keep and check a list of already copied nodes, because + // some are supposed to be the same, and it can cause infinite loops. + auto existingCopy = copiedNodesList->CheckKey(orig); + if (existingCopy != nullptr) + { + return *existingCopy; + } + + ZCC_TreeNode *ret = nullptr; + + switch (orig->NodeType) + { + case AST_Identifier: + { + TreeNodeDeepCopy_Start(Identifier); + + // ZCC_Identifier + copy->Id = origCasted->Id; + + break; + } + + case AST_Struct: + { + TreeNodeDeepCopy_Start(Struct); + + // ZCC_NamedNode + copy->NodeName = origCasted->NodeName; + copy->Symbol = origCasted->Symbol; + // ZCC_Struct + copy->Flags = origCasted->Flags; + copy->Body = TreeNodeDeepCopy_Internal(ast, origCasted->Body, true, copiedNodesList); + copy->Type = origCasted->Type; + copy->Version = origCasted->Version; + + break; + } + + case AST_Class: + { + TreeNodeDeepCopy_Start(Class); + + // ZCC_NamedNode + copy->NodeName = origCasted->NodeName; + copy->Symbol = origCasted->Symbol; + // ZCC_Struct + copy->Flags = origCasted->Flags; + copy->Body = TreeNodeDeepCopy_Internal(ast, origCasted->Body, true, copiedNodesList); + copy->Type = origCasted->Type; + copy->Version = origCasted->Version; + // ZCC_Class + copy->ParentName = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->ParentName, true, copiedNodesList)); + copy->Replaces = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Replaces, true, copiedNodesList)); + + break; + } + + case AST_Enum: + { + TreeNodeDeepCopy_Start(Enum); + + // ZCC_NamedNode + copy->NodeName = origCasted->NodeName; + copy->Symbol = origCasted->Symbol; + // ZCC_Enum + copy->EnumType = origCasted->EnumType; + copy->Elements = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Elements, false, copiedNodesList)); + + break; + } + + case AST_EnumTerminator: + { + TreeNodeDeepCopy_Start (EnumTerminator); + break; + } + + case AST_States: + { + TreeNodeDeepCopy_Start(States); + + // ZCC_States + copy->Body = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Body, true, copiedNodesList)); + copy->Flags = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Flags, true, copiedNodesList)); + + break; + } + + case AST_StatePart: + { + TreeNodeDeepCopy_Start (StatePart); + break; + } + + case AST_StateLabel: + { + TreeNodeDeepCopy_Start(StateLabel); + + // ZCC_StateLabel + copy->Label = origCasted->Label; + + break; + } + + case AST_StateStop: + { + TreeNodeDeepCopy_Start (StateStop); + break; + } + + case AST_StateWait: + { + TreeNodeDeepCopy_Start (StateWait); + break; + } + + case AST_StateFail: + { + TreeNodeDeepCopy_Start (StateFail); + break; + } + + case AST_StateLoop: + { + TreeNodeDeepCopy_Start (StateLoop); + break; + } + + case AST_StateGoto: + { + TreeNodeDeepCopy_Start(StateGoto); + + // ZCC_StateGoto + copy->Qualifier = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Qualifier, true, copiedNodesList)); + copy->Label = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Label, true, copiedNodesList)); + copy->Offset = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Offset, true, copiedNodesList)); + + break; + } + + case AST_StateLine: + { + TreeNodeDeepCopy_Start(StateLine); + + // ZCC_StateLine + copy->Sprite = origCasted->Sprite; + + copy->bBright = origCasted->bBright; + copy->bFast = origCasted->bFast; + copy->bSlow = origCasted->bSlow; + copy->bNoDelay = origCasted->bNoDelay; + copy->bCanRaise = origCasted->bCanRaise; + + copy->Frames = origCasted->Frames; + copy->Duration = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Duration, true, copiedNodesList)); + copy->Offset = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Offset, true, copiedNodesList)); + copy->Lights = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Lights, true, copiedNodesList)); + copy->Action = TreeNodeDeepCopy_Internal(ast, origCasted->Action, true, copiedNodesList); + + break; + } + + case AST_VarName: + { + TreeNodeDeepCopy_Start(VarName); + + // ZCC_VarName + copy->Name = origCasted->Name; + copy->ArraySize = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->ArraySize, true, copiedNodesList)); + + break; + } + + case AST_VarInit: + { + TreeNodeDeepCopy_Start(VarInit); + + // ZCC_VarName + copy->Name = origCasted->Name; + copy->ArraySize = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->ArraySize, true, copiedNodesList)); + // ZCC_VarInit + copy->Init = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Init, true, copiedNodesList)); + copy->InitIsArray = origCasted->InitIsArray; + + break; + } + + case AST_Type: + { + TreeNodeDeepCopy_Start(Type); + + // ZCC_Type + copy->ArraySize = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->ArraySize, true, copiedNodesList)); + + break; + } + + case AST_BasicType: + { + TreeNodeDeepCopy_Start(BasicType); + + // ZCC_Type + copy->ArraySize = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->ArraySize, true, copiedNodesList)); + // ZCC_BasicType + copy->Type = origCasted->Type; + copy->UserType = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->UserType, true, copiedNodesList)); + copy->isconst = origCasted->isconst; + + break; + } + + case AST_MapType: + { + TreeNodeDeepCopy_Start(MapType); + + // ZCC_Type + copy->ArraySize = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->ArraySize, true, copiedNodesList)); + // ZCC_MapType + copy->KeyType = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->KeyType, true, copiedNodesList)); + copy->ValueType = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->ValueType, true, copiedNodesList)); + + break; + } + + case AST_DynArrayType: + { + TreeNodeDeepCopy_Start(DynArrayType); + + // ZCC_Type + copy->ArraySize = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->ArraySize, true, copiedNodesList)); + // ZCC_DynArrayType + copy->ElementType = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->ElementType, true, copiedNodesList)); + + break; + } + + case AST_ClassType: + { + TreeNodeDeepCopy_Start(ClassType); + + // ZCC_Type + copy->ArraySize = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->ArraySize, true, copiedNodesList)); + // ZCC_ClassType + copy->Restriction = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Restriction, true, copiedNodesList)); + + break; + } + + case AST_Expression: + { + TreeNodeDeepCopy_Start(Expression); + + // ZCC_Expression + copy->Operation = origCasted->Operation; + copy->Type = origCasted->Type; + + break; + } + + case AST_ExprID: + { + TreeNodeDeepCopy_Start(ExprID); + + // ZCC_Expression + copy->Operation = origCasted->Operation; + copy->Type = origCasted->Type; + // ZCC_ExprID + copy->Identifier = origCasted->Identifier; + + break; + } + + case AST_ExprTypeRef: + { + TreeNodeDeepCopy_Start(ExprTypeRef); + + // ZCC_Expression + copy->Operation = origCasted->Operation; + copy->Type = origCasted->Type; + // ZCC_ExprTypeRef + copy->RefType = origCasted->RefType; + + break; + } + + case AST_ExprConstant: + { + TreeNodeDeepCopy_Start(ExprConstant); + + // ZCC_Expression + copy->Operation = origCasted->Operation; + copy->Type = origCasted->Type; + // ZCC_ExprConstant + // Currently handled: StringVal, IntVal and DoubleVal. (UIntVal appears to be completely unused.) + if (origCasted->Type == TypeString) + { + copy->StringVal = origCasted->StringVal; + } + else if (origCasted->Type == TypeFloat64 || origCasted->Type == TypeFloat32) + { + copy->DoubleVal = origCasted->DoubleVal; + } + else if (origCasted->Type == TypeName || origCasted->Type->isIntCompatible()) + { + copy->IntVal = origCasted->IntVal; + } + + break; + } + + case AST_ExprFuncCall: + { + TreeNodeDeepCopy_Start(ExprFuncCall); + + // ZCC_Expression + copy->Operation = origCasted->Operation; + copy->Type = origCasted->Type; + // ZCC_ExprFuncCall + copy->Function = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Function, true, copiedNodesList)); + copy->Parameters = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Parameters, true, copiedNodesList)); + + break; + } + + case AST_ExprMemberAccess: + { + TreeNodeDeepCopy_Start(ExprMemberAccess); + + // ZCC_Expression + copy->Operation = origCasted->Operation; + copy->Type = origCasted->Type; + // ZCC_ExprMemberAccess + copy->Left = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Left, true, copiedNodesList)); + copy->Right = origCasted->Right; + + break; + } + + case AST_ExprUnary: + { + TreeNodeDeepCopy_Start(ExprUnary); + + // ZCC_Expression + copy->Operation = origCasted->Operation; + copy->Type = origCasted->Type; + // ZCC_ExprUnary + copy->Operand = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Operand, true, copiedNodesList)); + + break; + } + + case AST_ExprBinary: + { + TreeNodeDeepCopy_Start(ExprBinary); + + // ZCC_Expression + copy->Operation = origCasted->Operation; + copy->Type = origCasted->Type; + // ZCC_ExprBinary + copy->Left = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Left, true, copiedNodesList)); + copy->Right = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Right, true, copiedNodesList)); + + break; + } + + case AST_ExprTrinary: + { + TreeNodeDeepCopy_Start(ExprTrinary); + + // ZCC_Expression + copy->Operation = origCasted->Operation; + copy->Type = origCasted->Type; + // ZCC_ExprTrinary + copy->Test = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Test, true, copiedNodesList)); + copy->Left = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Left, true, copiedNodesList)); + copy->Right = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Right, true, copiedNodesList)); + + break; + } + + case AST_FuncParm: + { + TreeNodeDeepCopy_Start(FuncParm); + + // ZCC_FuncParm + copy->Value = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Value, true, copiedNodesList)); + copy->Label = origCasted->Label; + + break; + } + + case AST_Statement: + { + TreeNodeDeepCopy_Start (Statement); + break; + } + + case AST_CompoundStmt: + { + TreeNodeDeepCopy_Start(CompoundStmt); + + // ZCC_CompoundStmt + copy->Content = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Content, true, copiedNodesList)); + + break; + } + + case AST_ContinueStmt: + { + TreeNodeDeepCopy_Start (ContinueStmt); + break; + } + + case AST_BreakStmt: + { + TreeNodeDeepCopy_Start (BreakStmt); + break; + } + + case AST_ReturnStmt: + { + TreeNodeDeepCopy_Start(ReturnStmt); + + // ZCC_ReturnStmt + copy->Values = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Values, true, copiedNodesList)); + + break; + } + + case AST_ExpressionStmt: + { + TreeNodeDeepCopy_Start(ExpressionStmt); + + // ZCC_ExpressionStmt + copy->Expression = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Expression, true, copiedNodesList)); + + break; + } + + case AST_IterationStmt: + { + TreeNodeDeepCopy_Start(IterationStmt); + + // ZCC_IterationStmt + copy->LoopCondition = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->LoopCondition, true, copiedNodesList)); + copy->LoopStatement = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->LoopStatement, true, copiedNodesList)); + copy->LoopBumper = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->LoopBumper, true, copiedNodesList)); + copy->CheckAt = origCasted->CheckAt; + + break; + } + + case AST_IfStmt: + { + TreeNodeDeepCopy_Start(IfStmt); + + // ZCC_IfStmt + copy->Condition = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Condition, true, copiedNodesList)); + copy->TruePath = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->TruePath, true, copiedNodesList)); + copy->FalsePath = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->FalsePath, true, copiedNodesList)); + + break; + } + + case AST_SwitchStmt: + { + TreeNodeDeepCopy_Start(SwitchStmt); + + // ZCC_SwitchStmt + copy->Condition = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Condition, true, copiedNodesList)); + copy->Content = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Content, true, copiedNodesList)); + + break; + } + + case AST_CaseStmt: + { + TreeNodeDeepCopy_Start(CaseStmt); + + // ZCC_CaseStmt + copy->Condition = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Condition, true, copiedNodesList)); + + break; + } + + case AST_AssignStmt: + { + TreeNodeDeepCopy_Start(AssignStmt); + + // ZCC_AssignStmt + copy->Dests = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Dests, true, copiedNodesList)); + copy->Sources = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Sources, true, copiedNodesList)); + copy->AssignOp = origCasted->AssignOp; + + break; + } + + case AST_LocalVarStmt: + { + TreeNodeDeepCopy_Start(LocalVarStmt); + + // ZCC_LocalVarStmt + copy->Type = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Type, true, copiedNodesList)); + copy->Vars = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Vars, true, copiedNodesList)); + + break; + } + + case AST_FuncParamDecl: + { + TreeNodeDeepCopy_Start(FuncParamDecl); + + // ZCC_FuncParamDecl + copy->Type = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Type, true, copiedNodesList)); + copy->Default = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Default, true, copiedNodesList)); + copy->Name = origCasted->Name; + copy->Flags = origCasted->Flags; + + break; + } + + case AST_ConstantDef: + { + TreeNodeDeepCopy_Start(ConstantDef); + + // ZCC_NamedNode + copy->NodeName = origCasted->NodeName; + copy->Symbol = origCasted->Symbol; + // ZCC_ConstantDef + copy->Value = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Value, true, copiedNodesList)); + copy->Symbol = origCasted->Symbol; + if (copy->Symbol != nullptr) + { + copy->Type = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Type, true, copiedNodesList)); + } + + break; + } + + case AST_Declarator: + { + TreeNodeDeepCopy_Start(Declarator); + + // ZCC_Declarator + copy->Type = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Type, true, copiedNodesList)); + copy->Flags = origCasted->Flags; + copy->Version = origCasted->Version; + + break; + } + + case AST_VarDeclarator: + { + TreeNodeDeepCopy_Start(VarDeclarator); + + // ZCC_Declarator + copy->Type = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Type, true, copiedNodesList)); + copy->Flags = origCasted->Flags; + copy->Version = origCasted->Version; + // ZCC_VarDeclarator + copy->Names = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Names, true, copiedNodesList)); + copy->DeprecationMessage = origCasted->DeprecationMessage; + + break; + } + + case AST_FuncDeclarator: + { + TreeNodeDeepCopy_Start(FuncDeclarator); + + // ZCC_Declarator + copy->Type = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Type, true, copiedNodesList)); + copy->Flags = origCasted->Flags; + copy->Version = origCasted->Version; + // ZCC_FuncDeclarator + copy->Params = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Params, true, copiedNodesList)); + copy->Name = origCasted->Name; + copy->Body = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Body, true, copiedNodesList)); + copy->UseFlags = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->UseFlags, true, copiedNodesList)); + copy->DeprecationMessage = origCasted->DeprecationMessage; + + break; + } + + case AST_Default: + { + TreeNodeDeepCopy_Start(Default); + + // ZCC_CompoundStmt + copy->Content = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Content, true, copiedNodesList)); + + break; + } + + case AST_FlagStmt: + { + TreeNodeDeepCopy_Start(FlagStmt); + + // ZCC_FlagStmt + copy->name = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->name, true, copiedNodesList)); + copy->set = origCasted->set; + + break; + } + + case AST_PropertyStmt: + { + TreeNodeDeepCopy_Start(PropertyStmt); + + // ZCC_PropertyStmt + copy->Prop = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Prop, true, copiedNodesList)); + copy->Values = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Values, true, copiedNodesList)); + + break; + } + + case AST_VectorValue: + { + TreeNodeDeepCopy_Start(VectorValue); + + // ZCC_Expression + copy->Operation = origCasted->Operation; + copy->Type = origCasted->Type; + // ZCC_VectorValue + copy->X = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->X, true, copiedNodesList)); + copy->Y = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Y, true, copiedNodesList)); + copy->Z = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Z, true, copiedNodesList)); + + break; + } + + case AST_DeclFlags: + { + TreeNodeDeepCopy_Start(DeclFlags); + + // ZCC_DeclFlags + copy->Id = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Id, true, copiedNodesList)); + copy->DeprecationMessage = origCasted->DeprecationMessage; + copy->Version = origCasted->Version; + copy->Flags = origCasted->Flags; + + break; + } + + case AST_ClassCast: + { + TreeNodeDeepCopy_Start(ClassCast); + + // ZCC_Expression + copy->Operation = origCasted->Operation; + copy->Type = origCasted->Type; + // ZCC_ClassCast + copy->ClassName = origCasted->ClassName; + copy->Parameters = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Parameters, true, copiedNodesList)); + + break; + } + + case AST_StaticArrayStatement: + { + TreeNodeDeepCopy_Start(StaticArrayStatement); + + // ZCC_StaticArrayStatement + copy->Type = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Type, true, copiedNodesList)); + copy->Id = origCasted->Id; + copy->Values = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Values, true, copiedNodesList)); + + break; + } + + case AST_Property: + { + TreeNodeDeepCopy_Start(Property); + + // ZCC_NamedNode + copy->NodeName = origCasted->NodeName; + copy->Symbol = origCasted->Symbol; + // ZCC_Property + copy->Body = TreeNodeDeepCopy_Internal(ast, origCasted->Body, true, copiedNodesList); + + break; + } + + case AST_FlagDef: + { + TreeNodeDeepCopy_Start(FlagDef); + + // ZCC_NamedNode + copy->NodeName = origCasted->NodeName; + copy->Symbol = origCasted->Symbol; + // ZCC_FlagDef + copy->RefName = origCasted->RefName; + copy->BitValue = origCasted->BitValue; + + break; + } + + case AST_MixinDef: + { + TreeNodeDeepCopy_Start(MixinDef); + + // ZCC_NamedNode + copy->NodeName = origCasted->NodeName; + copy->Symbol = origCasted->Symbol; + // ZCC_MixinDef + copy->Body = TreeNodeDeepCopy_Internal(ast, origCasted->Body, true, copiedNodesList); + copy->MixinType = origCasted->MixinType; + + break; + } + + case AST_MixinStmt: + { + TreeNodeDeepCopy_Start(MixinStmt); + + // ZCC_MixinStmt + copy->MixinName = origCasted->MixinName; + + break; + } + + default: + assert(0 && "Unimplemented node type"); + break; + } + + TreeNodeDeepCopy_Base(ast, orig, ret, copySiblings, copiedNodesList); + + return ret; +} \ No newline at end of file diff --git a/source/common/scripting/frontend/zcc_parser.h b/source/common/scripting/frontend/zcc_parser.h new file mode 100644 index 000000000..3b0e9eb19 --- /dev/null +++ b/source/common/scripting/frontend/zcc_parser.h @@ -0,0 +1,621 @@ +#ifndef ZCC_PARSER_H +#define ZCC_PARSER_H + +#include "memarena.h" +#include "sc_man.h" +#include "types.h" + +struct ZCCToken +{ + template + struct TLargest; + + template + struct TLargest + { + using Type = T; + }; + + template + struct TLargest + { + using Type = typename TLargest< + typename std::conditional< + (sizeof(T) > sizeof(U)), T, U + >::type, Ts... + >::Type; + }; + + union + { + int Int; + double Float; + FString *String; + TLargest::Type Largest; + }; + int SourceLoc; + + ENamedName Name() { return ENamedName(Int); } +}; + +// Variable / Function / Class modifiers +enum +{ + ZCC_Native = 1 << 0, + ZCC_Static = 1 << 1, + ZCC_Private = 1 << 2, + ZCC_Protected = 1 << 3, + ZCC_Latent = 1 << 4, + ZCC_Final = 1 << 5, + ZCC_Meta = 1 << 6, + ZCC_Action = 1 << 7, + ZCC_Deprecated = 1 << 8, + ZCC_ReadOnly = 1 << 9, + ZCC_FuncConst = 1 << 10, + ZCC_Abstract = 1 << 11, + ZCC_Extension = 1 << 12, + ZCC_Virtual = 1 << 13, + ZCC_Override = 1 << 14, + ZCC_Transient = 1 << 15, + ZCC_VarArg = 1 << 16, + ZCC_UIFlag = 1 << 17, // there's also token called ZCC_UI + ZCC_Play = 1 << 18, + ZCC_ClearScope = 1 << 19, + ZCC_VirtualScope = 1 << 20, + ZCC_Version = 1 << 21, + ZCC_Internal = 1 << 22, +}; + +// Function parameter modifiers +enum +{ + ZCC_In = 1 << 0, + ZCC_Out = 1 << 1, + ZCC_Optional = 1 << 2, +}; + + +// Syntax tree structures. +// [pbeta] Any changes to AST node structure or new node types require TreeNodeDeepCopy in zcc_parser.cpp to be updated! +enum EZCCTreeNodeType +{ + AST_Identifier, + AST_Class, + AST_Struct, + AST_Enum, + AST_EnumTerminator, + AST_States, + AST_StatePart, + AST_StateLabel, + AST_StateStop, + AST_StateWait, + AST_StateFail, + AST_StateLoop, + AST_StateGoto, + AST_StateLine, + AST_VarName, + AST_VarInit, + AST_Type, + AST_BasicType, + AST_MapType, + AST_DynArrayType, + AST_ClassType, + AST_Expression, + AST_ExprID, + AST_ExprTypeRef, + AST_ExprConstant, + AST_ExprFuncCall, + AST_ExprMemberAccess, + AST_ExprUnary, + AST_ExprBinary, + AST_ExprTrinary, + AST_FuncParm, + AST_Statement, + AST_CompoundStmt, + AST_ContinueStmt, + AST_BreakStmt, + AST_ReturnStmt, + AST_ExpressionStmt, + AST_IterationStmt, + AST_IfStmt, + AST_SwitchStmt, + AST_CaseStmt, + AST_AssignStmt, + AST_LocalVarStmt, + AST_FuncParamDecl, + AST_ConstantDef, + AST_Declarator, + AST_VarDeclarator, + AST_FuncDeclarator, + AST_Default, + AST_FlagStmt, + AST_PropertyStmt, + AST_VectorValue, + AST_DeclFlags, + AST_ClassCast, + AST_StaticArrayStatement, + AST_Property, + AST_FlagDef, + AST_MixinDef, + AST_MixinStmt, + + NUM_AST_NODE_TYPES +}; + +enum EZCCBuiltinType +{ + ZCC_SInt8, + ZCC_UInt8, + ZCC_SInt16, + ZCC_UInt16, // smaller than 32 bit types are only valid in structs, classes and arrays. + ZCC_SInt32, + ZCC_UInt32, + ZCC_IntAuto, // for enums, autoselect appropriately sized int + + ZCC_Bool, + ZCC_Float64, + ZCC_FloatAuto, // 32-bit in structs/classes, 64-bit everywhere else + ZCC_String, + ZCC_Vector2, + ZCC_Vector3, + ZCC_Name, + + ZCC_Color, // special types for ZDoom. + ZCC_State, + ZCC_Sound, + + ZCC_UserType, + ZCC_NativeType, + ZCC_Let, + + ZCC_NUM_BUILT_IN_TYPES +}; + +enum EZCCMixinType +{ + ZCC_Mixin_Class, + + ZCC_NUM_MIXIN_TYPES +}; + +enum EZCCExprType +{ +#define xx(a,z) PEX_##a, +#include "zcc_exprlist.h" + + PEX_COUNT_OF +}; + +struct ZCC_TreeNode +{ + // This tree node's siblings are stored in a circular linked list. + // When you get back to this node, you know you've been through + // the whole list. + ZCC_TreeNode *SiblingNext; + ZCC_TreeNode *SiblingPrev; + + // can't use FScriptPosition, because the string wouldn't have a chance to + // destruct if we did that. + FString *SourceName; + int SourceLump; + int SourceLoc; + + // Node type is one of the node types above, which corresponds with + // one of the structures below. + EZCCTreeNodeType NodeType; + + operator FScriptPosition() + { + return FScriptPosition(*SourceName, SourceLoc); + } + +}; + +void AppendTreeNodeSibling(ZCC_TreeNode *thisnode, ZCC_TreeNode *sibling); + +struct ZCC_Identifier : ZCC_TreeNode +{ + ENamedName Id; +}; + +struct ZCC_NamedNode : ZCC_TreeNode +{ + ENamedName NodeName; + PSymbolType *Symbol; +}; + +struct ZCC_Struct : ZCC_NamedNode +{ + uint32_t Flags; + ZCC_TreeNode *Body; + PContainerType *Type; + VersionInfo Version; +}; + +struct ZCC_Property : ZCC_NamedNode +{ + ZCC_TreeNode *Body; +}; + +struct ZCC_FlagDef : ZCC_NamedNode +{ + ENamedName RefName; + int BitValue; +}; + +struct ZCC_Class : ZCC_Struct +{ + ZCC_Identifier *ParentName; + ZCC_Identifier *Replaces; + + PClass *CType() { return static_cast(Type)->Descriptor; } +}; + +struct ZCC_MixinDef : ZCC_NamedNode +{ + ZCC_TreeNode *Body; + + EZCCMixinType MixinType; +}; + +struct ZCC_Enum : ZCC_NamedNode +{ + EZCCBuiltinType EnumType; + struct ZCC_ConstantDef *Elements; +}; + +struct ZCC_EnumTerminator : ZCC_TreeNode +{ +}; + +struct ZCC_States : ZCC_TreeNode +{ + struct ZCC_StatePart *Body; + ZCC_Identifier *Flags; +}; + +struct ZCC_StatePart : ZCC_TreeNode +{ +}; + +struct ZCC_StateLabel : ZCC_StatePart +{ + ENamedName Label; +}; + +struct ZCC_StateStop : ZCC_StatePart +{ +}; + +struct ZCC_StateWait : ZCC_StatePart +{ +}; + +struct ZCC_StateFail : ZCC_StatePart +{ +}; + +struct ZCC_StateLoop : ZCC_StatePart +{ +}; + +struct ZCC_Expression : ZCC_TreeNode +{ + EZCCExprType Operation; + PType *Type; + + // Repurposes this node as an error node + void ToErrorNode() + { + Type = TypeError; + Operation = PEX_Nil; + NodeType = AST_Expression; + } +}; + +struct ZCC_StateGoto : ZCC_StatePart +{ + ZCC_Identifier *Qualifier; + ZCC_Identifier *Label; + ZCC_Expression *Offset; +}; + +struct ZCC_StateLine : ZCC_StatePart +{ + FString *Sprite; + BITFIELD bBright : 1; + BITFIELD bFast : 1; + BITFIELD bSlow : 1; + BITFIELD bNoDelay : 1; + BITFIELD bCanRaise : 1; + FString *Frames; + ZCC_Expression *Duration; + ZCC_Expression *Offset; + ZCC_ExprConstant *Lights; + ZCC_TreeNode *Action; +}; + +struct ZCC_VarName : ZCC_TreeNode +{ + ENamedName Name; + ZCC_Expression *ArraySize; // NULL if not an array +}; + +struct ZCC_VarInit : ZCC_VarName +{ + ZCC_Expression *Init; + bool InitIsArray; // this is needed to distinguish one-element arrays from raw elements. +}; + +struct ZCC_Type : ZCC_TreeNode +{ + ZCC_Expression *ArraySize; // NULL if not an array +}; + +struct ZCC_BasicType : ZCC_Type +{ + EZCCBuiltinType Type; + ZCC_Identifier *UserType; + bool isconst; +}; + +struct ZCC_MapType : ZCC_Type +{ + ZCC_Type *KeyType; + ZCC_Type *ValueType; +}; + +struct ZCC_DynArrayType : ZCC_Type +{ + ZCC_Type *ElementType; +}; + +struct ZCC_ClassType : ZCC_Type +{ + ZCC_Identifier *Restriction; +}; + +struct ZCC_ExprID : ZCC_Expression +{ + ENamedName Identifier; +}; + +struct ZCC_ExprTypeRef : ZCC_Expression +{ + PType *RefType; +}; + +struct ZCC_ExprConstant : ZCC_Expression +{ + // [pbeta] The ZCC_ExprConstant case in TreeNodeDeepCopy in zcc_parser.cpp + // must be updated if this union is changed! + union + { + FString *StringVal; + int IntVal; + unsigned int UIntVal; + double DoubleVal; + }; +}; + +struct ZCC_FuncParm : ZCC_TreeNode +{ + ZCC_Expression *Value; + ENamedName Label; +}; + +struct ZCC_ExprFuncCall : ZCC_Expression +{ + ZCC_Expression *Function; + ZCC_FuncParm *Parameters; +}; + +struct ZCC_ClassCast : ZCC_Expression +{ + ENamedName ClassName; + ZCC_FuncParm *Parameters; +}; + +struct ZCC_ExprMemberAccess : ZCC_Expression +{ + ZCC_Expression *Left; + ENamedName Right; +}; + +struct ZCC_ExprUnary : ZCC_Expression +{ + ZCC_Expression *Operand; +}; + +struct ZCC_ExprBinary : ZCC_Expression +{ + ZCC_Expression *Left; + ZCC_Expression *Right; +}; + +struct ZCC_ExprTrinary : ZCC_Expression +{ + ZCC_Expression *Test; + ZCC_Expression *Left; + ZCC_Expression *Right; +}; + +struct ZCC_VectorValue : ZCC_Expression +{ + ZCC_Expression *X, *Y, *Z; +}; + +struct ZCC_Statement : ZCC_TreeNode +{ +}; + +struct ZCC_StaticArrayStatement : ZCC_Statement +{ + ZCC_Type *Type; + ENamedName Id; + ZCC_Expression *Values; +}; + +struct ZCC_CompoundStmt : ZCC_Statement +{ + ZCC_Statement *Content; +}; + +struct ZCC_ContinueStmt : ZCC_Statement +{ +}; + +struct ZCC_BreakStmt : ZCC_Statement +{ +}; + +struct ZCC_ReturnStmt : ZCC_Statement +{ + ZCC_Expression *Values; +}; + +struct ZCC_ExpressionStmt : ZCC_Statement +{ + ZCC_Expression *Expression; +}; + +struct ZCC_IterationStmt : ZCC_Statement +{ + ZCC_Expression *LoopCondition; + ZCC_Statement *LoopStatement; + ZCC_Statement *LoopBumper; + + // Should the loop condition be checked at the + // start of the loop (before the LoopStatement) + // or at the end (after the LoopStatement)? + enum { Start, End } CheckAt; +}; + +struct ZCC_IfStmt : ZCC_Statement +{ + ZCC_Expression *Condition; + ZCC_Statement *TruePath; + ZCC_Statement *FalsePath; +}; + +struct ZCC_SwitchStmt : ZCC_Statement +{ + ZCC_Expression *Condition; + ZCC_Statement *Content; +}; + +struct ZCC_CaseStmt : ZCC_Statement +{ + // A NULL Condition represents the default branch + ZCC_Expression *Condition; +}; + +struct ZCC_AssignStmt : ZCC_Statement +{ + ZCC_Expression *Dests; + ZCC_Expression *Sources; + int AssignOp; +}; + +struct ZCC_LocalVarStmt : ZCC_Statement +{ + ZCC_Type *Type; + ZCC_VarInit *Vars; +}; + +struct ZCC_FuncParamDecl : ZCC_TreeNode +{ + ZCC_Type *Type; + ZCC_Expression *Default; + ENamedName Name; + int Flags; +}; + +struct ZCC_DeclFlags : ZCC_TreeNode +{ + ZCC_Identifier *Id; + FString *DeprecationMessage; + VersionInfo Version; + int Flags; +}; + +struct ZCC_ConstantDef : ZCC_NamedNode +{ + ZCC_Expression *Value; + PSymbolConst *Symbol; + ZCC_Enum *Type; // gets set when the constant originates from an enum. +}; + +struct ZCC_Declarator : ZCC_TreeNode +{ + ZCC_Type *Type; + int Flags; + VersionInfo Version; +}; + +// A variable in a class or struct. +struct ZCC_VarDeclarator : ZCC_Declarator +{ + ZCC_VarName *Names; + FString *DeprecationMessage; +}; + +// A function in a class. +struct ZCC_FuncDeclarator : ZCC_Declarator +{ + ZCC_FuncParamDecl *Params; + ENamedName Name; + ZCC_Statement *Body; + ZCC_Identifier *UseFlags; + FString *DeprecationMessage; +}; + +struct ZCC_Default : ZCC_CompoundStmt +{ +}; + +struct ZCC_PropertyStmt : ZCC_Statement +{ + ZCC_Identifier *Prop; + ZCC_Expression *Values; +}; + + +struct ZCC_FlagStmt : ZCC_Statement +{ + ZCC_Identifier *name; + bool set; +}; + +struct ZCC_MixinStmt : ZCC_Statement +{ + ENamedName MixinName; +}; + +FString ZCC_PrintAST(ZCC_TreeNode *root); + + +struct ZCC_AST +{ + ZCC_AST() : TopNode(NULL) {} + ZCC_TreeNode *InitNode(size_t size, EZCCTreeNodeType type, ZCC_TreeNode *basis); + + FSharedStringArena Strings; + FMemArena SyntaxArena; + struct ZCC_TreeNode *TopNode; + VersionInfo ParseVersion; +}; + +struct ZCCParseState : public ZCC_AST +{ + ZCCParseState(FScanner *scanner = nullptr) : sc(scanner) {} + ZCC_TreeNode *InitNode(size_t size, EZCCTreeNodeType type); + + FScanner *sc; +}; + +const char *GetMixinTypeString(EZCCMixinType type); + +ZCC_TreeNode *TreeNodeDeepCopy(ZCC_AST *ast, ZCC_TreeNode *orig, bool copySiblings); + +#endif diff --git a/tools/lemon/CMakeLists.txt b/tools/lemon/CMakeLists.txt new file mode 100644 index 000000000..e092cf6e9 --- /dev/null +++ b/tools/lemon/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required( VERSION 2.8.7 ) + +if( NOT CMAKE_CROSSCOMPILING ) + set( CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -D_DEBUG" ) + + add_executable( lemon lemon.c ) + set( CROSS_EXPORTS ${CROSS_EXPORTS} lemon PARENT_SCOPE ) +endif() + +# Lemon wants lempar.c in its directory +add_custom_command( TARGET lemon + POST_BUILD + COMMAND echo $ + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/lempar.c $ ) diff --git a/tools/lemon/lemon.c b/tools/lemon/lemon.c new file mode 100644 index 000000000..bdc004a17 --- /dev/null +++ b/tools/lemon/lemon.c @@ -0,0 +1,5449 @@ +/* +** This file contains all sources (including headers) to the LEMON +** LALR(1) parser generator. The sources have been combined into a +** single file to make it easy to include LEMON in the source tree +** and Makefile of another program. +** +** The author of this program disclaims copyright. +** +** This file is based on version 1.69 of lemon.c from the SQLite +** CVS, with modifications to make it work nicer when run +** from Developer Studio. +*/ +#include +#include +#include +#include +#include +#include + +#define ISSPACE(X) isspace((unsigned char)(X)) +#define ISDIGIT(X) isdigit((unsigned char)(X)) +#define ISALNUM(X) isalnum((unsigned char)(X)) +#define ISALPHA(X) isalpha((unsigned char)(X)) +#define ISUPPER(X) isupper((unsigned char)(X)) +#define ISLOWER(X) islower((unsigned char)(X)) + + +#ifndef __WIN32__ +# if defined(_WIN32) || defined(WIN32) +# define __WIN32__ +# endif +#endif + +#ifdef __WIN32__ +#ifdef __cplusplus +extern "C" { +#endif +extern int access(char *path, int mode); +#ifdef __cplusplus +} +#endif +#else +#include +#endif + +/* #define PRIVATE static */ +#define PRIVATE + +#ifdef TEST +#define MAXRHS 5 /* Set low to exercise exception code */ +#else +#define MAXRHS 1000 +#endif + +static int showPrecedenceConflict = 0; +static void *msort(void *list, void *next, int (*cmp)()); + +/* +** Compilers are getting increasingly pedantic about type conversions +** as C evolves ever closer to Ada.... To work around the latest problems +** we have to define the following variant of strlen(). +*/ +#define lemonStrlen(X) ((int)strlen(X)) + +/* a few forward declarations... */ +struct rule; +struct lemon; +struct action; + +/******** From the file "action.h" *************************************/ +static struct action *Action_new(void); +static struct action *Action_sort(struct action *); + +/********** From the file "build.h" ************************************/ +void FindRulePrecedences(); +void FindFirstSets(); +void FindStates(); +void FindLinks(); +void FindFollowSets(); +void FindActions(); + +/********* From the file "configlist.h" *********************************/ +void Configlist_init(void); +struct config *Configlist_add(struct rule *, int); +struct config *Configlist_addbasis(struct rule *, int); +void Configlist_closure(struct lemon *); +void Configlist_sort(void); +void Configlist_sortbasis(void); +struct config *Configlist_return(void); +struct config *Configlist_basis(void); +void Configlist_eat(struct config *); +void Configlist_reset(void); + +/********* From the file "error.h" ***************************************/ +void ErrorMsg(const char *, int,const char *, ...); + +/****** From the file "option.h" ******************************************/ +enum option_type { OPT_FLAG=1, OPT_INT, OPT_DBL, OPT_STR, + OPT_FFLAG, OPT_FINT, OPT_FDBL, OPT_FSTR}; +struct s_options { + enum option_type type; + const char *label; + char *arg; + const char *message; +}; +int OptInit(char**,struct s_options*,FILE*); +int OptNArgs(void); +char *OptArg(int); +void OptErr(int); +void OptPrint(void); + +/******** From the file "parse.h" *****************************************/ +void Parse(struct lemon *lemp); + +/********* From the file "plink.h" ***************************************/ +struct plink *Plink_new(void); +void Plink_add(struct plink **, struct config *); +void Plink_copy(struct plink **, struct plink *); +void Plink_delete(struct plink *); + +/********** From the file "report.h" *************************************/ +void Reprint(struct lemon *); +void ReportOutput(struct lemon *); +void ReportTable(struct lemon *, int); +void ReportHeader(struct lemon *); +void CompressTables(struct lemon *); +void ResortStates(struct lemon *); + +/********** From the file "set.h" ****************************************/ +void SetSize(int); /* All sets will be of size N */ +char *SetNew(void); /* A new set for element 0..N */ +void SetFree(char*); /* Deallocate a set */ +int SetAdd(char*,int); /* Add element to a set */ +int SetUnion(char *,char *); /* A <- A U B, thru element N */ +#define SetFind(X,Y) (X[Y]) /* True if Y is in set X */ + +/********** From the file "struct.h" *************************************/ +/* +** Principal data structures for the LEMON parser generator. +*/ + +typedef enum {LEMON_FALSE=0, LEMON_TRUE} Boolean; + +/* Symbols (terminals and nonterminals) of the grammar are stored +** in the following: */ +enum symbol_type { + TERMINAL, + NONTERMINAL, + MULTITERMINAL +}; +enum e_assoc { + LEFT, + RIGHT, + NONE, + UNK +}; +struct symbol { + const char *name; /* Name of the symbol */ + int index; /* Index number for this symbol */ + enum symbol_type type; /* Symbols are all either TERMINALS or NTs */ + struct rule *rule; /* Linked list of rules of this (if an NT) */ + struct symbol *fallback; /* fallback token in case this token doesn't parse */ + int prec; /* Precedence if defined (-1 otherwise) */ + enum e_assoc assoc; /* Associativity if precedence is defined */ + char *firstset; /* First-set for all rules of this symbol */ + Boolean lambda; /* True if NT and can generate an empty string */ + int useCnt; /* Number of times used */ + char *destructor; /* Code which executes whenever this symbol is + ** popped from the stack during error processing */ + int destLineno; /* Line number for start of destructor. Set to + ** -1 for duplicate destructors. */ + char *datatype; /* The data type of information held by this + ** object. Only used if type==NONTERMINAL */ + int dtnum; /* The data type number. In the parser, the value + ** stack is a union. The .yy%d element of this + ** union is the correct data type for this object */ + /* The following fields are used by MULTITERMINALs only */ + int nsubsym; /* Number of constituent symbols in the MULTI */ + struct symbol **subsym; /* Array of constituent symbols */ +}; + +/* Each production rule in the grammar is stored in the following +** structure. */ +struct rule { + struct symbol *lhs; /* Left-hand side of the rule */ + const char *lhsalias; /* Alias for the LHS (NULL if none) */ + int lhsStart; /* True if left-hand side is the start symbol */ + int ruleline; /* Line number for the rule */ + int nrhs; /* Number of RHS symbols */ + struct symbol **rhs; /* The RHS symbols */ + const char **rhsalias; /* An alias for each RHS symbol (NULL if none) */ + int line; /* Line number at which code begins */ + const char *code; /* The code executed when this rule is reduced */ + const char *codePrefix; /* Setup code before code[] above */ + const char *codeSuffix; /* Breakdown code after code[] above */ + int noCode; /* True if this rule has no associated C code */ + int codeEmitted; /* True if the code has been emitted already */ + struct symbol *precsym; /* Precedence symbol for this rule */ + int index; /* An index number for this rule */ + int iRule; /* Rule number as used in the generated tables */ + Boolean canReduce; /* True if this rule is ever reduced */ +#if 0 + Boolean doesReduce; /* Reduce actions occur after optimization */ +#endif + struct rule *nextlhs; /* Next rule with the same LHS */ + struct rule *next; /* Next rule in the global list */ +}; + +/* A configuration is a production rule of the grammar together with +** a mark (dot) showing how much of that rule has been processed so far. +** Configurations also contain a follow-set which is a list of terminal +** symbols which are allowed to immediately follow the end of the rule. +** Every configuration is recorded as an instance of the following: */ +enum cfgstatus { + COMPLETE, + INCOMPLETE +}; +struct config { + struct rule *rp; /* The rule upon which the configuration is based */ + int dot; /* The parse point */ + char *fws; /* Follow-set for this configuration only */ + struct plink *fplp; /* Follow-set forward propagation links */ + struct plink *bplp; /* Follow-set backwards propagation links */ + struct state *stp; /* Pointer to state which contains this */ + enum cfgstatus status; /* used during followset and shift computations */ + struct config *next; /* Next configuration in the state */ + struct config *bp; /* The next basis configuration */ +}; + +enum e_action { + SHIFT, + ACCEPT, + REDUCE, + ERROR, + SSCONFLICT, /* A shift/shift conflict */ + SRCONFLICT, /* Was a reduce, but part of a conflict */ + RRCONFLICT, /* Was a reduce, but part of a conflict */ + SH_RESOLVED, /* Was a shift. Precedence resolved conflict */ + RD_RESOLVED, /* Was reduce. Precedence resolved conflict */ + NOT_USED, /* Deleted by compression */ + SHIFTREDUCE /* Shift first, then reduce */ +}; + +/* Every shift or reduce operation is stored as one of the following */ +struct action { + struct symbol *sp; /* The look-ahead symbol */ + enum e_action type; + union { + struct state *stp; /* The new state, if a shift */ + struct rule *rp; /* The rule, if a reduce */ + } x; + struct symbol *spOpt; /* SHIFTREDUCE optimization to this symbol */ + struct action *next; /* Next action for this state */ + struct action *collide; /* Next action with the same hash */ +}; + +/* Each state of the generated parser's finite state machine +** is encoded as an instance of the following structure. */ +struct state { + struct config *bp; /* The basis configurations for this state */ + struct config *cfp; /* All configurations in this set */ + int statenum; /* Sequential number for this state */ + struct action *ap; /* List of actions for this state */ + int nTknAct, nNtAct; /* Number of actions on terminals and nonterminals */ + int iTknOfst, iNtOfst; /* yy_action[] offset for terminals and nonterms */ + int iDfltReduce; /* Default action is to REDUCE by this rule */ + struct rule *pDfltReduce;/* The default REDUCE rule. */ + int autoReduce; /* True if this is an auto-reduce state */ +}; +#define NO_OFFSET (-2147483647) + +/* A followset propagation link indicates that the contents of one +** configuration followset should be propagated to another whenever +** the first changes. */ +struct plink { + struct config *cfp; /* The configuration to which linked */ + struct plink *next; /* The next propagate link */ +}; + +/* The state vector for the entire parser generator is recorded as +** follows. (LEMON uses no global variables and makes little use of +** static variables. Fields in the following structure can be thought +** of as being global variables in the program.) */ +struct lemon { + struct state **sorted; /* Table of states sorted by state number */ + struct rule *rule; /* List of all rules */ + struct rule *startRule; /* First rule */ + int nstate; /* Number of states */ + int nxstate; /* nstate with tail degenerate states removed */ + int nrule; /* Number of rules */ + int nsymbol; /* Number of terminal and nonterminal symbols */ + int nterminal; /* Number of terminal symbols */ + struct symbol **symbols; /* Sorted array of pointers to symbols */ + int errorcnt; /* Number of errors */ + struct symbol *wildcard; /* Token that matches anything */ + struct symbol *errsym; /* The error symbol */ + char *name; /* Name of the generated parser */ + char *arg; /* Declaration of the 3th argument to parser */ + char *tokentype; /* Type of terminal symbols in the parser stack */ + char *vartype; /* The default type of non-terminal symbols */ + char *start; /* Name of the start symbol for the grammar */ + char *stacksize; /* Size of the parser stack */ + char *include; /* Code to put at the start of the C file */ + char *error; /* Code to execute when an error is seen */ + char *overflow; /* Code to execute on a stack overflow */ + char *failure; /* Code to execute on parser failure */ + char *accept; /* Code to execute when the parser excepts */ + char *extracode; /* Code appended to the generated file */ + char *tokendest; /* Code to execute to destroy token data */ + char *vardest; /* Code for the default non-terminal destructor */ + char *filename; /* Name of the input file */ + char *outbasefilename; /* Name of the input file, with the output dir's path */ + char *outname; /* Name of the current output file */ + char *tokenprefix; /* A prefix added to token names in the .h file */ + int nconflict; /* Number of parsing conflicts */ + int nactiontab; /* Number of entries in the yy_action[] table */ + int tablesize; /* Total table size of all tables in bytes */ + int basisflag; /* Print only basis configurations */ + int has_fallback; /* True if any %fallback is seen in the grammar */ + int nolinenosflag; /* True if #line statements should not be printed */ + char *argv0; /* Name of the program */ +}; + +#define MemoryCheck(X) if((X)==0){ \ + extern void memory_error(); \ + memory_error(); \ +} + +/**************** From the file "table.h" *********************************/ +/* +** All code in this file has been automatically generated +** from a specification in the file +** "table.q" +** by the associative array code building program "aagen". +** Do not edit this file! Instead, edit the specification +** file, then rerun aagen. +*/ +/* +** Code for processing tables in the LEMON parser generator. +*/ +/* Routines for handling a strings */ + +const char *Strsafe(const char *); + +void Strsafe_init(void); +int Strsafe_insert(const char *); +const char *Strsafe_find(const char *); + +/* Routines for handling symbols of the grammar */ + +struct symbol *Symbol_new(const char *); +int Symbolcmpp(const void *, const void *); +void Symbol_init(void); +int Symbol_insert(struct symbol *, const char *); +struct symbol *Symbol_find(const char *); +struct symbol *Symbol_Nth(int); +int Symbol_count(void); +struct symbol **Symbol_arrayof(void); + +/* Routines to manage the state table */ + +int Configcmp(const char *, const char *); +struct state *State_new(void); +void State_init(void); +int State_insert(struct state *, struct config *); +struct state *State_find(struct config *); +struct state **State_arrayof(/* */); + +/* Routines used for efficiency in Configlist_add */ + +void Configtable_init(void); +int Configtable_insert(struct config *); +struct config *Configtable_find(struct config *); +void Configtable_clear(int(*)(struct config *)); + +/****************** From the file "action.c" *******************************/ +/* +** Routines processing parser actions in the LEMON parser generator. +*/ + +/* Allocate a new parser action */ +static struct action *Action_new(void){ + static struct action *freelist = 0; + struct action *newaction; + + if( freelist==0 ){ + int i; + int amt = 100; + freelist = (struct action *)calloc(amt, sizeof(struct action)); + if( freelist==0 ){ + fprintf(stderr,"Unable to allocate memory for a new parser action."); + exit(1); + } + for(i=0; inext; + return newaction; +} + +/* Compare two actions for sorting purposes. Return negative, zero, or +** positive if the first action is less than, equal to, or greater than +** the first +*/ +static int actioncmp(ap1,ap2) +struct action *ap1; +struct action *ap2; +{ + int rc; + rc = ap1->sp->index - ap2->sp->index; + if( rc==0 ){ + rc = (int)ap1->type - (int)ap2->type; + } + if( rc==0 && (ap1->type==REDUCE || ap1->type==SHIFTREDUCE) ){ + rc = ap1->x.rp->index - ap2->x.rp->index; + } + if( rc==0 ){ + rc = ap2 - ap1; + } + return rc; +} + +/* Sort parser actions */ +static struct action *Action_sort(struct action *ap) +{ + ap = (struct action *)msort(ap,&ap->next,actioncmp); + return ap; +} + +void Action_add( + struct action **app, + enum e_action type, + struct symbol *sp, + char *arg +){ + struct action *newaction; + newaction = Action_new(); + newaction->next = *app; + *app = newaction; + newaction->type = type; + newaction->sp = sp; + newaction->spOpt = 0; + if( type==SHIFT ){ + newaction->x.stp = (struct state *)arg; + }else{ + newaction->x.rp = (struct rule *)arg; + } +} +/********************** New code to implement the "acttab" module ***********/ +/* +** This module implements routines use to construct the yy_action[] table. +*/ + +/* +** The state of the yy_action table under construction is an instance of +** the following structure. +** +** The yy_action table maps the pair (state_number, lookahead) into an +** action_number. The table is an array of integers pairs. The state_number +** determines an initial offset into the yy_action array. The lookahead +** value is then added to this initial offset to get an index X into the +** yy_action array. If the aAction[X].lookahead equals the value of the +** of the lookahead input, then the value of the action_number output is +** aAction[X].action. If the lookaheads do not match then the +** default action for the state_number is returned. +** +** All actions associated with a single state_number are first entered +** into aLookahead[] using multiple calls to acttab_action(). Then the +** actions for that single state_number are placed into the aAction[] +** array with a single call to acttab_insert(). The acttab_insert() call +** also resets the aLookahead[] array in preparation for the next +** state number. +*/ +struct lookahead_action { + int lookahead; /* Value of the lookahead token */ + int action; /* Action to take on the given lookahead */ +}; +typedef struct acttab acttab; +struct acttab { + int nAction; /* Number of used slots in aAction[] */ + int nActionAlloc; /* Slots allocated for aAction[] */ + struct lookahead_action + *aAction, /* The yy_action[] table under construction */ + *aLookahead; /* A single new transaction set */ + int mnLookahead; /* Minimum aLookahead[].lookahead */ + int mnAction; /* Action associated with mnLookahead */ + int mxLookahead; /* Maximum aLookahead[].lookahead */ + int nLookahead; /* Used slots in aLookahead[] */ + int nLookaheadAlloc; /* Slots allocated in aLookahead[] */ +}; + +/* Return the number of entries in the yy_action table */ +#define acttab_size(X) ((X)->nAction) + +/* The value for the N-th entry in yy_action */ +#define acttab_yyaction(X,N) ((X)->aAction[N].action) + +/* The value for the N-th entry in yy_lookahead */ +#define acttab_yylookahead(X,N) ((X)->aAction[N].lookahead) + +/* Free all memory associated with the given acttab */ +void acttab_free(acttab **pp){ + acttab *p = *pp; + free( p->aAction ); + free( p->aLookahead ); + free( p ); +} + +/* Allocate a new acttab structure */ +acttab *acttab_alloc(void){ + acttab *p = (acttab *) calloc( 1, sizeof(*p) ); + if( p==0 ){ + fprintf(stderr,"Unable to allocate memory for a new acttab."); + exit(1); + } + memset(p, 0, sizeof(*p)); + return p; +} + +/* Add a new action to the current transaction set. +** +** This routine is called once for each lookahead for a particular +** state. +*/ +void acttab_action(acttab *p, int lookahead, int action){ + if( p->nLookahead>=p->nLookaheadAlloc ){ + p->nLookaheadAlloc += 25; + p->aLookahead = (struct lookahead_action *) realloc( p->aLookahead, + sizeof(p->aLookahead[0])*p->nLookaheadAlloc ); + if( p->aLookahead==0 ){ + fprintf(stderr,"malloc failed\n"); + exit(1); + } + } + if( p->nLookahead==0 ){ + p->mxLookahead = lookahead; + p->mnLookahead = lookahead; + p->mnAction = action; + }else{ + if( p->mxLookaheadmxLookahead = lookahead; + if( p->mnLookahead>lookahead ){ + p->mnLookahead = lookahead; + p->mnAction = action; + } + } + p->aLookahead[p->nLookahead].lookahead = lookahead; + p->aLookahead[p->nLookahead].action = action; + p->nLookahead++; +} + +/* +** Add the transaction set built up with prior calls to acttab_action() +** into the current action table. Then reset the transaction set back +** to an empty set in preparation for a new round of acttab_action() calls. +** +** Return the offset into the action table of the new transaction. +*/ +int acttab_insert(acttab *p){ + int i, j, k, n; + assert( p->nLookahead>0 ); + + /* Make sure we have enough space to hold the expanded action table + ** in the worst case. The worst case occurs if the transaction set + ** must be appended to the current action table + */ + n = p->mxLookahead + 1; + if( p->nAction + n >= p->nActionAlloc ){ + int oldAlloc = p->nActionAlloc; + p->nActionAlloc = p->nAction + n + p->nActionAlloc + 20; + p->aAction = (struct lookahead_action *) realloc( p->aAction, + sizeof(p->aAction[0])*p->nActionAlloc); + if( p->aAction==0 ){ + fprintf(stderr,"malloc failed\n"); + exit(1); + } + for(i=oldAlloc; inActionAlloc; i++){ + p->aAction[i].lookahead = -1; + p->aAction[i].action = -1; + } + } + + /* Scan the existing action table looking for an offset that is a + ** duplicate of the current transaction set. Fall out of the loop + ** if and when the duplicate is found. + ** + ** i is the index in p->aAction[] where p->mnLookahead is inserted. + */ + for(i=p->nAction-1; i>=0; i--){ + if( p->aAction[i].lookahead==p->mnLookahead ){ + /* All lookaheads and actions in the aLookahead[] transaction + ** must match against the candidate aAction[i] entry. */ + if( p->aAction[i].action!=p->mnAction ) continue; + for(j=0; jnLookahead; j++){ + k = p->aLookahead[j].lookahead - p->mnLookahead + i; + if( k<0 || k>=p->nAction ) break; + if( p->aLookahead[j].lookahead!=p->aAction[k].lookahead ) break; + if( p->aLookahead[j].action!=p->aAction[k].action ) break; + } + if( jnLookahead ) continue; + + /* No possible lookahead value that is not in the aLookahead[] + ** transaction is allowed to match aAction[i] */ + n = 0; + for(j=0; jnAction; j++){ + if( p->aAction[j].lookahead<0 ) continue; + if( p->aAction[j].lookahead==j+p->mnLookahead-i ) n++; + } + if( n==p->nLookahead ){ + break; /* An exact match is found at offset i */ + } + } + } + + /* If no existing offsets exactly match the current transaction, find an + ** an empty offset in the aAction[] table in which we can add the + ** aLookahead[] transaction. + */ + if( i<0 ){ + /* Look for holes in the aAction[] table that fit the current + ** aLookahead[] transaction. Leave i set to the offset of the hole. + ** If no holes are found, i is left at p->nAction, which means the + ** transaction will be appended. */ + for(i=0; inActionAlloc - p->mxLookahead; i++){ + if( p->aAction[i].lookahead<0 ){ + for(j=0; jnLookahead; j++){ + k = p->aLookahead[j].lookahead - p->mnLookahead + i; + if( k<0 ) break; + if( p->aAction[k].lookahead>=0 ) break; + } + if( jnLookahead ) continue; + for(j=0; jnAction; j++){ + if( p->aAction[j].lookahead==j+p->mnLookahead-i ) break; + } + if( j==p->nAction ){ + break; /* Fits in empty slots */ + } + } + } + } + /* Insert transaction set at index i. */ + for(j=0; jnLookahead; j++){ + k = p->aLookahead[j].lookahead - p->mnLookahead + i; + p->aAction[k] = p->aLookahead[j]; + if( k>=p->nAction ) p->nAction = k+1; + } + p->nLookahead = 0; + + /* Return the offset that is added to the lookahead in order to get the + ** index into yy_action of the action */ + return i - p->mnLookahead; +} + +/********************** From the file "build.c" *****************************/ +/* +** Routines to construction the finite state machine for the LEMON +** parser generator. +*/ + +/* Find a precedence symbol of every rule in the grammar. +** +** Those rules which have a precedence symbol coded in the input +** grammar using the "[symbol]" construct will already have the +** rp->precsym field filled. Other rules take as their precedence +** symbol the first RHS symbol with a defined precedence. If there +** are not RHS symbols with a defined precedence, the precedence +** symbol field is left blank. +*/ +void FindRulePrecedences(struct lemon *xp) +{ + struct rule *rp; + for(rp=xp->rule; rp; rp=rp->next){ + if( rp->precsym==0 ){ + int i, j; + for(i=0; inrhs && rp->precsym==0; i++){ + struct symbol *sp = rp->rhs[i]; + if( sp->type==MULTITERMINAL ){ + for(j=0; jnsubsym; j++){ + if( sp->subsym[j]->prec>=0 ){ + rp->precsym = sp->subsym[j]; + break; + } + } + }else if( sp->prec>=0 ){ + rp->precsym = rp->rhs[i]; + } + } + } + } + return; +} + +/* Find all nonterminals which will generate the empty string. +** Then go back and compute the first sets of every nonterminal. +** The first set is the set of all terminal symbols which can begin +** a string generated by that nonterminal. +*/ +void FindFirstSets(struct lemon *lemp) +{ + int i, j; + struct rule *rp; + int progress; + + for(i=0; insymbol; i++){ + lemp->symbols[i]->lambda = LEMON_FALSE; + } + for(i=lemp->nterminal; insymbol; i++){ + lemp->symbols[i]->firstset = SetNew(); + } + + /* First compute all lambdas */ + do{ + progress = 0; + for(rp=lemp->rule; rp; rp=rp->next){ + if( rp->lhs->lambda ) continue; + for(i=0; inrhs; i++){ + struct symbol *sp = rp->rhs[i]; + assert( sp->type==NONTERMINAL || sp->lambda==LEMON_FALSE ); + if( sp->lambda==LEMON_FALSE ) break; + } + if( i==rp->nrhs ){ + rp->lhs->lambda = LEMON_TRUE; + progress = 1; + } + } + }while( progress ); + + /* Now compute all first sets */ + do{ + struct symbol *s1, *s2; + progress = 0; + for(rp=lemp->rule; rp; rp=rp->next){ + s1 = rp->lhs; + for(i=0; inrhs; i++){ + s2 = rp->rhs[i]; + if( s2->type==TERMINAL ){ + progress += SetAdd(s1->firstset,s2->index); + break; + }else if( s2->type==MULTITERMINAL ){ + for(j=0; jnsubsym; j++){ + progress += SetAdd(s1->firstset,s2->subsym[j]->index); + } + break; + }else if( s1==s2 ){ + if( s1->lambda==LEMON_FALSE ) break; + }else{ + progress += SetUnion(s1->firstset,s2->firstset); + if( s2->lambda==LEMON_FALSE ) break; + } + } + } + }while( progress ); + return; +} + +/* Compute all LR(0) states for the grammar. Links +** are added to between some states so that the LR(1) follow sets +** can be computed later. +*/ +PRIVATE struct state *getstate(struct lemon *); /* forward reference */ +void FindStates(struct lemon *lemp) +{ + struct symbol *sp; + struct rule *rp; + + Configlist_init(); + + /* Find the start symbol */ + if( lemp->start ){ + sp = Symbol_find(lemp->start); + if( sp==0 ){ + ErrorMsg(lemp->filename,0, +"The specified start symbol \"%s\" is not \ +in a nonterminal of the grammar. \"%s\" will be used as the start \ +symbol instead.",lemp->start,lemp->startRule->lhs->name); + lemp->errorcnt++; + sp = lemp->startRule->lhs; + } + }else{ + sp = lemp->startRule->lhs; + } + + /* Make sure the start symbol doesn't occur on the right-hand side of + ** any rule. Report an error if it does. (YACC would generate a new + ** start symbol in this case.) */ + for(rp=lemp->rule; rp; rp=rp->next){ + int i; + for(i=0; inrhs; i++){ + if( rp->rhs[i]==sp ){ /* FIX ME: Deal with multiterminals */ + ErrorMsg(lemp->filename,0, +"The start symbol \"%s\" occurs on the \ +right-hand side of a rule. This will result in a parser which \ +does not work properly.",sp->name); + lemp->errorcnt++; + } + } + } + + /* The basis configuration set for the first state + ** is all rules which have the start symbol as their + ** left-hand side */ + for(rp=sp->rule; rp; rp=rp->nextlhs){ + struct config *newcfp; + rp->lhsStart = 1; + newcfp = Configlist_addbasis(rp,0); + SetAdd(newcfp->fws,0); + } + + /* Compute the first state. All other states will be + ** computed automatically during the computation of the first one. + ** The returned pointer to the first state is not used. */ + (void)getstate(lemp); + return; +} + +/* Return a pointer to a state which is described by the configuration +** list which has been built from calls to Configlist_add. +*/ +PRIVATE void buildshifts(struct lemon *, struct state *); /* Forwd ref */ +PRIVATE struct state *getstate(struct lemon *lemp) +{ + struct config *cfp, *bp; + struct state *stp; + + /* Extract the sorted basis of the new state. The basis was constructed + ** by prior calls to "Configlist_addbasis()". */ + Configlist_sortbasis(); + bp = Configlist_basis(); + + /* Get a state with the same basis */ + stp = State_find(bp); + if( stp ){ + /* A state with the same basis already exists! Copy all the follow-set + ** propagation links from the state under construction into the + ** preexisting state, then return a pointer to the preexisting state */ + struct config *x, *y; + for(x=bp, y=stp->bp; x && y; x=x->bp, y=y->bp){ + Plink_copy(&y->bplp,x->bplp); + Plink_delete(x->fplp); + x->fplp = x->bplp = 0; + } + cfp = Configlist_return(); + Configlist_eat(cfp); + }else{ + /* This really is a new state. Construct all the details */ + Configlist_closure(lemp); /* Compute the configuration closure */ + Configlist_sort(); /* Sort the configuration closure */ + cfp = Configlist_return(); /* Get a pointer to the config list */ + stp = State_new(); /* A new state structure */ + MemoryCheck(stp); + stp->bp = bp; /* Remember the configuration basis */ + stp->cfp = cfp; /* Remember the configuration closure */ + stp->statenum = lemp->nstate++; /* Every state gets a sequence number */ + stp->ap = 0; /* No actions, yet. */ + State_insert(stp,stp->bp); /* Add to the state table */ + buildshifts(lemp,stp); /* Recursively compute successor states */ + } + return stp; +} + +/* +** Return true if two symbols are the same. +*/ +int same_symbol(struct symbol *a, struct symbol *b) +{ + int i; + if( a==b ) return 1; + if( a->type!=MULTITERMINAL ) return 0; + if( b->type!=MULTITERMINAL ) return 0; + if( a->nsubsym!=b->nsubsym ) return 0; + for(i=0; insubsym; i++){ + if( a->subsym[i]!=b->subsym[i] ) return 0; + } + return 1; +} + +/* Construct all successor states to the given state. A "successor" +** state is any state which can be reached by a shift action. +*/ +PRIVATE void buildshifts(struct lemon *lemp, struct state *stp) +{ + struct config *cfp; /* For looping thru the config closure of "stp" */ + struct config *bcfp; /* For the inner loop on config closure of "stp" */ + struct config *newcfg; /* */ + struct symbol *sp; /* Symbol following the dot in configuration "cfp" */ + struct symbol *bsp; /* Symbol following the dot in configuration "bcfp" */ + struct state *newstp; /* A pointer to a successor state */ + + /* Each configuration becomes complete after it contibutes to a successor + ** state. Initially, all configurations are incomplete */ + for(cfp=stp->cfp; cfp; cfp=cfp->next) cfp->status = INCOMPLETE; + + /* Loop through all configurations of the state "stp" */ + for(cfp=stp->cfp; cfp; cfp=cfp->next){ + if( cfp->status==COMPLETE ) continue; /* Already used by inner loop */ + if( cfp->dot>=cfp->rp->nrhs ) continue; /* Can't shift this config */ + Configlist_reset(); /* Reset the new config set */ + sp = cfp->rp->rhs[cfp->dot]; /* Symbol after the dot */ + + /* For every configuration in the state "stp" which has the symbol "sp" + ** following its dot, add the same configuration to the basis set under + ** construction but with the dot shifted one symbol to the right. */ + for(bcfp=cfp; bcfp; bcfp=bcfp->next){ + if( bcfp->status==COMPLETE ) continue; /* Already used */ + if( bcfp->dot>=bcfp->rp->nrhs ) continue; /* Can't shift this one */ + bsp = bcfp->rp->rhs[bcfp->dot]; /* Get symbol after dot */ + if( !same_symbol(bsp,sp) ) continue; /* Must be same as for "cfp" */ + bcfp->status = COMPLETE; /* Mark this config as used */ + newcfg = Configlist_addbasis(bcfp->rp,bcfp->dot+1); + Plink_add(&newcfg->bplp,bcfp); + } + + /* Get a pointer to the state described by the basis configuration set + ** constructed in the preceding loop */ + newstp = getstate(lemp); + + /* The state "newstp" is reached from the state "stp" by a shift action + ** on the symbol "sp" */ + if( sp->type==MULTITERMINAL ){ + int i; + for(i=0; insubsym; i++){ + Action_add(&stp->ap,SHIFT,sp->subsym[i],(char*)newstp); + } + }else{ + Action_add(&stp->ap,SHIFT,sp,(char *)newstp); + } + } +} + +/* +** Construct the propagation links +*/ +void FindLinks(struct lemon *lemp) +{ + int i; + struct config *cfp, *other; + struct state *stp; + struct plink *plp; + + /* Housekeeping detail: + ** Add to every propagate link a pointer back to the state to + ** which the link is attached. */ + for(i=0; instate; i++){ + stp = lemp->sorted[i]; + for(cfp=stp->cfp; cfp; cfp=cfp->next){ + cfp->stp = stp; + } + } + + /* Convert all backlinks into forward links. Only the forward + ** links are used in the follow-set computation. */ + for(i=0; instate; i++){ + stp = lemp->sorted[i]; + for(cfp=stp->cfp; cfp; cfp=cfp->next){ + for(plp=cfp->bplp; plp; plp=plp->next){ + other = plp->cfp; + Plink_add(&other->fplp,cfp); + } + } + } +} + +/* Compute all followsets. +** +** A followset is the set of all symbols which can come immediately +** after a configuration. +*/ +void FindFollowSets(struct lemon *lemp) +{ + int i; + struct config *cfp; + struct plink *plp; + int progress; + int change; + + for(i=0; instate; i++){ + for(cfp=lemp->sorted[i]->cfp; cfp; cfp=cfp->next){ + cfp->status = INCOMPLETE; + } + } + + do{ + progress = 0; + for(i=0; instate; i++){ + for(cfp=lemp->sorted[i]->cfp; cfp; cfp=cfp->next){ + if( cfp->status==COMPLETE ) continue; + for(plp=cfp->fplp; plp; plp=plp->next){ + change = SetUnion(plp->cfp->fws,cfp->fws); + if( change ){ + plp->cfp->status = INCOMPLETE; + progress = 1; + } + } + cfp->status = COMPLETE; + } + } + }while( progress ); +} + +static int resolve_conflict(struct action *,struct action *); + +/* Compute the reduce actions, and resolve conflicts. +*/ +void FindActions(struct lemon *lemp) +{ + int i,j; + struct config *cfp; + struct state *stp; + struct symbol *sp; + struct rule *rp; + + /* Add all of the reduce actions + ** A reduce action is added for each element of the followset of + ** a configuration which has its dot at the extreme right. + */ + for(i=0; instate; i++){ /* Loop over all states */ + stp = lemp->sorted[i]; + for(cfp=stp->cfp; cfp; cfp=cfp->next){ /* Loop over all configurations */ + if( cfp->rp->nrhs==cfp->dot ){ /* Is dot at extreme right? */ + for(j=0; jnterminal; j++){ + if( SetFind(cfp->fws,j) ){ + /* Add a reduce action to the state "stp" which will reduce by the + ** rule "cfp->rp" if the lookahead symbol is "lemp->symbols[j]" */ + Action_add(&stp->ap,REDUCE,lemp->symbols[j],(char *)cfp->rp); + } + } + } + } + } + + /* Add the accepting token */ + if( lemp->start ){ + sp = Symbol_find(lemp->start); + if( sp==0 ) sp = lemp->startRule->lhs; + }else{ + sp = lemp->startRule->lhs; + } + /* Add to the first state (which is always the starting state of the + ** finite state machine) an action to ACCEPT if the lookahead is the + ** start nonterminal. */ + Action_add(&lemp->sorted[0]->ap,ACCEPT,sp,0); + + /* Resolve conflicts */ + for(i=0; instate; i++){ + struct action *ap, *nap; + struct state *stp; + stp = lemp->sorted[i]; + /* assert( stp->ap ); */ + stp->ap = Action_sort(stp->ap); + for(ap=stp->ap; ap && ap->next; ap=ap->next){ + for(nap=ap->next; nap && nap->sp==ap->sp; nap=nap->next){ + /* The two actions "ap" and "nap" have the same lookahead. + ** Figure out which one should be used */ + lemp->nconflict += resolve_conflict(ap,nap); + } + } + } + + /* Report an error for each rule that can never be reduced. */ + for(rp=lemp->rule; rp; rp=rp->next) rp->canReduce = LEMON_FALSE; + for(i=0; instate; i++){ + struct action *ap; + for(ap=lemp->sorted[i]->ap; ap; ap=ap->next){ + if( ap->type==REDUCE ) ap->x.rp->canReduce = LEMON_TRUE; + } + } + for(rp=lemp->rule; rp; rp=rp->next){ + if( rp->canReduce ) continue; + ErrorMsg(lemp->filename,rp->ruleline,"This rule can not be reduced.\n"); + lemp->errorcnt++; + } +} + +/* Resolve a conflict between the two given actions. If the +** conflict can't be resolved, return non-zero. +** +** NO LONGER TRUE: +** To resolve a conflict, first look to see if either action +** is on an error rule. In that case, take the action which +** is not associated with the error rule. If neither or both +** actions are associated with an error rule, then try to +** use precedence to resolve the conflict. +** +** If either action is a SHIFT, then it must be apx. This +** function won't work if apx->type==REDUCE and apy->type==SHIFT. +*/ +static int resolve_conflict( + struct action *apx, + struct action *apy +){ + struct symbol *spx, *spy; + int errcnt = 0; + assert( apx->sp==apy->sp ); /* Otherwise there would be no conflict */ + if( apx->type==SHIFT && apy->type==SHIFT ){ + apy->type = SSCONFLICT; + errcnt++; + } + if( apx->type==SHIFT && apy->type==REDUCE ){ + spx = apx->sp; + spy = apy->x.rp->precsym; + if( spy==0 || spx->prec<0 || spy->prec<0 ){ + /* Not enough precedence information. */ + apy->type = SRCONFLICT; + errcnt++; + }else if( spx->prec>spy->prec ){ /* higher precedence wins */ + apy->type = RD_RESOLVED; + }else if( spx->precprec ){ + apx->type = SH_RESOLVED; + }else if( spx->prec==spy->prec && spx->assoc==RIGHT ){ /* Use operator */ + apy->type = RD_RESOLVED; /* associativity */ + }else if( spx->prec==spy->prec && spx->assoc==LEFT ){ /* to break tie */ + apx->type = SH_RESOLVED; + }else{ + assert( spx->prec==spy->prec && spx->assoc==NONE ); + apy->type = ERROR; + } + }else if( apx->type==REDUCE && apy->type==REDUCE ){ + spx = apx->x.rp->precsym; + spy = apy->x.rp->precsym; + if( spx==0 || spy==0 || spx->prec<0 || + spy->prec<0 || spx->prec==spy->prec ){ + apy->type = RRCONFLICT; + errcnt++; + }else if( spx->prec>spy->prec ){ + apy->type = RD_RESOLVED; + }else if( spx->precprec ){ + apx->type = RD_RESOLVED; + } + }else{ + assert( + apx->type==SH_RESOLVED || + apx->type==RD_RESOLVED || + apx->type==SSCONFLICT || + apx->type==SRCONFLICT || + apx->type==RRCONFLICT || + apy->type==SH_RESOLVED || + apy->type==RD_RESOLVED || + apy->type==SSCONFLICT || + apy->type==SRCONFLICT || + apy->type==RRCONFLICT + ); + /* The REDUCE/SHIFT case cannot happen because SHIFTs come before + ** REDUCEs on the list. If we reach this point it must be because + ** the parser conflict had already been resolved. */ + } + return errcnt; +} +/********************* From the file "configlist.c" *************************/ +/* +** Routines to processing a configuration list and building a state +** in the LEMON parser generator. +*/ + +static struct config *freelist = 0; /* List of free configurations */ +static struct config *current = 0; /* Top of list of configurations */ +static struct config **currentend = 0; /* Last on list of configs */ +static struct config *basis = 0; /* Top of list of basis configs */ +static struct config **basisend = 0; /* End of list of basis configs */ + +/* Return a pointer to a new configuration */ +PRIVATE struct config *newconfig(){ + struct config *newcfg; + if( freelist==0 ){ + int i; + int amt = 3; + freelist = (struct config *)calloc( amt, sizeof(struct config) ); + if( freelist==0 ){ + fprintf(stderr,"Unable to allocate memory for a new configuration."); + exit(1); + } + for(i=0; inext; + return newcfg; +} + +/* The configuration "old" is no longer used */ +PRIVATE void deleteconfig(struct config *old) +{ + old->next = freelist; + freelist = old; +} + +/* Initialized the configuration list builder */ +void Configlist_init(){ + current = 0; + currentend = ¤t; + basis = 0; + basisend = &basis; + Configtable_init(); + return; +} + +/* Initialized the configuration list builder */ +void Configlist_reset(){ + current = 0; + currentend = ¤t; + basis = 0; + basisend = &basis; + Configtable_clear(0); + return; +} + +/* Add another configuration to the configuration list */ +struct config *Configlist_add( + struct rule *rp, /* The rule */ + int dot /* Index into the RHS of the rule where the dot goes */ +){ + struct config *cfp, model; + + assert( currentend!=0 ); + model.rp = rp; + model.dot = dot; + cfp = Configtable_find(&model); + if( cfp==0 ){ + cfp = newconfig(); + cfp->rp = rp; + cfp->dot = dot; + cfp->fws = SetNew(); + cfp->stp = 0; + cfp->fplp = cfp->bplp = 0; + cfp->next = 0; + cfp->bp = 0; + *currentend = cfp; + currentend = &cfp->next; + Configtable_insert(cfp); + } + return cfp; +} + +/* Add a basis configuration to the configuration list */ +struct config *Configlist_addbasis(struct rule *rp, int dot) +{ + struct config *cfp, model; + + assert( basisend!=0 ); + assert( currentend!=0 ); + model.rp = rp; + model.dot = dot; + cfp = Configtable_find(&model); + if( cfp==0 ){ + cfp = newconfig(); + cfp->rp = rp; + cfp->dot = dot; + cfp->fws = SetNew(); + cfp->stp = 0; + cfp->fplp = cfp->bplp = 0; + cfp->next = 0; + cfp->bp = 0; + *currentend = cfp; + currentend = &cfp->next; + *basisend = cfp; + basisend = &cfp->bp; + Configtable_insert(cfp); + } + return cfp; +} + +/* Compute the closure of the configuration list */ +void Configlist_closure(struct lemon *lemp) +{ + struct config *cfp, *newcfp; + struct rule *rp, *newrp; + struct symbol *sp, *xsp; + int i, dot; + + assert( currentend!=0 ); + for(cfp=current; cfp; cfp=cfp->next){ + rp = cfp->rp; + dot = cfp->dot; + if( dot>=rp->nrhs ) continue; + sp = rp->rhs[dot]; + if( sp->type==NONTERMINAL ){ + if( sp->rule==0 && sp!=lemp->errsym ){ + ErrorMsg(lemp->filename,rp->line,"Nonterminal \"%s\" has no rules.", + sp->name); + lemp->errorcnt++; + } + for(newrp=sp->rule; newrp; newrp=newrp->nextlhs){ + newcfp = Configlist_add(newrp,0); + for(i=dot+1; inrhs; i++){ + xsp = rp->rhs[i]; + if( xsp->type==TERMINAL ){ + SetAdd(newcfp->fws,xsp->index); + break; + }else if( xsp->type==MULTITERMINAL ){ + int k; + for(k=0; knsubsym; k++){ + SetAdd(newcfp->fws, xsp->subsym[k]->index); + } + break; + }else{ + SetUnion(newcfp->fws,xsp->firstset); + if( xsp->lambda==LEMON_FALSE ) break; + } + } + if( i==rp->nrhs ) Plink_add(&cfp->fplp,newcfp); + } + } + } + return; +} + +/* Sort the configuration list */ +void Configlist_sort(){ + current = (struct config*)msort((char*)current,(char**)&(current->next), + Configcmp); + currentend = 0; + return; +} + +/* Sort the basis configuration list */ +void Configlist_sortbasis(){ + basis = (struct config *)msort((char*)current,(char**)&(current->bp), + Configcmp); + basisend = 0; + return; +} + +/* Return a pointer to the head of the configuration list and +** reset the list */ +struct config *Configlist_return(){ + struct config *old; + old = current; + current = 0; + currentend = 0; + return old; +} + +/* Return a pointer to the head of the configuration list and +** reset the list */ +struct config *Configlist_basis(){ + struct config *old; + old = basis; + basis = 0; + basisend = 0; + return old; +} + +/* Free all elements of the given configuration list */ +void Configlist_eat(struct config *cfp) +{ + struct config *nextcfp; + for(; cfp; cfp=nextcfp){ + nextcfp = cfp->next; + assert( cfp->fplp==0 ); + assert( cfp->bplp==0 ); + if( cfp->fws ) SetFree(cfp->fws); + deleteconfig(cfp); + } + return; +} +/***************** From the file "error.c" *********************************/ +/* +** Code for printing error message. +*/ + +void ErrorMsg(const char *filename, int lineno, const char *format, ...){ + va_list ap; + +#if _MSC_VER + if( lineno>0 ){ + fprintf(stderr,"%s(%d) : error : ",filename,lineno); + }else{ + fprintf(stderr,"%s : error : ",filename); + } +#else + if( lineno>0 ){ + fprintf(stderr,"%s:%d: ",filename,lineno); + }else{ + fprintf(stderr,"%s: ",filename); + } +#endif + va_start(ap, format); + vfprintf(stderr,format,ap); + va_end(ap); + fprintf(stderr, "\n"); +} +/**************** From the file "main.c" ************************************/ +/* +** Main program file for the LEMON parser generator. +*/ + +/* Report an out-of-memory condition and abort. This function +** is used mostly by the "MemoryCheck" macro in struct.h +*/ +void memory_error(){ + fprintf(stderr,"Out of memory. Aborting...\n"); + exit(1); +} + +static int nDefine = 0; /* Number of -D options on the command line */ +static char **azDefine = 0; /* Name of the -D macros */ + +/* This routine is called with the argument to each -D command-line option. +** Add the macro defined to the azDefine array. +*/ +static void handle_D_option(char *z){ + char **paz; + nDefine++; + azDefine = (char **) realloc(azDefine, sizeof(azDefine[0])*nDefine); + if( azDefine==0 ){ + fprintf(stderr,"out of memory\n"); + exit(1); + } + paz = &azDefine[nDefine-1]; + *paz = (char *) malloc( lemonStrlen(z)+1 ); + if( *paz==0 ){ + fprintf(stderr,"out of memory\n"); + exit(1); + } + strcpy(*paz, z); + for(z=*paz; *z && *z!='='; z++){} + *z = 0; +} + +static char *user_templatename = NULL; +static void handle_T_option(char *z){ + user_templatename = (char *) malloc( lemonStrlen(z)+1 ); + if( user_templatename==0 ){ + memory_error(); + } + strcpy(user_templatename, z); +} + +/* Routines for routing output to a different directory than the one +** the source file resides in. +*/ +static char *output_dir = NULL; + +static inline Boolean is_seperator(int c) +{ + if (c == '/') + return LEMON_TRUE; +#if defined(_WIN32) || defined(DOS) + if (c == '\\' || c == ':') + return LEMON_TRUE; +#endif + return LEMON_FALSE; +} + +/* Returns the file part of a pathname. +*/ +const char *file_base(const char *path) +{ + const char *src = path + strlen(path) - 1; + if( src >= path ){ + // back up until a / or the start + while (src != path && !is_seperator(*(src - 1))) + src--; + + // Check for files with drive specification but no path +#if defined(_WIN32) || defined(DOS) + if( src == path && src[0] != 0 ){ + if( src[1] == ':' ) + src += 2; + } +#endif + return src; + } + return NULL; +} + +static char *stitch_outdir(char *path) +{ + if( output_dir ){ + const char *base = file_base(path); + char *newpath = (char *) malloc( lemonStrlen(output_dir) + lemonStrlen(path) + 1 ); + if( newpath==0 ){ + memory_error(); + } + strcpy(newpath, output_dir); + strcat(newpath, base); + return newpath; + } + return path; +} + +static void handle_C_option(char *z){ + int len = lemonStrlen(z); + output_dir = (char *) malloc( len+2 ); + if( output_dir==0 ){ + memory_error(); + } + strcpy(output_dir, z); + if( !is_seperator(output_dir[len-1]) ){ + output_dir[len] = '/'; + output_dir[len+1] = '\0'; + } +} + +/* Merge together to lists of rules ordered by rule.iRule */ +static struct rule *Rule_merge(struct rule *pA, struct rule *pB){ + struct rule *pFirst = 0; + struct rule **ppPrev = &pFirst; + while( pA && pB ){ + if( pA->iRuleiRule ){ + *ppPrev = pA; + ppPrev = &pA->next; + pA = pA->next; + }else{ + *ppPrev = pB; + ppPrev = &pB->next; + pB = pB->next; + } + } + if( pA ){ + *ppPrev = pA; + }else{ + *ppPrev = pB; + } + return pFirst; +} + +/* +** Sort a list of rules in order of increasing iRule value +*/ +static struct rule *Rule_sort(struct rule *rp){ + int i; + struct rule *pNext; + struct rule *x[32]; + memset(x, 0, sizeof(x)); + while( rp ){ + pNext = rp->next; + rp->next = 0; + for(i=0; iuseCnt = 0; + + /* Parse the input file */ + Parse(&lem); + if( lem.errorcnt ) exit(lem.errorcnt); + if( lem.nrule==0 ){ + fprintf(stderr,"Empty grammar.\n"); + exit(1); + } + + /* Count and index the symbols of the grammar */ + Symbol_new("{default}"); + lem.nsymbol = Symbol_count(); + lem.symbols = Symbol_arrayof(); + for(i=0; iindex = i; + qsort(lem.symbols,lem.nsymbol,sizeof(struct symbol*), Symbolcmpp); + for(i=0; iindex = i; + while( lem.symbols[i-1]->type==MULTITERMINAL ){ i--; } + assert( strcmp(lem.symbols[i-1]->name,"{default}")==0 ); + lem.nsymbol = i - 1; + for(i=1; ISUPPER(lem.symbols[i]->name[0]); i++); + lem.nterminal = i; + + /* Assign sequential rule numbers. Start with 0. Put rules that have no + ** reduce action C-code associated with them last, so that the switch() + ** statement that selects reduction actions will have a smaller jump table. + */ + for(i=0, rp=lem.rule; rp; rp=rp->next){ + rp->iRule = rp->code ? i++ : -1; + } + for(rp=lem.rule; rp; rp=rp->next){ + if( rp->iRule<0 ) rp->iRule = i++; + } + lem.startRule = lem.rule; + lem.rule = Rule_sort(lem.rule); + + /* Generate a reprint of the grammar, if requested on the command line */ + if( rpflag ){ + Reprint(&lem); + }else{ + /* Initialize the size for all follow and first sets */ + SetSize(lem.nterminal+1); + + /* Find the precedence for every production rule (that has one) */ + FindRulePrecedences(&lem); + + /* Compute the lambda-nonterminals and the first-sets for every + ** nonterminal */ + FindFirstSets(&lem); + + /* Compute all LR(0) states. Also record follow-set propagation + ** links so that the follow-set can be computed later */ + lem.nstate = 0; + FindStates(&lem); + lem.sorted = State_arrayof(); + + /* Tie up loose ends on the propagation links */ + FindLinks(&lem); + + /* Compute the follow set of every reducible configuration */ + FindFollowSets(&lem); + + /* Compute the action tables */ + FindActions(&lem); + + /* Compress the action tables */ + if( compress==0 ) CompressTables(&lem); + + /* Reorder and renumber the states so that states with fewer choices + ** occur at the end. This is an optimization that helps make the + ** generated parser tables smaller. */ + if( noResort==0 ) ResortStates(&lem); + + /* Generate a report of the parser generated. (the "y.output" file) */ + if( !quiet ) ReportOutput(&lem); + + /* Generate the source code for the parser */ + ReportTable(&lem, mhflag); + + /* Produce a header file for use by the scanner. (This step is + ** omitted if the "-m" option is used because makeheaders will + ** generate the file for us.) */ + if( !mhflag ) ReportHeader(&lem); + } + if( statistics ){ + printf("Parser statistics:\n"); + stats_line("terminal symbols", lem.nterminal); + stats_line("non-terminal symbols", lem.nsymbol - lem.nterminal); + stats_line("total symbols", lem.nsymbol); + stats_line("rules", lem.nrule); + stats_line("states", lem.nxstate); + stats_line("conflicts", lem.nconflict); + stats_line("action table entries", lem.nactiontab); + stats_line("total table size (bytes)", lem.tablesize); + } + if( lem.nconflict ){ + fprintf(stderr,"%d parsing conflicts.\n",lem.nconflict); + } + exit(lem.errorcnt + lem.nconflict); + return (lem.errorcnt + lem.nconflict); +} +/******************** From the file "msort.c" *******************************/ +/* +** A generic merge-sort program. +** +** USAGE: +** Let "ptr" be a pointer to some structure which is at the head of +** a null-terminated list. Then to sort the list call: +** +** ptr = msort(ptr,&(ptr->next),cmpfnc); +** +** In the above, "cmpfnc" is a pointer to a function which compares +** two instances of the structure and returns an integer, as in +** strcmp. The second argument is a pointer to the pointer to the +** second element of the linked list. This address is used to compute +** the offset to the "next" field within the structure. The offset to +** the "next" field must be constant for all structures in the list. +** +** The function returns a new pointer which is the head of the list +** after sorting. +** +** ALGORITHM: +** Merge-sort. +*/ + +/* +** Return a pointer to the next structure in the linked list. +*/ +#define NEXT(A) (*(void**)(((size_t)A)+offset)) + +/* +** Inputs: +** a: A sorted, null-terminated linked list. (May be null). +** b: A sorted, null-terminated linked list. (May be null). +** cmp: A pointer to the comparison function. +** offset: Offset in the structure to the "next" field. +** +** Return Value: +** A pointer to the head of a sorted list containing the elements +** of both a and b. +** +** Side effects: +** The "next" pointers for elements in the lists a and b are +** changed. +*/ +static void *merge(void *a,void *b,int (*cmp)(),size_t offset) +{ + char *ptr, *head; + + if( a==0 ){ + head = b; + }else if( b==0 ){ + head = a; + }else{ + if( (*cmp)(a,b)<=0 ){ + ptr = a; + a = NEXT(a); + }else{ + ptr = b; + b = NEXT(b); + } + head = ptr; + while( a && b ){ + if( (*cmp)(a,b)<=0 ){ + NEXT(ptr) = a; + ptr = a; + a = NEXT(a); + }else{ + NEXT(ptr) = b; + ptr = b; + b = NEXT(b); + } + } + if( a ) NEXT(ptr) = a; + else NEXT(ptr) = b; + } + return head; +} + +/* +** Inputs: +** list: Pointer to a singly-linked list of structures. +** next: Pointer to pointer to the second element of the list. +** cmp: A comparison function. +** +** Return Value: +** A pointer to the head of a sorted list containing the elements +** orginally in list. +** +** Side effects: +** The "next" pointers for elements in list are changed. +*/ +#define LISTSIZE 30 +static void *msort(void *list,void *next,int (*cmp)()) +{ + size_t offset; + char *ep; + char *set[LISTSIZE]; + int i; + offset = (size_t)next - (size_t)list; + for(i=0; istate = WAITING_FOR_DECL_KEYWORD; + }else if( ISLOWER(x[0]) ){ + psp->lhs = Symbol_new(x); + psp->nrhs = 0; + psp->lhsalias = 0; + psp->state = WAITING_FOR_ARROW; + }else if( x[0]=='{' ){ + if( psp->prevrule==0 ){ + ErrorMsg(psp->filename,psp->tokenlineno, +"There is no prior rule upon which to attach the code \ +fragment which begins on this line."); + psp->errorcnt++; + }else if( psp->prevrule->code!=0 ){ + ErrorMsg(psp->filename,psp->tokenlineno, +"Code fragment beginning on this line is not the first \ +to follow the previous rule."); + psp->errorcnt++; + }else{ + psp->prevrule->line = psp->tokenlineno; + psp->prevrule->code = &x[1]; + psp->prevrule->noCode = 0; + } + }else if( x[0]=='[' ){ + psp->state = PRECEDENCE_MARK_1; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Token \"%s\" should be either \"%%\" or a nonterminal name.", + x); + psp->errorcnt++; + } + break; + case PRECEDENCE_MARK_1: + if( !ISUPPER(x[0]) ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "The precedence symbol must be a terminal."); + psp->errorcnt++; + }else if( psp->prevrule==0 ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "There is no prior rule to assign precedence \"[%s]\".",x); + psp->errorcnt++; + }else if( psp->prevrule->precsym!=0 ){ + ErrorMsg(psp->filename,psp->tokenlineno, +"Precedence mark on this line is not the first \ +to follow the previous rule."); + psp->errorcnt++; + }else{ + psp->prevrule->precsym = Symbol_new(x); + } + psp->state = PRECEDENCE_MARK_2; + break; + case PRECEDENCE_MARK_2: + if( x[0]!=']' ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "Missing \"]\" on precedence mark."); + psp->errorcnt++; + } + psp->state = WAITING_FOR_DECL_OR_RULE; + break; + case WAITING_FOR_ARROW: + if( x[0]==':' && x[1]==':' && x[2]=='=' ){ + psp->state = IN_RHS; + }else if( x[0]=='(' ){ + psp->state = LHS_ALIAS_1; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Expected to see a \":\" following the LHS symbol \"%s\".", + psp->lhs->name); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + } + break; + case LHS_ALIAS_1: + if( ISALPHA(x[0]) ){ + psp->lhsalias = x; + psp->state = LHS_ALIAS_2; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "\"%s\" is not a valid alias for the LHS \"%s\"\n", + x,psp->lhs->name); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + } + break; + case LHS_ALIAS_2: + if( x[0]==')' ){ + psp->state = LHS_ALIAS_3; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Missing \")\" following LHS alias name \"%s\".",psp->lhsalias); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + } + break; + case LHS_ALIAS_3: + if( x[0]==':' && x[1]==':' && x[2]=='=' ){ + psp->state = IN_RHS; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Missing \"->\" following: \"%s(%s)\".", + psp->lhs->name,psp->lhsalias); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + } + break; + case IN_RHS: + if( x[0]=='.' ){ + struct rule *rp; + rp = (struct rule *)calloc( sizeof(struct rule) + + sizeof(struct symbol*)*psp->nrhs + sizeof(char*)*psp->nrhs, 1); + if( rp==0 ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "Can't allocate enough memory for this rule."); + psp->errorcnt++; + psp->prevrule = 0; + }else{ + int i; + rp->ruleline = psp->tokenlineno; + rp->rhs = (struct symbol**)&rp[1]; + rp->rhsalias = (const char**)&(rp->rhs[psp->nrhs]); + for(i=0; inrhs; i++){ + rp->rhs[i] = psp->rhs[i]; + rp->rhsalias[i] = psp->alias[i]; + } + rp->lhs = psp->lhs; + rp->lhsalias = psp->lhsalias; + rp->nrhs = psp->nrhs; + rp->code = 0; + rp->noCode = 1; + rp->precsym = 0; + rp->index = psp->gp->nrule++; + rp->nextlhs = rp->lhs->rule; + rp->lhs->rule = rp; + rp->next = 0; + if( psp->firstrule==0 ){ + psp->firstrule = psp->lastrule = rp; + }else{ + psp->lastrule->next = rp; + psp->lastrule = rp; + } + psp->prevrule = rp; + } + psp->state = WAITING_FOR_DECL_OR_RULE; + }else if( ISALPHA(x[0]) ){ + if( psp->nrhs>=MAXRHS ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "Too many symbols on RHS of rule beginning at \"%s\".", + x); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + }else{ + psp->rhs[psp->nrhs] = Symbol_new(x); + psp->alias[psp->nrhs] = 0; + psp->nrhs++; + } + }else if( (x[0]=='|' || x[0]=='/') && psp->nrhs>0 ){ + struct symbol *msp = psp->rhs[psp->nrhs-1]; + if( msp->type!=MULTITERMINAL ){ + struct symbol *origsp = msp; + msp = (struct symbol *) calloc(1,sizeof(*msp)); + msp->type = MULTITERMINAL; + msp->nsubsym = 1; + msp->subsym = (struct symbol **) calloc(1,sizeof(struct symbol*)); + msp->subsym[0] = origsp; + msp->name = origsp->name; + psp->rhs[psp->nrhs-1] = msp; + } + msp->nsubsym++; + msp->subsym = (struct symbol **) realloc(msp->subsym, + sizeof(struct symbol*)*msp->nsubsym); + msp->subsym[msp->nsubsym-1] = Symbol_new(&x[1]); + if( ISLOWER(x[1]) || ISLOWER(msp->subsym[0]->name[0]) ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "Cannot form a compound containing a non-terminal"); + psp->errorcnt++; + } + }else if( x[0]=='(' && psp->nrhs>0 ){ + psp->state = RHS_ALIAS_1; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Illegal character on RHS of rule: \"%s\".",x); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + } + break; + case RHS_ALIAS_1: + if( ISALPHA(x[0]) ){ + psp->alias[psp->nrhs-1] = x; + psp->state = RHS_ALIAS_2; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "\"%s\" is not a valid alias for the RHS symbol \"%s\"\n", + x,psp->rhs[psp->nrhs-1]->name); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + } + break; + case RHS_ALIAS_2: + if( x[0]==')' ){ + psp->state = IN_RHS; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Missing \")\" following LHS alias name \"%s\".",psp->lhsalias); + psp->errorcnt++; + psp->state = RESYNC_AFTER_RULE_ERROR; + } + break; + case WAITING_FOR_DECL_KEYWORD: + if( ISALPHA(x[0]) ){ + psp->declkeyword = x; + psp->declargslot = 0; + psp->decllinenoslot = 0; + psp->insertLineMacro = 1; + psp->state = WAITING_FOR_DECL_ARG; + if( strcmp(x,"name")==0 ){ + psp->declargslot = &(psp->gp->name); + psp->insertLineMacro = 0; + }else if( strcmp(x,"include")==0 ){ + psp->declargslot = &(psp->gp->include); + }else if( strcmp(x,"code")==0 ){ + psp->declargslot = &(psp->gp->extracode); + }else if( strcmp(x,"token_destructor")==0 ){ + psp->declargslot = &psp->gp->tokendest; + }else if( strcmp(x,"default_destructor")==0 ){ + psp->declargslot = &psp->gp->vardest; + }else if( strcmp(x,"token_prefix")==0 ){ + psp->declargslot = &psp->gp->tokenprefix; + psp->insertLineMacro = 0; + }else if( strcmp(x,"syntax_error")==0 ){ + psp->declargslot = &(psp->gp->error); + }else if( strcmp(x,"parse_accept")==0 ){ + psp->declargslot = &(psp->gp->accept); + }else if( strcmp(x,"parse_failure")==0 ){ + psp->declargslot = &(psp->gp->failure); + }else if( strcmp(x,"stack_overflow")==0 ){ + psp->declargslot = &(psp->gp->overflow); + }else if( strcmp(x,"extra_argument")==0 ){ + psp->declargslot = &(psp->gp->arg); + psp->insertLineMacro = 0; + }else if( strcmp(x,"token_type")==0 ){ + psp->declargslot = &(psp->gp->tokentype); + psp->insertLineMacro = 0; + }else if( strcmp(x,"default_type")==0 ){ + psp->declargslot = &(psp->gp->vartype); + psp->insertLineMacro = 0; + }else if( strcmp(x,"stack_size")==0 ){ + psp->declargslot = &(psp->gp->stacksize); + psp->insertLineMacro = 0; + }else if( strcmp(x,"start_symbol")==0 ){ + psp->declargslot = &(psp->gp->start); + psp->insertLineMacro = 0; + }else if( strcmp(x,"left")==0 ){ + psp->preccounter++; + psp->declassoc = LEFT; + psp->state = WAITING_FOR_PRECEDENCE_SYMBOL; + }else if( strcmp(x,"right")==0 ){ + psp->preccounter++; + psp->declassoc = RIGHT; + psp->state = WAITING_FOR_PRECEDENCE_SYMBOL; + }else if( strcmp(x,"nonassoc")==0 ){ + psp->preccounter++; + psp->declassoc = NONE; + psp->state = WAITING_FOR_PRECEDENCE_SYMBOL; + }else if( strcmp(x,"destructor")==0 ){ + psp->state = WAITING_FOR_DESTRUCTOR_SYMBOL; + }else if( strcmp(x,"type")==0 ){ + psp->state = WAITING_FOR_DATATYPE_SYMBOL; + }else if( strcmp(x,"fallback")==0 ){ + psp->fallback = 0; + psp->state = WAITING_FOR_FALLBACK_ID; + }else if( strcmp(x,"wildcard")==0 ){ + psp->state = WAITING_FOR_WILDCARD_ID; + }else if( strcmp(x,"token_class")==0 ){ + psp->state = WAITING_FOR_CLASS_ID; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Unknown declaration keyword: \"%%%s\".",x); + psp->errorcnt++; + psp->state = RESYNC_AFTER_DECL_ERROR; + } + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Illegal declaration keyword: \"%s\".",x); + psp->errorcnt++; + psp->state = RESYNC_AFTER_DECL_ERROR; + } + break; + case WAITING_FOR_DESTRUCTOR_SYMBOL: + if( !ISALPHA(x[0]) ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "Symbol name missing after %%destructor keyword"); + psp->errorcnt++; + psp->state = RESYNC_AFTER_DECL_ERROR; + }else{ + struct symbol *sp = Symbol_new(x); + psp->declargslot = &sp->destructor; + psp->decllinenoslot = &sp->destLineno; + psp->insertLineMacro = 1; + psp->state = WAITING_FOR_DECL_ARG; + } + break; + case WAITING_FOR_DATATYPE_SYMBOL: + if( !ISALPHA(x[0]) ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "Symbol name missing after %%type keyword"); + psp->errorcnt++; + psp->state = RESYNC_AFTER_DECL_ERROR; + }else{ + struct symbol *sp = Symbol_find(x); + if((sp) && (sp->datatype)){ + ErrorMsg(psp->filename,psp->tokenlineno, + "Symbol %%type \"%s\" already defined", x); + psp->errorcnt++; + psp->state = RESYNC_AFTER_DECL_ERROR; + }else{ + if (!sp){ + sp = Symbol_new(x); + } + psp->declargslot = &sp->datatype; + psp->insertLineMacro = 0; + psp->state = WAITING_FOR_DECL_ARG; + } + } + break; + case WAITING_FOR_PRECEDENCE_SYMBOL: + if( x[0]=='.' ){ + psp->state = WAITING_FOR_DECL_OR_RULE; + }else if( ISUPPER(x[0]) ){ + struct symbol *sp; + sp = Symbol_new(x); + if( sp->prec>=0 ){ + ErrorMsg(psp->filename,psp->tokenlineno, + "Symbol \"%s\" has already be given a precedence.",x); + psp->errorcnt++; + }else{ + sp->prec = psp->preccounter; + sp->assoc = psp->declassoc; + } + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Can't assign a precedence to \"%s\".",x); + psp->errorcnt++; + } + break; + case WAITING_FOR_DECL_ARG: + if( x[0]=='{' || x[0]=='\"' || ISALNUM(x[0]) ){ + const char *zOld, *zNew; + char *zBuf, *z; + int nOld, n, nLine, nNew, nBack; + int addLineMacro; + char zLine[50]; + zNew = x; + if( zNew[0]=='"' || zNew[0]=='{' ) zNew++; + nNew = lemonStrlen(zNew); + if( *psp->declargslot ){ + zOld = *psp->declargslot; + }else{ + zOld = ""; + } + nOld = lemonStrlen(zOld); + n = nOld + nNew + 20; + addLineMacro = !psp->gp->nolinenosflag && psp->insertLineMacro && + (psp->decllinenoslot==0 || psp->decllinenoslot[0]!=0); + if( addLineMacro ){ + for(z=psp->filename, nBack=0; *z; z++){ + if( *z=='\\' ) nBack++; + } + sprintf(zLine, "#line %d ", psp->tokenlineno); + nLine = lemonStrlen(zLine); + n += nLine + lemonStrlen(psp->filename) + nBack; + } + *psp->declargslot = (char *) realloc(*psp->declargslot, n); + zBuf = *psp->declargslot + nOld; + if( addLineMacro ){ + if( nOld && zBuf[-1]!='\n' ){ + *(zBuf++) = '\n'; + } + memcpy(zBuf, zLine, nLine); + zBuf += nLine; + *(zBuf++) = '"'; + for(z=psp->filename; *z; z++){ + if( *z=='\\' ){ + *(zBuf++) = '\\'; + } + *(zBuf++) = *z; + } + *(zBuf++) = '"'; + *(zBuf++) = '\n'; + } + if( psp->decllinenoslot && psp->decllinenoslot[0]==0 ){ + psp->decllinenoslot[0] = psp->tokenlineno; + } + memcpy(zBuf, zNew, nNew); + zBuf += nNew; + *zBuf = 0; + psp->state = WAITING_FOR_DECL_OR_RULE; + }else{ + ErrorMsg(psp->filename,psp->tokenlineno, + "Illegal argument to %%%s: %s",psp->declkeyword,x); + psp->errorcnt++; + psp->state = RESYNC_AFTER_DECL_ERROR; + } + break; + case WAITING_FOR_FALLBACK_ID: + if( x[0]=='.' ){ + psp->state = WAITING_FOR_DECL_OR_RULE; + }else if( !ISUPPER(x[0]) ){ + ErrorMsg(psp->filename, psp->tokenlineno, + "%%fallback argument \"%s\" should be a token", x); + psp->errorcnt++; + }else{ + struct symbol *sp = Symbol_new(x); + if( psp->fallback==0 ){ + psp->fallback = sp; + }else if( sp->fallback ){ + ErrorMsg(psp->filename, psp->tokenlineno, + "More than one fallback assigned to token %s", x); + psp->errorcnt++; + }else{ + sp->fallback = psp->fallback; + psp->gp->has_fallback = 1; + } + } + break; + case WAITING_FOR_WILDCARD_ID: + if( x[0]=='.' ){ + psp->state = WAITING_FOR_DECL_OR_RULE; + }else if( !ISUPPER(x[0]) ){ + ErrorMsg(psp->filename, psp->tokenlineno, + "%%wildcard argument \"%s\" should be a token", x); + psp->errorcnt++; + }else{ + struct symbol *sp = Symbol_new(x); + if( psp->gp->wildcard==0 ){ + psp->gp->wildcard = sp; + }else{ + ErrorMsg(psp->filename, psp->tokenlineno, + "Extra wildcard to token: %s", x); + psp->errorcnt++; + } + } + break; + case WAITING_FOR_CLASS_ID: + if( !ISLOWER(x[0]) ){ + ErrorMsg(psp->filename, psp->tokenlineno, + "%%token_class must be followed by an identifier: ", x); + psp->errorcnt++; + psp->state = RESYNC_AFTER_DECL_ERROR; + }else if( Symbol_find(x) ){ + ErrorMsg(psp->filename, psp->tokenlineno, + "Symbol \"%s\" already used", x); + psp->errorcnt++; + psp->state = RESYNC_AFTER_DECL_ERROR; + }else{ + psp->tkclass = Symbol_new(x); + psp->tkclass->type = MULTITERMINAL; + psp->state = WAITING_FOR_CLASS_TOKEN; + } + break; + case WAITING_FOR_CLASS_TOKEN: + if( x[0]=='.' ){ + psp->state = WAITING_FOR_DECL_OR_RULE; + }else if( ISUPPER(x[0]) || ((x[0]=='|' || x[0]=='/') && ISUPPER(x[1])) ){ + struct symbol *msp = psp->tkclass; + msp->nsubsym++; + msp->subsym = (struct symbol **) realloc(msp->subsym, + sizeof(struct symbol*)*msp->nsubsym); + if( !ISUPPER(x[0]) ) x++; + msp->subsym[msp->nsubsym-1] = Symbol_new(x); + }else{ + ErrorMsg(psp->filename, psp->tokenlineno, + "%%token_class argument \"%s\" should be a token", x); + psp->errorcnt++; + psp->state = RESYNC_AFTER_DECL_ERROR; + } + break; + case RESYNC_AFTER_RULE_ERROR: +/* if( x[0]=='.' ) psp->state = WAITING_FOR_DECL_OR_RULE; +** break; */ + case RESYNC_AFTER_DECL_ERROR: + if( x[0]=='.' ) psp->state = WAITING_FOR_DECL_OR_RULE; + if( x[0]=='%' ) psp->state = WAITING_FOR_DECL_KEYWORD; + break; + } +} + +/* Run the preprocessor over the input file text. The global variables +** azDefine[0] through azDefine[nDefine-1] contains the names of all defined +** macros. This routine looks for "%ifdef" and "%ifndef" and "%endif" and +** comments them out. Text in between is also commented out as appropriate. +*/ +static void preprocess_input(char *z){ + int i, j, k, n; + int exclude = 0; + int start = 0; + int lineno = 1; + int start_lineno = 1; + for(i=0; z[i]; i++){ + if( z[i]=='\n' ) lineno++; + if( z[i]!='%' || (i>0 && z[i-1]!='\n') ) continue; + if( strncmp(&z[i],"%endif",6)==0 && ISSPACE(z[i+6]) ){ + if( exclude ){ + exclude--; + if( exclude==0 ){ + for(j=start; jfilename; + ps.errorcnt = 0; + ps.state = INITIALIZE; + + /* Begin by reading the input file */ + fp = fopen(ps.filename,"rb"); + if( fp==0 ){ + ErrorMsg(ps.filename,0,"Can't open this file for reading."); + gp->errorcnt++; + return; + } + fseek(fp,0,2); + filesize = ftell(fp); + rewind(fp); + filebuf = (char *)malloc( filesize+1 ); + if( filesize>100000000 || filebuf==0 ){ + ErrorMsg(ps.filename,0,"Input file too large."); + gp->errorcnt++; + fclose(fp); + return; + } + if( fread(filebuf,1,filesize,fp)!=filesize ){ + ErrorMsg(ps.filename,0,"Can't read in all %d bytes of this file.", + filesize); + free(filebuf); + gp->errorcnt++; + fclose(fp); + return; + } + fclose(fp); + filesize = strip_crlf(filebuf, filesize); + filebuf[filesize] = 0; + + /* Make an initial pass through the file to handle %ifdef and %ifndef */ + preprocess_input(filebuf); + + /* Now scan the text of the input file */ + lineno = 1; + for(cp=filebuf; (c= *cp)!=0; ){ + if( c=='\n' ) lineno++; /* Keep track of the line number */ + if( ISSPACE(c) ){ cp++; continue; } /* Skip all white space */ + if( c=='/' && cp[1]=='/' ){ /* Skip C++ style comments */ + cp+=2; + while( (c= *cp)!=0 && c!='\n' ) cp++; + continue; + } + if( c=='/' && cp[1]=='*' ){ /* Skip C style comments */ + cp+=2; + while( (c= *cp)!=0 && (c!='/' || cp[-1]!='*') ){ + if( c=='\n' ) lineno++; + cp++; + } + if( c ) cp++; + continue; + } + ps.tokenstart = cp; /* Mark the beginning of the token */ + ps.tokenlineno = lineno; /* Linenumber on which token begins */ + if( c=='\"' ){ /* String literals */ + cp++; + while( (c= *cp)!=0 && c!='\"' ){ + if( c=='\n' ) lineno++; + cp++; + } + if( c==0 ){ + ErrorMsg(ps.filename,startline, +"String starting on this line is not terminated before the end of the file."); + ps.errorcnt++; + nextcp = cp; + }else{ + nextcp = cp+1; + } + }else if( c=='{' ){ /* A block of C code */ + int level; + cp++; + for(level=1; (c= *cp)!=0 && (level>1 || c!='}'); cp++){ + if( c=='\n' ) lineno++; + else if( c=='{' ) level++; + else if( c=='}' ) level--; + else if( c=='/' && cp[1]=='*' ){ /* Skip comments */ + int prevc; + cp = &cp[2]; + prevc = 0; + while( (c= *cp)!=0 && (c!='/' || prevc!='*') ){ + if( c=='\n' ) lineno++; + prevc = c; + cp++; + } + }else if( c=='/' && cp[1]=='/' ){ /* Skip C++ style comments too */ + cp = &cp[2]; + while( (c= *cp)!=0 && c!='\n' ) cp++; + if( c ) lineno++; + }else if( c=='\'' || c=='\"' ){ /* String a character literals */ + int startchar, prevc; + startchar = c; + prevc = 0; + for(cp++; (c= *cp)!=0 && (c!=startchar || prevc=='\\'); cp++){ + if( c=='\n' ) lineno++; + if( prevc=='\\' ) prevc = 0; + else prevc = c; + } + } + } + if( c==0 ){ + ErrorMsg(ps.filename,ps.tokenlineno, +"C code starting on this line is not terminated before the end of the file."); + ps.errorcnt++; + nextcp = cp; + }else{ + nextcp = cp+1; + } + }else if( ISALNUM(c) ){ /* Identifiers */ + while( (c= *cp)!=0 && (ISALNUM(c) || c=='_') ) cp++; + nextcp = cp; + }else if( c==':' && cp[1]==':' && cp[2]=='=' ){ /* The operator "::=" */ + cp += 3; + nextcp = cp; + }else if( (c=='/' || c=='|') && ISALPHA(cp[1]) ){ + cp += 2; + while( (c = *cp)!=0 && (ISALNUM(c) || c=='_') ) cp++; + nextcp = cp; + }else{ /* All other (one character) operators */ + cp++; + nextcp = cp; + } + c = *cp; + *cp = 0; /* Null terminate the token */ + parseonetoken(&ps); /* Parse the token */ + *cp = c; /* Restore the buffer */ + cp = nextcp; + } + free(filebuf); /* Release the buffer after parsing */ + gp->rule = ps.firstrule; + gp->errorcnt = ps.errorcnt; +} +/*************************** From the file "plink.c" *********************/ +/* +** Routines processing configuration follow-set propagation links +** in the LEMON parser generator. +*/ +static struct plink *plink_freelist = 0; + +/* Allocate a new plink */ +struct plink *Plink_new(){ + struct plink *newlink; + + if( plink_freelist==0 ){ + int i; + int amt = 100; + plink_freelist = (struct plink *)calloc( amt, sizeof(struct plink) ); + if( plink_freelist==0 ){ + fprintf(stderr, + "Unable to allocate memory for a new follow-set propagation link.\n"); + exit(1); + } + for(i=0; inext; + return newlink; +} + +/* Add a plink to a plink list */ +void Plink_add(struct plink **plpp, struct config *cfp) +{ + struct plink *newlink; + newlink = Plink_new(); + newlink->next = *plpp; + *plpp = newlink; + newlink->cfp = cfp; +} + +/* Transfer every plink on the list "from" to the list "to" */ +void Plink_copy(struct plink **to, struct plink *from) +{ + struct plink *nextpl; + while( from ){ + nextpl = from->next; + from->next = *to; + *to = from; + from = nextpl; + } +} + +/* Delete every plink on the list */ +void Plink_delete(struct plink *plp) +{ + struct plink *nextpl; + + while( plp ){ + nextpl = plp->next; + plp->next = plink_freelist; + plink_freelist = plp; + plp = nextpl; + } +} +/*********************** From the file "report.c" **************************/ +/* +** Procedures for generating reports and tables in the LEMON parser generator. +*/ + +/* Generate a filename with the given suffix. Space to hold the +** name comes from malloc() and must be freed by the calling +** function. +*/ +PRIVATE char *file_makename(struct lemon *lemp, const char *suffix) +{ + char *name; + char *cp; + + name = (char*)malloc( lemonStrlen(lemp->outbasefilename) + lemonStrlen(suffix) + 5 ); + if( name==0 ){ + fprintf(stderr,"Can't allocate space for a filename.\n"); + exit(1); + } + strcpy(name,lemp->outbasefilename); + cp = strrchr(name,'.'); + if( cp ) *cp = 0; + strcat(name,suffix); + return name; +} + +/* Open a file with a name based on the name of the input file, +** but with a different (specified) suffix, and return a pointer +** to the stream */ +PRIVATE FILE *file_open( + struct lemon *lemp, + const char *suffix, + const char *mode +){ + FILE *fp; + + if( lemp->outname ) free(lemp->outname); + lemp->outname = file_makename(lemp, suffix); + fp = fopen(lemp->outname,mode); + if( fp==0 && *mode=='w' ){ + fprintf(stderr,"Can't open file \"%s\".\n",lemp->outname); + lemp->errorcnt++; + return 0; + } + return fp; +} + +/* Duplicate the input file without comments and without actions +** on rules */ +void Reprint(struct lemon *lemp) +{ + struct rule *rp; + struct symbol *sp; + int i, j, maxlen, len, ncolumns, skip; + printf("// Reprint of input file \"%s\".\n// Symbols:\n",lemp->filename); + maxlen = 10; + for(i=0; insymbol; i++){ + sp = lemp->symbols[i]; + len = lemonStrlen(sp->name); + if( len>maxlen ) maxlen = len; + } + ncolumns = 76/(maxlen+5); + if( ncolumns<1 ) ncolumns = 1; + skip = (lemp->nsymbol + ncolumns - 1)/ncolumns; + for(i=0; insymbol; j+=skip){ + sp = lemp->symbols[j]; + assert( sp->index==j ); + printf(" %3d %-*.*s",j,maxlen,maxlen,sp->name); + } + printf("\n"); + } + for(rp=lemp->rule; rp; rp=rp->next){ + printf("%s",rp->lhs->name); + /* if( rp->lhsalias ) printf("(%s)",rp->lhsalias); */ + printf(" ::="); + for(i=0; inrhs; i++){ + sp = rp->rhs[i]; + if( sp->type==MULTITERMINAL ){ + printf(" %s", sp->subsym[0]->name); + for(j=1; jnsubsym; j++){ + printf("|%s", sp->subsym[j]->name); + } + }else{ + printf(" %s", sp->name); + } + /* if( rp->rhsalias[i] ) printf("(%s)",rp->rhsalias[i]); */ + } + printf("."); + if( rp->precsym ) printf(" [%s]",rp->precsym->name); + /* if( rp->code ) printf("\n %s",rp->code); */ + printf("\n"); + } +} + +/* Print a single rule. +*/ +void RulePrint(FILE *fp, struct rule *rp, int iCursor){ + struct symbol *sp; + int i, j; + fprintf(fp,"%s ::=",rp->lhs->name); + for(i=0; i<=rp->nrhs; i++){ + if( i==iCursor ) fprintf(fp," *"); + if( i==rp->nrhs ) break; + sp = rp->rhs[i]; + if( sp->type==MULTITERMINAL ){ + fprintf(fp," %s", sp->subsym[0]->name); + for(j=1; jnsubsym; j++){ + fprintf(fp,"|%s",sp->subsym[j]->name); + } + }else{ + fprintf(fp," %s", sp->name); + } + } +} + +/* Print the rule for a configuration. +*/ +void ConfigPrint(FILE *fp, struct config *cfp){ + RulePrint(fp, cfp->rp, cfp->dot); +} + +/* #define TEST */ +#if 0 +/* Print a set */ +PRIVATE void SetPrint(out,set,lemp) +FILE *out; +char *set; +struct lemon *lemp; +{ + int i; + char *spacer; + spacer = ""; + fprintf(out,"%12s[",""); + for(i=0; interminal; i++){ + if( SetFind(set,i) ){ + fprintf(out,"%s%s",spacer,lemp->symbols[i]->name); + spacer = " "; + } + } + fprintf(out,"]\n"); +} + +/* Print a plink chain */ +PRIVATE void PlinkPrint(out,plp,tag) +FILE *out; +struct plink *plp; +char *tag; +{ + while( plp ){ + fprintf(out,"%12s%s (state %2d) ","",tag,plp->cfp->stp->statenum); + ConfigPrint(out,plp->cfp); + fprintf(out,"\n"); + plp = plp->next; + } +} +#endif + +/* Print an action to the given file descriptor. Return FALSE if +** nothing was actually printed. +*/ +int PrintAction( + struct action *ap, /* The action to print */ + FILE *fp, /* Print the action here */ + int indent /* Indent by this amount */ +){ + int result = 1; + switch( ap->type ){ + case SHIFT: { + struct state *stp = ap->x.stp; + fprintf(fp,"%*s shift %-7d",indent,ap->sp->name,stp->statenum); + break; + } + case REDUCE: { + struct rule *rp = ap->x.rp; + fprintf(fp,"%*s reduce %-7d",indent,ap->sp->name,rp->iRule); + RulePrint(fp, rp, -1); + break; + } + case SHIFTREDUCE: { + struct rule *rp = ap->x.rp; + fprintf(fp,"%*s shift-reduce %-7d",indent,ap->sp->name,rp->iRule); + RulePrint(fp, rp, -1); + break; + } + case ACCEPT: + fprintf(fp,"%*s accept",indent,ap->sp->name); + break; + case ERROR: + fprintf(fp,"%*s error",indent,ap->sp->name); + break; + case SRCONFLICT: + case RRCONFLICT: + fprintf(fp,"%*s reduce %-7d ** Parsing conflict **", + indent,ap->sp->name,ap->x.rp->iRule); + break; + case SSCONFLICT: + fprintf(fp,"%*s shift %-7d ** Parsing conflict **", + indent,ap->sp->name,ap->x.stp->statenum); + break; + case SH_RESOLVED: + if( showPrecedenceConflict ){ + fprintf(fp,"%*s shift %-7d -- dropped by precedence", + indent,ap->sp->name,ap->x.stp->statenum); + }else{ + result = 0; + } + break; + case RD_RESOLVED: + if( showPrecedenceConflict ){ + fprintf(fp,"%*s reduce %-7d -- dropped by precedence", + indent,ap->sp->name,ap->x.rp->iRule); + }else{ + result = 0; + } + break; + case NOT_USED: + result = 0; + break; + } + if( result && ap->spOpt ){ + fprintf(fp," /* because %s==%s */", ap->sp->name, ap->spOpt->name); + } + return result; +} + +/* Generate the "*.out" log file */ +void ReportOutput(struct lemon *lemp) +{ + int i; + struct state *stp; + struct config *cfp; + struct action *ap; + FILE *fp; + + fp = file_open(lemp,".out","wb"); + if( fp==0 ) return; + for(i=0; inxstate; i++){ + stp = lemp->sorted[i]; + fprintf(fp,"State %d:\n",stp->statenum); + if( lemp->basisflag ) cfp=stp->bp; + else cfp=stp->cfp; + while( cfp ){ + char buf[20]; + if( cfp->dot==cfp->rp->nrhs ){ + sprintf(buf,"(%d)",cfp->rp->iRule); + fprintf(fp," %5s ",buf); + }else{ + fprintf(fp," "); + } + ConfigPrint(fp,cfp); + fprintf(fp,"\n"); +#if 0 + SetPrint(fp,cfp->fws,lemp); + PlinkPrint(fp,cfp->fplp,"To "); + PlinkPrint(fp,cfp->bplp,"From"); +#endif + if( lemp->basisflag ) cfp=cfp->bp; + else cfp=cfp->next; + } + fprintf(fp,"\n"); + for(ap=stp->ap; ap; ap=ap->next){ + if( PrintAction(ap,fp,30) ) fprintf(fp,"\n"); + } + fprintf(fp,"\n"); + } + fprintf(fp, "----------------------------------------------------\n"); + fprintf(fp, "Symbols:\n"); + for(i=0; insymbol; i++){ + int j; + struct symbol *sp; + + sp = lemp->symbols[i]; + fprintf(fp, " %3d: %s", i, sp->name); + if( sp->type==NONTERMINAL ){ + fprintf(fp, ":"); + if( sp->lambda ){ + fprintf(fp, " "); + } + for(j=0; jnterminal; j++){ + if( sp->firstset && SetFind(sp->firstset, j) ){ + fprintf(fp, " %s", lemp->symbols[j]->name); + } + } + } + fprintf(fp, "\n"); + } + fclose(fp); + return; +} + +/* Search for the file "name" which is in the same directory as +** the exacutable */ +PRIVATE char *pathsearch(char *argv0, char *name, int modemask) +{ + const char *pathlist; + char *pathbufptr; + char *pathbuf; + char *path,*cp; + char c; + +#ifdef __WIN32__ + for (cp = argv0 + lemonStrlen(argv0); cp-- > argv0; ) + { + if( *cp == '\\' || *cp == '/' ) + break; + } +#else + cp = strrchr(argv0,'/'); +#endif + if( cp ){ + c = *cp; + *cp = 0; + path = (char *)malloc( lemonStrlen(argv0) + lemonStrlen(name) + 2 ); + if( path ) sprintf(path,"%s/%s",argv0,name); + *cp = c; + }else{ + pathlist = getenv("PATH"); + if( pathlist==0 ) pathlist = ".:/bin:/usr/bin"; + pathbuf = (char *) malloc( lemonStrlen(pathlist) + 1 ); + path = (char *)malloc( lemonStrlen(pathlist)+lemonStrlen(name)+2 ); + if( (pathbuf != 0) && (path!=0) ){ + pathbufptr = pathbuf; + strcpy(pathbuf, pathlist); + while( *pathbuf ){ + cp = strchr(pathbuf,':'); + if( cp==0 ) cp = &pathbuf[lemonStrlen(pathbuf)]; + c = *cp; + *cp = 0; + sprintf(path,"%s/%s",pathbuf,name); + *cp = c; + if( c==0 ) pathbuf[0] = 0; + else pathbuf = &cp[1]; + if( access(path,modemask)==0 ) break; + } + free(pathbufptr); + } + } + return path; +} + +/* Given an action, compute the integer value for that action +** which is to be put in the action table of the generated machine. +** Return negative if no action should be generated. +*/ +PRIVATE int compute_action(struct lemon *lemp, struct action *ap) +{ + int act; + switch( ap->type ){ + case SHIFT: act = ap->x.stp->statenum; break; + case SHIFTREDUCE: act = ap->x.rp->iRule + lemp->nstate; break; + case REDUCE: act = ap->x.rp->iRule + lemp->nstate+lemp->nrule; break; + case ERROR: act = lemp->nstate + lemp->nrule*2; break; + case ACCEPT: act = lemp->nstate + lemp->nrule*2 + 1; break; + default: act = -1; break; + } + return act; +} + +#define LINESIZE 1000 +/* The next cluster of routines are for reading the template file +** and writing the results to the generated parser */ +/* The first function transfers data from "in" to "out" until +** a line is seen which begins with "%%". The line number is +** tracked. +** +** if name!=0, then any word that begin with "Parse" is changed to +** begin with *name instead. +*/ +PRIVATE void tplt_xfer(char *name, FILE *in, FILE *out, int *lineno) +{ + int i, iStart; + char line[LINESIZE]; + while( fgets(line,LINESIZE,in) && (line[0]!='%' || line[1]!='%') ){ + (*lineno)++; + iStart = 0; + if( name ){ + for(i=0; line[i]; i++){ + if( line[i]=='P' && strncmp(&line[i],"Parse",5)==0 + && (i==0 || !ISALPHA(line[i-1])) + ){ + if( i>iStart ) fprintf(out,"%.*s",i-iStart,&line[iStart]); + fprintf(out,"%s",name); + i += 4; + iStart = i+1; + } + } + } + fprintf(out,"%s",&line[iStart]); + } +} + +/* The next function finds the template file and opens it, returning +** a pointer to the opened file. */ +PRIVATE FILE *tplt_open(struct lemon *lemp) +{ + static char templatename[] = "lempar.c"; + char buf[1000]; + FILE *in; + char *tpltname; + char *cp; + Boolean tpltnameinbuf; + + /* first, see if user specified a template filename on the command line. */ + if (user_templatename != 0) { + if( access(user_templatename,004)==-1 ){ + fprintf(stderr,"Can't find the parser driver template file \"%s\".\n", + user_templatename); + lemp->errorcnt++; + return 0; + } + in = fopen(user_templatename,"rb"); + if( in==0 ){ + fprintf(stderr,"Can't open the template file \"%s\".\n", + user_templatename); + lemp->errorcnt++; + return 0; + } + return in; + } + + cp = strrchr(lemp->filename,'.'); + if( cp ){ + sprintf(buf,"%.*s.lt",(int)(cp-lemp->filename),lemp->filename); + }else{ + sprintf(buf,"%s.lt",lemp->filename); + } + if( access(buf,004)==0 ){ + tpltname = buf; + tpltnameinbuf = LEMON_TRUE; + }else if( access(templatename,004)==0 ){ + tpltname = templatename; + tpltnameinbuf = LEMON_TRUE; + }else{ + tpltname = pathsearch(lemp->argv0,templatename,0); + tpltnameinbuf = LEMON_FALSE; + } + if( tpltname==0 ){ + fprintf(stderr,"Can't find the parser driver template file \"%s\".\n", + templatename); + lemp->errorcnt++; + return 0; + } + in = fopen(tpltname,"rb"); + if( in==0 ){ + fprintf(stderr,"Can't open the template file \"%s\".\n",templatename); + if (tpltnameinbuf == LEMON_FALSE) free(tpltname); + lemp->errorcnt++; + return 0; + } + if (tpltnameinbuf == LEMON_FALSE) free(tpltname); + return in; +} + +/* Print a #line directive line to the output file. */ +PRIVATE void tplt_linedir(FILE *out, int lineno, char *filename) +{ + fprintf(out,"#line %d \"",lineno); + while( *filename ){ + if( *filename == '\\' ) putc('\\',out); + putc(*filename,out); + filename++; + } + fprintf(out,"\"\n"); +} + +/* Print a string to the file and keep the linenumber up to date */ +PRIVATE void tplt_print(FILE *out, struct lemon *lemp, char *str, int *lineno) +{ + if( str==0 ) return; + while( *str ){ + if( *str=='\n' ) (*lineno)++; + putc(*str,out); + str++; + } + if( str[-1]!='\n' ){ + putc('\n',out); + (*lineno)++; + } + if (!lemp->nolinenosflag) { + (*lineno)++; tplt_linedir(out,*lineno,lemp->outname); + } + + return; +} + +/* +** The following routine emits code for the destructor for the +** symbol sp +*/ +void emit_destructor_code( + FILE *out, + struct symbol *sp, + struct lemon *lemp, + int *lineno +){ + char *cp = 0; + + if( sp->type==TERMINAL ){ + cp = lemp->tokendest; + if( cp==0 ) return; + fprintf(out,"{\n"); (*lineno)++; + }else if( sp->destructor ){ + cp = sp->destructor; + fprintf(out,"{\n"); (*lineno)++; + if( !lemp->nolinenosflag ){ + (*lineno)++; + tplt_linedir(out,sp->destLineno,lemp->filename); + } + }else if( lemp->vardest ){ + cp = lemp->vardest; + if( cp==0 ) return; + fprintf(out,"{\n"); (*lineno)++; + }else{ + assert( 0 ); /* Cannot happen */ + } + for(; *cp; cp++){ + if( *cp=='$' && cp[1]=='$' ){ + fprintf(out,"(yypminor->yy%d)",sp->dtnum); + cp++; + continue; + } + if( *cp=='\n' ) (*lineno)++; + fputc(*cp,out); + } + fprintf(out,"\n"); (*lineno)++; + if (!lemp->nolinenosflag) { + (*lineno)++; tplt_linedir(out,*lineno,lemp->outname); + } + fprintf(out,"}\n"); (*lineno)++; + return; +} + +/* +** Return TRUE (non-zero) if the given symbol has a destructor. +*/ +int has_destructor(struct symbol *sp, struct lemon *lemp) +{ + int ret; + if( sp->type==TERMINAL ){ + ret = lemp->tokendest!=0; + }else{ + ret = lemp->vardest!=0 || sp->destructor!=0; + } + return ret; +} + +/* +** Append text to a dynamically allocated string. If zText is 0 then +** reset the string to be empty again. Always return the complete text +** of the string (which is overwritten with each call). +** +** n bytes of zText are stored. If n==0 then all of zText up to the first +** \000 terminator is stored. zText can contain up to two instances of +** %d. The values of p1 and p2 are written into the first and second +** %d. +** +** If n==-1, then the previous character is overwritten. +*/ +PRIVATE char *append_str(const char *zText, int n, int p1, int p2, int bNoSubst){ + static char empty[1] = { 0 }; + static char *z = 0; + static int alloced = 0; + static int used = 0; + int c; + char zInt[40]; + if( zText==0 ){ + if( used==0 && z!=0 ) z[0] = 0; + used = 0; + return z; + } + if( n<=0 ){ + if( n<0 ){ + used += n; + assert( used>=0 ); + } + n = lemonStrlen(zText); + } + if( n+sizeof(zInt)*2+used >= (size_t)alloced ){ + alloced = n + sizeof(zInt)*2 + used + 200; + z = (char *) realloc(z, alloced); + } + if( z==0 ) return empty; + while( n-- > 0 ){ + c = *(zText++); + if( !bNoSubst && c=='%' && n>0 && zText[0]=='d' ){ + sprintf(zInt, "%d", p1); + p1 = p2; + strcpy(&z[used], zInt); + used += lemonStrlen(&z[used]); + zText++; + n--; + }else{ + z[used++] = c; + } + } + z[used] = 0; + return z; +} + +/* +** Write and transform the rp->code string so that symbols are expanded. +** Populate the rp->codePrefix and rp->codeSuffix strings, as appropriate. +** +** Return 1 if the expanded code requires that "yylhsminor" local variable +** to be defined. +*/ +PRIVATE int translate_code(struct lemon *lemp, struct rule *rp){ + char *cp, *xp; + int i; + int rc = 0; /* True if yylhsminor is used */ + int dontUseRhs0 = 0; /* If true, use of left-most RHS label is illegal */ + const char *zSkip = 0; /* The zOvwrt comment within rp->code, or NULL */ + char lhsused = 0; /* True if the LHS element has been used */ + char lhsdirect; /* True if LHS writes directly into stack */ + char used[MAXRHS]; /* True for each RHS element which is used */ + char zLhs[50]; /* Convert the LHS symbol into this string */ + char zOvwrt[900]; /* Comment that to allow LHS to overwrite RHS */ + + for(i=0; inrhs; i++) used[i] = 0; + lhsused = 0; + + if( rp->code==0 ){ + static char newlinestr[2] = { '\n', '\0' }; + rp->code = newlinestr; + rp->line = rp->ruleline; + rp->noCode = 1; + }else{ + rp->noCode = 0; + } + + if( rp->nrhs==0 ){ + /* If there are no RHS symbols, then writing directly to the LHS is ok */ + lhsdirect = 1; + }else if( rp->rhsalias[0]==0 ){ + /* The left-most RHS symbol has not value. LHS direct is ok. But + ** we have to call the distructor on the RHS symbol first. */ + lhsdirect = 1; + if( has_destructor(rp->rhs[0],lemp) ){ + append_str(0,0,0,0,0); + append_str(" yy_destructor(yypParser,%d,&yymsp[%d].minor);\n", 0, + rp->rhs[0]->index,1-rp->nrhs,0); + rp->codePrefix = Strsafe(append_str(0,0,0,0,0)); + rp->noCode = 0; + } + }else if( rp->lhsalias==0 ){ + /* There is no LHS value symbol. */ + lhsdirect = 1; + }else if( strcmp(rp->lhsalias,rp->rhsalias[0])==0 ){ + /* The LHS symbol and the left-most RHS symbol are the same, so + ** direct writing is allowed */ + lhsdirect = 1; + lhsused = 1; + used[0] = 1; + if( rp->lhs->dtnum!=rp->rhs[0]->dtnum ){ + ErrorMsg(lemp->filename,rp->ruleline, + "%s(%s) and %s(%s) share the same label but have " + "different datatypes.", + rp->lhs->name, rp->lhsalias, rp->rhs[0]->name, rp->rhsalias[0]); + lemp->errorcnt++; + } + }else{ + sprintf(zOvwrt, "/*%s-overwrites-%s*/", rp->lhsalias, rp->rhsalias[0]); + zSkip = strstr(rp->code, zOvwrt); + if( zSkip!=0 ){ + /* The code contains a special comment that indicates that it is safe + ** for the LHS label to overwrite left-most RHS label. */ + lhsdirect = 1; + }else{ + lhsdirect = 0; + } + } + if( lhsdirect ){ + sprintf(zLhs, "yymsp[%d].minor.yy%d",1-rp->nrhs,rp->lhs->dtnum); + }else{ + rc = 1; + sprintf(zLhs, "yylhsminor.yy%d",rp->lhs->dtnum); + } + + append_str(0,0,0,0,0); + + /* This const cast is wrong but harmless, if we're careful. */ + for(cp=(char *)rp->code; *cp; cp++){ + if( cp==zSkip ){ + append_str(zOvwrt,0,0,0,0); + cp += lemonStrlen(zOvwrt)-1; + dontUseRhs0 = 1; + continue; + } + if( ISALPHA(*cp) && (cp==rp->code || (!ISALNUM(cp[-1]) && cp[-1]!='_')) ){ + char saved; + for(xp= &cp[1]; ISALNUM(*xp) || *xp=='_'; xp++); + saved = *xp; + *xp = 0; + if( rp->lhsalias && strcmp(cp,rp->lhsalias)==0 ){ + append_str(zLhs,0,0,0,0); + cp = xp; + lhsused = 1; + }else{ + for(i=0; inrhs; i++){ + if( rp->rhsalias[i] && strcmp(cp,rp->rhsalias[i])==0 ){ + if( i==0 && dontUseRhs0 ){ + ErrorMsg(lemp->filename,rp->ruleline, + "Label %s used after '%s'.", + rp->rhsalias[0], zOvwrt); + lemp->errorcnt++; + }else if( cp!=rp->code && cp[-1]=='@' ){ + /* If the argument is of the form @X then substituted + ** the token number of X, not the value of X */ + append_str("yymsp[%d].major",-1,i-rp->nrhs+1,0,0); + }else{ + struct symbol *sp = rp->rhs[i]; + int dtnum; + if( sp->type==MULTITERMINAL ){ + dtnum = sp->subsym[0]->dtnum; + }else{ + dtnum = sp->dtnum; + } + append_str("yymsp[%d].minor.yy%d",0,i-rp->nrhs+1, dtnum,0); + } + cp = xp; + used[i] = 1; + break; + } + } + } + *xp = saved; + } + append_str(cp, 1, 0, 0, 1); + } /* End loop */ + + /* Main code generation completed */ + cp = append_str(0,0,0,0,0); + if( cp && cp[0] ) rp->code = Strsafe(cp); + append_str(0,0,0,0,0); + + /* Check to make sure the LHS has been used */ + if( rp->lhsalias && !lhsused ){ + ErrorMsg(lemp->filename,rp->ruleline, + "Label \"%s\" for \"%s(%s)\" is never used.", + rp->lhsalias,rp->lhs->name,rp->lhsalias); + lemp->errorcnt++; + } + + /* Generate destructor code for RHS minor values which are not referenced. + ** Generate error messages for unused labels and duplicate labels. + */ + for(i=0; inrhs; i++){ + if( rp->rhsalias[i] ){ + if( i>0 ){ + int j; + if( rp->lhsalias && strcmp(rp->lhsalias,rp->rhsalias[i])==0 ){ + ErrorMsg(lemp->filename,rp->ruleline, + "%s(%s) has the same label as the LHS but is not the left-most " + "symbol on the RHS.", + rp->rhs[i]->name, rp->rhsalias); + lemp->errorcnt++; + } + for(j=0; jrhsalias[j] && strcmp(rp->rhsalias[j],rp->rhsalias[i])==0 ){ + ErrorMsg(lemp->filename,rp->ruleline, + "Label %s used for multiple symbols on the RHS of a rule.", + rp->rhsalias[i]); + lemp->errorcnt++; + break; + } + } + } + if( !used[i] ){ + ErrorMsg(lemp->filename,rp->ruleline, + "Label %s for \"%s(%s)\" is never used.", + rp->rhsalias[i],rp->rhs[i]->name,rp->rhsalias[i]); + lemp->errorcnt++; + } + }else if( i>0 && has_destructor(rp->rhs[i],lemp) ){ + append_str(" yy_destructor(yypParser,%d,&yymsp[%d].minor);\n", 0, + rp->rhs[i]->index,i-rp->nrhs+1,0); + } + } + + /* If unable to write LHS values directly into the stack, write the + ** saved LHS value now. */ + if( lhsdirect==0 ){ + append_str(" yymsp[%d].minor.yy%d = ", 0, 1-rp->nrhs, rp->lhs->dtnum, 0); + append_str(zLhs, 0, 0, 0, 0); + append_str(";\n", 0, 0, 0, 0); + } + + /* Suffix code generation complete */ + cp = append_str(0,0,0,0,0); + if( cp && cp[0] ){ + rp->codeSuffix = Strsafe(cp); + rp->noCode = 0; + } + + return rc; +} + +/* +** Generate code which executes when the rule "rp" is reduced. Write +** the code to "out". Make sure lineno stays up-to-date. +*/ +PRIVATE void emit_code( + FILE *out, + struct rule *rp, + struct lemon *lemp, + int *lineno +){ + const char *cp; + + /* Setup code prior to the #line directive */ + if( rp->codePrefix && rp->codePrefix[0] ){ + fprintf(out, "{%s", rp->codePrefix); + for(cp=rp->codePrefix; *cp; cp++){ if( *cp=='\n' ) (*lineno)++; } + } + + /* Generate code to do the reduce action */ + if( rp->code ){ + if( !lemp->nolinenosflag ){ + (*lineno)++; + tplt_linedir(out,rp->line,lemp->filename); + } + fprintf(out,"{%s",rp->code); + for(cp=rp->code; *cp; cp++){ if( *cp=='\n' ) (*lineno)++; } + fprintf(out,"}\n"); (*lineno)++; + if( !lemp->nolinenosflag ){ + (*lineno)++; + tplt_linedir(out,*lineno,lemp->outname); + } + } + + /* Generate breakdown code that occurs after the #line directive */ + if( rp->codeSuffix && rp->codeSuffix[0] ){ + fprintf(out, "%s", rp->codeSuffix); + for(cp=rp->codeSuffix; *cp; cp++){ if( *cp=='\n' ) (*lineno)++; } + } + + if( rp->codePrefix ){ + fprintf(out, "}\n"); (*lineno)++; + } + + return; +} + +/* +** Print the definition of the union used for the parser's data stack. +** This union contains fields for every possible data type for tokens +** and nonterminals. In the process of computing and printing this +** union, also set the ".dtnum" field of every terminal and nonterminal +** symbol. +*/ +void print_stack_union( + FILE *out, /* The output stream */ + struct lemon *lemp, /* The main info structure for this parser */ + int *plineno, /* Pointer to the line number */ + int mhflag /* True if generating makeheaders output */ +){ + int lineno = *plineno; /* The line number of the output */ + char **types; /* A hash table of datatypes */ + int arraysize; /* Size of the "types" array */ + int maxdtlength; /* Maximum length of any ".datatype" field. */ + char *stddt; /* Standardized name for a datatype */ + int i,j; /* Loop counters */ + unsigned hash; /* For hashing the name of a type */ + const char *name; /* Name of the parser */ + + /* Allocate and initialize types[] and allocate stddt[] */ + arraysize = lemp->nsymbol * 2; + types = (char**)calloc( arraysize, sizeof(char*) ); + if( types==0 ){ + fprintf(stderr,"Out of memory.\n"); + exit(1); + } + maxdtlength = 0; + if( lemp->vartype ){ + maxdtlength = lemonStrlen(lemp->vartype); + } + for(i=0; insymbol; i++){ + int len; + struct symbol *sp = lemp->symbols[i]; + if( sp->datatype==0 ) continue; + len = lemonStrlen(sp->datatype); + if( len>maxdtlength ) maxdtlength = len; + } + stddt = (char*)malloc( maxdtlength*2 + 1 ); + if( types==0 || stddt==0 ){ + fprintf(stderr,"Out of memory.\n"); + exit(1); + } + + /* Build a hash table of datatypes. The ".dtnum" field of each symbol + ** is filled in with the hash index plus 1. A ".dtnum" value of 0 is + ** used for terminal symbols. If there is no %default_type defined then + ** 0 is also used as the .dtnum value for nonterminals which do not specify + ** a datatype using the %type directive. + */ + for(i=0; insymbol; i++){ + struct symbol *sp = lemp->symbols[i]; + char *cp; + if( sp==lemp->errsym ){ + sp->dtnum = arraysize+1; + continue; + } + if( sp->type!=NONTERMINAL || (sp->datatype==0 && lemp->vartype==0) ){ + sp->dtnum = 0; + continue; + } + cp = sp->datatype; + if( cp==0 ) cp = lemp->vartype; + j = 0; + while( ISSPACE(*cp) ) cp++; + while( *cp ) stddt[j++] = *cp++; + while( j>0 && ISSPACE(stddt[j-1]) ) j--; + stddt[j] = 0; + if( lemp->tokentype && strcmp(stddt, lemp->tokentype)==0 ){ + sp->dtnum = 0; + continue; + } + hash = 0; + for(j=0; stddt[j]; j++){ + hash = hash*53 + stddt[j]; + } + hash = (hash & 0x7fffffff)%arraysize; + while( types[hash] ){ + if( strcmp(types[hash],stddt)==0 ){ + sp->dtnum = hash + 1; + break; + } + hash++; + if( hash>=(unsigned)arraysize ) hash = 0; + } + if( types[hash]==0 ){ + sp->dtnum = hash + 1; + types[hash] = (char*)malloc( lemonStrlen(stddt)+1 ); + if( types[hash]==0 ){ + fprintf(stderr,"Out of memory.\n"); + exit(1); + } + strcpy(types[hash],stddt); + } + } + + /* Print out the definition of YYTOKENTYPE and YYMINORTYPE */ + name = lemp->name ? lemp->name : "Parse"; + lineno = *plineno; + if( mhflag ){ fprintf(out,"#if INTERFACE\n"); lineno++; } + fprintf(out,"#define %sTOKENTYPE %s\n",name, + lemp->tokentype?lemp->tokentype:"void*"); lineno++; + if( mhflag ){ fprintf(out,"#endif\n"); lineno++; } + fprintf(out,"typedef union {\n"); lineno++; + fprintf(out," int yyinit;\n"); lineno++; + fprintf(out," %sTOKENTYPE yy0;\n",name); lineno++; + for(i=0; ierrsym->useCnt ){ + fprintf(out," int yy%d;\n",lemp->errsym->dtnum); lineno++; + } + free(stddt); + free(types); + fprintf(out,"} YYMINORTYPE;\n"); lineno++; + *plineno = lineno; +} + +/* +** Return the name of a C datatype able to represent values between +** lwr and upr, inclusive. If pnByte!=NULL then also write the sizeof +** for that type (1, 2, or 4) into *pnByte. +*/ +static const char *minimum_size_type(int lwr, int upr, int *pnByte){ + const char *zType = "int"; + int nByte = 4; + if( lwr>=0 ){ + if( upr<=255 ){ + zType = "unsigned char"; + nByte = 1; + }else if( upr<65535 ){ + zType = "unsigned short int"; + nByte = 2; + }else{ + zType = "unsigned int"; + nByte = 4; + } + }else if( lwr>=-127 && upr<=127 ){ + zType = "signed char"; + nByte = 1; + }else if( lwr>=-32767 && upr<32767 ){ + zType = "short"; + nByte = 2; + } + if( pnByte ) *pnByte = nByte; + return zType; +} + +/* +** Each state contains a set of token transaction and a set of +** nonterminal transactions. Each of these sets makes an instance +** of the following structure. An array of these structures is used +** to order the creation of entries in the yy_action[] table. +*/ +struct axset { + struct state *stp; /* A pointer to a state */ + int isTkn; /* True to use tokens. False for non-terminals */ + int nAction; /* Number of actions */ + int iOrder; /* Original order of action sets */ +}; + +/* +** Compare to axset structures for sorting purposes +*/ +static int axset_compare(const void *a, const void *b){ + struct axset *p1 = (struct axset*)a; + struct axset *p2 = (struct axset*)b; + int c; + c = p2->nAction - p1->nAction; + if( c==0 ){ + c = p1->iOrder - p2->iOrder; + } + assert( c!=0 || p1==p2 ); + return c; +} + +/* +** Write text on "out" that describes the rule "rp". +*/ +static void writeRuleText(FILE *out, struct rule *rp){ + int j; + fprintf(out,"%s ::=", rp->lhs->name); + for(j=0; jnrhs; j++){ + struct symbol *sp = rp->rhs[j]; + if( sp->type!=MULTITERMINAL ){ + fprintf(out," %s", sp->name); + }else{ + int k; + fprintf(out," %s", sp->subsym[0]->name); + for(k=1; knsubsym; k++){ + fprintf(out,"|%s",sp->subsym[k]->name); + } + } + } +} + + +/* Generate C source code for the parser */ +void ReportTable( + struct lemon *lemp, + int mhflag /* Output in makeheaders format if true */ +){ + FILE *out, *in; + char line[LINESIZE]; + int lineno; + struct state *stp; + struct action *ap; + struct rule *rp; + struct acttab *pActtab; + int i, j, n, sz; + int szActionType; /* sizeof(YYACTIONTYPE) */ + int szCodeType; /* sizeof(YYCODETYPE) */ + const char *name; + int mnTknOfst, mxTknOfst; + int mnNtOfst, mxNtOfst; + struct axset *ax; + + in = tplt_open(lemp); + if( in==0 ) return; + out = file_open(lemp,".c","wb"); + if( out==0 ){ + fclose(in); + return; + } + lineno = 1; + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate the include code, if any */ + tplt_print(out,lemp,lemp->include,&lineno); + if( mhflag ){ + char *name = file_makename(lemp, ".h"); + fprintf(out,"#include \"%s\"\n", name); lineno++; + free(name); + } + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate #defines for all tokens */ + if( mhflag ){ + const char *prefix; + fprintf(out,"#if INTERFACE\n"); lineno++; + if( lemp->tokenprefix ) prefix = lemp->tokenprefix; + else prefix = ""; + for(i=1; interminal; i++){ + fprintf(out,"#define %s%-30s %2d\n",prefix,lemp->symbols[i]->name,i); + lineno++; + } + fprintf(out,"#endif\n"); lineno++; + } + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate the defines */ + fprintf(out,"#define YYCODETYPE %s\n", + minimum_size_type(0, lemp->nsymbol+1, &szCodeType)); lineno++; + fprintf(out,"#define YYNOCODE %d\n",lemp->nsymbol+1); lineno++; + fprintf(out,"#define YYACTIONTYPE %s\n", + minimum_size_type(0,lemp->nstate+lemp->nrule*2+5,&szActionType)); lineno++; + if( lemp->wildcard ){ + fprintf(out,"#define YYWILDCARD %d\n", + lemp->wildcard->index); lineno++; + } + print_stack_union(out,lemp,&lineno,mhflag); + fprintf(out, "#ifndef YYSTACKDEPTH\n"); lineno++; + if( lemp->stacksize ){ + fprintf(out,"#define YYSTACKDEPTH %s\n",lemp->stacksize); lineno++; + }else{ + fprintf(out,"#define YYSTACKDEPTH 100\n"); lineno++; + } + fprintf(out, "#endif\n"); lineno++; + if( mhflag ){ + fprintf(out,"#if INTERFACE\n"); lineno++; + } + name = lemp->name ? lemp->name : "Parse"; + if( lemp->arg && lemp->arg[0] ){ + size_t i; + i = lemonStrlen(lemp->arg); + while( i>=1 && ISSPACE(lemp->arg[i-1]) ) i--; + while( i>=1 && (ISALNUM(lemp->arg[i-1]) || lemp->arg[i-1]=='_') ) i--; + fprintf(out,"#define %sARG_SDECL %s;\n",name,lemp->arg); lineno++; + fprintf(out,"#define %sARG_PDECL ,%s\n",name,lemp->arg); lineno++; + fprintf(out,"#define %sARG_FETCH %s = yypParser->%s\n", + name,lemp->arg,&lemp->arg[i]); lineno++; + fprintf(out,"#define %sARG_STORE yypParser->%s = %s\n", + name,&lemp->arg[i],&lemp->arg[i]); lineno++; + }else{ + fprintf(out,"#define %sARG_SDECL\n",name); lineno++; + fprintf(out,"#define %sARG_PDECL\n",name); lineno++; + fprintf(out,"#define %sARG_FETCH\n",name); lineno++; + fprintf(out,"#define %sARG_STORE\n",name); lineno++; + } + if( mhflag ){ + fprintf(out,"#endif\n"); lineno++; + } + if( lemp->errsym->useCnt ){ + fprintf(out,"#define YYERRORSYMBOL %d\n",lemp->errsym->index); lineno++; + fprintf(out,"#define YYERRSYMDT yy%d\n",lemp->errsym->dtnum); lineno++; + } + if( lemp->has_fallback ){ + fprintf(out,"#define YYFALLBACK 1\n"); lineno++; + } + + /* Compute the action table, but do not output it yet. The action + ** table must be computed before generating the YYNSTATE macro because + ** we need to know how many states can be eliminated. + */ + ax = (struct axset *) calloc(lemp->nxstate*2 , sizeof(ax[0])); + if( ax==0 ){ + fprintf(stderr,"malloc failed\n"); + exit(1); + } + for(i=0; inxstate; i++){ + stp = lemp->sorted[i]; + ax[i*2].stp = stp; + ax[i*2].isTkn = 1; + ax[i*2].nAction = stp->nTknAct; + ax[i*2+1].stp = stp; + ax[i*2+1].isTkn = 0; + ax[i*2+1].nAction = stp->nNtAct; + } + mxTknOfst = mnTknOfst = 0; + mxNtOfst = mnNtOfst = 0; + /* In an effort to minimize the action table size, use the heuristic + ** of placing the largest action sets first */ + for(i=0; inxstate*2; i++) ax[i].iOrder = i; + qsort(ax, lemp->nxstate*2, sizeof(ax[0]), axset_compare); + pActtab = acttab_alloc(); + for(i=0; inxstate*2 && ax[i].nAction>0; i++){ + stp = ax[i].stp; + if( ax[i].isTkn ){ + for(ap=stp->ap; ap; ap=ap->next){ + int action; + if( ap->sp->index>=lemp->nterminal ) continue; + action = compute_action(lemp, ap); + if( action<0 ) continue; + acttab_action(pActtab, ap->sp->index, action); + } + stp->iTknOfst = acttab_insert(pActtab); + if( stp->iTknOfstiTknOfst; + if( stp->iTknOfst>mxTknOfst ) mxTknOfst = stp->iTknOfst; + }else{ + for(ap=stp->ap; ap; ap=ap->next){ + int action; + if( ap->sp->indexnterminal ) continue; + if( ap->sp->index==lemp->nsymbol ) continue; + action = compute_action(lemp, ap); + if( action<0 ) continue; + acttab_action(pActtab, ap->sp->index, action); + } + stp->iNtOfst = acttab_insert(pActtab); + if( stp->iNtOfstiNtOfst; + if( stp->iNtOfst>mxNtOfst ) mxNtOfst = stp->iNtOfst; + } +#if 0 /* Uncomment for a trace of how the yy_action[] table fills out */ + { int jj, nn; + for(jj=nn=0; jjnAction; jj++){ + if( pActtab->aAction[jj].action<0 ) nn++; + } + printf("%4d: State %3d %s n: %2d size: %5d freespace: %d\n", + i, stp->statenum, ax[i].isTkn ? "Token" : "Var ", + ax[i].nAction, pActtab->nAction, nn); + } +#endif + } + free(ax); + +#if 0 + /* Mark rules that are actually used for reduce actions after all + ** optimizations have been applied + */ + for(rp=lemp->rule; rp; rp=rp->next) rp->doesReduce = LEMON_FALSE; + for(i=0; inxstate; i++){ + struct action *ap; + for(ap=lemp->sorted[i]->ap; ap; ap=ap->next){ + if( ap->type==REDUCE || ap->type==SHIFTREDUCE ){ + ap->x.rp->doesReduce = i; + } + } + } +#endif + + /* Finish rendering the constants now that the action table has + ** been computed */ + fprintf(out,"#define YYNSTATE %d\n",lemp->nxstate); lineno++; + fprintf(out,"#define YYNRULE %d\n",lemp->nrule); lineno++; + fprintf(out,"#define YY_MAX_SHIFT %d\n",lemp->nxstate-1); lineno++; + fprintf(out,"#define YY_MIN_SHIFTREDUCE %d\n",lemp->nstate); lineno++; + i = lemp->nstate + lemp->nrule; + fprintf(out,"#define YY_MAX_SHIFTREDUCE %d\n", i-1); lineno++; + fprintf(out,"#define YY_MIN_REDUCE %d\n", i); lineno++; + i = lemp->nstate + lemp->nrule*2; + fprintf(out,"#define YY_MAX_REDUCE %d\n", i-1); lineno++; + fprintf(out,"#define YY_ERROR_ACTION %d\n", i); lineno++; + fprintf(out,"#define YY_ACCEPT_ACTION %d\n", i+1); lineno++; + fprintf(out,"#define YY_NO_ACTION %d\n", i+2); lineno++; + tplt_xfer(lemp->name,in,out,&lineno); + + /* Now output the action table and its associates: + ** + ** yy_action[] A single table containing all actions. + ** yy_lookahead[] A table containing the lookahead for each entry in + ** yy_action. Used to detect hash collisions. + ** yy_shift_ofst[] For each state, the offset into yy_action for + ** shifting terminals. + ** yy_reduce_ofst[] For each state, the offset into yy_action for + ** shifting non-terminals after a reduce. + ** yy_default[] Default action for each state. + */ + + /* Output the yy_action table */ + lemp->nactiontab = n = acttab_size(pActtab); + lemp->tablesize += n*szActionType; + fprintf(out,"#define YY_ACTTAB_COUNT (%d)\n", n); lineno++; + fprintf(out,"static const YYACTIONTYPE yy_action[] = {\n"); lineno++; + for(i=j=0; instate + lemp->nrule + 2; + if( j==0 ) fprintf(out," /* %5d */ ", i); + fprintf(out, " %4d,", action); + if( j==9 || i==n-1 ){ + fprintf(out, "\n"); lineno++; + j = 0; + }else{ + j++; + } + } + fprintf(out, "};\n"); lineno++; + + /* Output the yy_lookahead table */ + lemp->tablesize += n*szCodeType; + fprintf(out,"static const YYCODETYPE yy_lookahead[] = {\n"); lineno++; + for(i=j=0; insymbol; + if( j==0 ) fprintf(out," /* %5d */ ", i); + fprintf(out, " %4d,", la); + if( j==9 || i==n-1 ){ + fprintf(out, "\n"); lineno++; + j = 0; + }else{ + j++; + } + } + fprintf(out, "};\n"); lineno++; + + /* Output the yy_shift_ofst[] table */ + n = lemp->nxstate; + while( n>0 && lemp->sorted[n-1]->iTknOfst==NO_OFFSET ) n--; + fprintf(out, "#define YY_SHIFT_USE_DFLT (%d)\n", lemp->nactiontab); lineno++; + fprintf(out, "#define YY_SHIFT_COUNT (%d)\n", n-1); lineno++; + fprintf(out, "#define YY_SHIFT_MIN (%d)\n", mnTknOfst); lineno++; + fprintf(out, "#define YY_SHIFT_MAX (%d)\n", mxTknOfst); lineno++; + fprintf(out, "static const %s yy_shift_ofst[] = {\n", + minimum_size_type(mnTknOfst, lemp->nterminal+lemp->nactiontab, &sz)); + lineno++; + lemp->tablesize += n*sz; + for(i=j=0; isorted[i]; + ofst = stp->iTknOfst; + if( ofst==NO_OFFSET ) ofst = lemp->nactiontab; + if( j==0 ) fprintf(out," /* %5d */ ", i); + fprintf(out, " %4d,", ofst); + if( j==9 || i==n-1 ){ + fprintf(out, "\n"); lineno++; + j = 0; + }else{ + j++; + } + } + fprintf(out, "};\n"); lineno++; + + /* Output the yy_reduce_ofst[] table */ + fprintf(out, "#define YY_REDUCE_USE_DFLT (%d)\n", mnNtOfst-1); lineno++; + n = lemp->nxstate; + while( n>0 && lemp->sorted[n-1]->iNtOfst==NO_OFFSET ) n--; + fprintf(out, "#define YY_REDUCE_COUNT (%d)\n", n-1); lineno++; + fprintf(out, "#define YY_REDUCE_MIN (%d)\n", mnNtOfst); lineno++; + fprintf(out, "#define YY_REDUCE_MAX (%d)\n", mxNtOfst); lineno++; + fprintf(out, "static const %s yy_reduce_ofst[] = {\n", + minimum_size_type(mnNtOfst-1, mxNtOfst, &sz)); lineno++; + lemp->tablesize += n*sz; + for(i=j=0; isorted[i]; + ofst = stp->iNtOfst; + if( ofst==NO_OFFSET ) ofst = mnNtOfst - 1; + if( j==0 ) fprintf(out," /* %5d */ ", i); + fprintf(out, " %4d,", ofst); + if( j==9 || i==n-1 ){ + fprintf(out, "\n"); lineno++; + j = 0; + }else{ + j++; + } + } + fprintf(out, "};\n"); lineno++; + + /* Output the default action table */ + fprintf(out, "static const YYACTIONTYPE yy_default[] = {\n"); lineno++; + n = lemp->nxstate; + lemp->tablesize += n*szActionType; + for(i=j=0; isorted[i]; + if( j==0 ) fprintf(out," /* %5d */ ", i); + fprintf(out, " %4d,", stp->iDfltReduce+lemp->nstate+lemp->nrule); + if( j==9 || i==n-1 ){ + fprintf(out, "\n"); lineno++; + j = 0; + }else{ + j++; + } + } + fprintf(out, "};\n"); lineno++; + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate the table of fallback tokens. + */ + if( lemp->has_fallback ){ + int mx = lemp->nterminal - 1; + while( mx>0 && lemp->symbols[mx]->fallback==0 ){ mx--; } + lemp->tablesize += (mx+1)*szCodeType; + for(i=0; i<=mx; i++){ + struct symbol *p = lemp->symbols[i]; + if( p->fallback==0 ){ + fprintf(out, " 0, /* %10s => nothing */\n", p->name); + }else{ + fprintf(out, " %3d, /* %10s => %s */\n", p->fallback->index, + p->name, p->fallback->name); + } + lineno++; + } + } + tplt_xfer(lemp->name, in, out, &lineno); + + /* Generate a table containing the symbolic name of every symbol + */ + for(i=0; insymbol; i++){ + sprintf(line,"\"%s\",",lemp->symbols[i]->name); + fprintf(out," %-15s",line); + if( (i&3)==3 ){ fprintf(out,"\n"); lineno++; } + } + if( (i&3)!=0 ){ fprintf(out,"\n"); lineno++; } + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate a table containing a text string that describes every + ** rule in the rule set of the grammar. This information is used + ** when tracing REDUCE actions. + */ + for(i=0, rp=lemp->rule; rp; rp=rp->next, i++){ + assert( rp->iRule==i ); + fprintf(out," /* %3d */ \"", i); + writeRuleText(out, rp); + fprintf(out,"\",\n"); lineno++; + } + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate code which executes every time a symbol is popped from + ** the stack while processing errors or while destroying the parser. + ** (In other words, generate the %destructor actions) + */ + if( lemp->tokendest ){ + int once = 1; + for(i=0; insymbol; i++){ + struct symbol *sp = lemp->symbols[i]; + if( sp==0 || sp->type!=TERMINAL ) continue; + if( once ){ + fprintf(out, " /* TERMINAL Destructor */\n"); lineno++; + once = 0; + } + fprintf(out," case %d: /* %s */\n", sp->index, sp->name); lineno++; + } + for(i=0; insymbol && lemp->symbols[i]->type!=TERMINAL; i++); + if( insymbol ){ + emit_destructor_code(out,lemp->symbols[i],lemp,&lineno); + fprintf(out," break;\n"); lineno++; + } + } + if( lemp->vardest ){ + struct symbol *dflt_sp = 0; + int once = 1; + for(i=0; insymbol; i++){ + struct symbol *sp = lemp->symbols[i]; + if( sp==0 || sp->type==TERMINAL || + sp->index<=0 || sp->destructor!=0 ) continue; + if( once ){ + fprintf(out, " /* Default NON-TERMINAL Destructor */\n"); lineno++; + once = 0; + } + fprintf(out," case %d: /* %s */\n", sp->index, sp->name); lineno++; + dflt_sp = sp; + } + if( dflt_sp!=0 ){ + emit_destructor_code(out,dflt_sp,lemp,&lineno); + } + fprintf(out," break;\n"); lineno++; + } + for(i=0; insymbol; i++){ + struct symbol *sp = lemp->symbols[i]; + if( sp==0 || sp->type==TERMINAL || sp->destructor==0 ) continue; + if( sp->destLineno<0 ) continue; /* Already emitted */ + fprintf(out," case %d: /* %s */\n", sp->index, sp->name); lineno++; + + /* Combine duplicate destructors into a single case */ + for(j=i+1; jnsymbol; j++){ + struct symbol *sp2 = lemp->symbols[j]; + if( sp2 && sp2->type!=TERMINAL && sp2->destructor + && sp2->dtnum==sp->dtnum + && strcmp(sp->destructor,sp2->destructor)==0 ){ + fprintf(out," case %d: /* %s */\n", + sp2->index, sp2->name); lineno++; + sp2->destLineno = -1; /* Avoid emitting this destructor again */ + } + } + + emit_destructor_code(out,lemp->symbols[i],lemp,&lineno); + fprintf(out," break;\n"); lineno++; + } + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate code which executes whenever the parser stack overflows */ + tplt_print(out,lemp,lemp->overflow,&lineno); + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate the table of rule information + ** + ** Note: This code depends on the fact that rules are number + ** sequentually beginning with 0. + */ + for(rp=lemp->rule; rp; rp=rp->next){ + fprintf(out," { %d, %d },\n",rp->lhs->index,rp->nrhs); lineno++; + } + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate code which executes during each REDUCE action */ + i = 0; + for(rp=lemp->rule; rp; rp=rp->next){ + i += translate_code(lemp, rp); + } + if( i ){ + fprintf(out," YYMINORTYPE yylhsminor;\n"); lineno++; + } + /* First output rules other than the default: rule */ + for(rp=lemp->rule; rp; rp=rp->next){ + struct rule *rp2; /* Other rules with the same action */ + if( rp->codeEmitted ) continue; + if( rp->noCode ){ + /* No C code actions, so this will be part of the "default:" rule */ + continue; + } + fprintf(out," case %d: /* ",rp->iRule); + writeRuleText(out, rp); + fprintf(out," */\n"); lineno++; + for(rp2=rp->next; rp2; rp2=rp2->next){ + if( rp2->code==rp->code && rp2->codePrefix==rp->codePrefix + && rp2->codeSuffix == rp->codeSuffix ){ + fprintf(out," case %d: /*",rp2->iRule); + writeRuleText(out, rp2); + fprintf(out, " */ yytestcase(yyruleno==%d);\n", rp2->iRule); lineno++; + rp2->codeEmitted = 1; + } + } + emit_code(out,rp,lemp,&lineno); + fprintf(out," break;\n"); lineno++; + rp->codeEmitted = 1; + } + /* Finally, output the default: rule. We choose as the default: all + ** empty actions. */ + fprintf(out," default:\n"); lineno++; + for(rp=lemp->rule; rp; rp=rp->next){ + if( rp->codeEmitted ) continue; + assert( rp->noCode ); + fprintf(out," /* (%d) ", rp->iRule); + writeRuleText(out, rp); +#if 0 + if( rp->doesReduce ){ +#endif + fprintf(out, " */ yytestcase(yyruleno==%d);\n", rp->iRule); lineno++; +#if 0 + }else{ + fprintf(out, " (OPTIMIZED OUT) */ assert(yyruleno!=%d);\n", + rp->iRule); lineno++; + } +#endif + } + fprintf(out," break;\n"); lineno++; + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate code which executes if a parse fails */ + tplt_print(out,lemp,lemp->failure,&lineno); + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate code which executes when a syntax error occurs */ + tplt_print(out,lemp,lemp->error,&lineno); + tplt_xfer(lemp->name,in,out,&lineno); + + /* Generate code which executes when the parser accepts its input */ + tplt_print(out,lemp,lemp->accept,&lineno); + tplt_xfer(lemp->name,in,out,&lineno); + + /* Append any addition code the user desires */ + tplt_print(out,lemp,lemp->extracode,&lineno); + + acttab_free(&pActtab); + fclose(in); + fclose(out); + return; +} + +/* Generate a header file for the parser */ +void ReportHeader(struct lemon *lemp) +{ + FILE *out, *in; + const char *prefix; + char line[LINESIZE]; + char pattern[LINESIZE]; + int i; + + if( lemp->tokenprefix ) prefix = lemp->tokenprefix; + else prefix = ""; + in = file_open(lemp,".h","rb"); + if( in ){ + int nextChar; + for(i=1; interminal && fgets(line,LINESIZE,in); i++){ + sprintf(pattern,"#define %s%-30s %2d\n", + prefix,lemp->symbols[i]->name,i); + if( strcmp(line,pattern) ) break; + } + nextChar = fgetc(in); + fclose(in); + if( i==lemp->nterminal && nextChar==EOF ){ + /* No change in the file. Don't rewrite it. */ + /* (not the best idea if you use make tools that check the date! */ + /*return;*/ + } + } + out = file_open(lemp,".h","wb"); + if( out ){ + for(i=1; interminal; i++){ + fprintf(out,"#define %s%-30s %3d\n",prefix,lemp->symbols[i]->name,i); + } + fclose(out); + } + return; +} + +/* Reduce the size of the action tables, if possible, by making use +** of defaults. +** +** In this version, we take the most frequent REDUCE action and make +** it the default. Except, there is no default if the wildcard token +** is a possible look-ahead. +*/ +void CompressTables(struct lemon *lemp) +{ + struct state *stp; + struct action *ap, *ap2, *nextap; + struct rule *rp, *rp2, *rbest; + int nbest, n; + int i; + int usesWildcard; + + for(i=0; instate; i++){ + stp = lemp->sorted[i]; + nbest = 0; + rbest = 0; + usesWildcard = 0; + + for(ap=stp->ap; ap; ap=ap->next){ + if( ap->type==SHIFT && ap->sp==lemp->wildcard ){ + usesWildcard = 1; + } + if( ap->type!=REDUCE ) continue; + rp = ap->x.rp; + if( rp->lhsStart ) continue; + if( rp==rbest ) continue; + n = 1; + for(ap2=ap->next; ap2; ap2=ap2->next){ + if( ap2->type!=REDUCE ) continue; + rp2 = ap2->x.rp; + if( rp2==rbest ) continue; + if( rp2==rp ) n++; + } + if( n>nbest ){ + nbest = n; + rbest = rp; + } + } + + /* Do not make a default if the number of rules to default + ** is not at least 1 or if the wildcard token is a possbile + ** lookahead. + */ + if( nbest<1 || usesWildcard ) continue; + + + /* Combine matching REDUCE actions into a single default */ + for(ap=stp->ap; ap; ap=ap->next){ + if( ap->type==REDUCE && ap->x.rp==rbest ) break; + } + assert( ap ); + ap->sp = Symbol_new("{default}"); + for(ap=ap->next; ap; ap=ap->next){ + if( ap->type==REDUCE && ap->x.rp==rbest ) ap->type = NOT_USED; + } + stp->ap = Action_sort(stp->ap); + + for(ap=stp->ap; ap; ap=ap->next){ + if( ap->type==SHIFT ) break; + if( ap->type==REDUCE && ap->x.rp!=rbest ) break; + } + if( ap==0 ){ + stp->autoReduce = 1; + stp->pDfltReduce = rbest; + } + } + + /* Make a second pass over all states and actions. Convert + ** every action that is a SHIFT to an autoReduce state into + ** a SHIFTREDUCE action. + */ + for(i=0; instate; i++){ + stp = lemp->sorted[i]; + for(ap=stp->ap; ap; ap=ap->next){ + struct state *pNextState; + if( ap->type!=SHIFT ) continue; + pNextState = ap->x.stp; + if( pNextState->autoReduce && pNextState->pDfltReduce!=0 ){ + ap->type = SHIFTREDUCE; + ap->x.rp = pNextState->pDfltReduce; + } + } + } + + /* If a SHIFTREDUCE action specifies a rule that has a single RHS term + ** (meaning that the SHIFTREDUCE will land back in the state where it + ** started) and if there is no C-code associated with the reduce action, + ** then we can go ahead and convert the action to be the same as the + ** action for the RHS of the rule. + */ + for(i=0; instate; i++){ + stp = lemp->sorted[i]; + for(ap=stp->ap; ap; ap=nextap){ + nextap = ap->next; + if( ap->type!=SHIFTREDUCE ) continue; + rp = ap->x.rp; + if( rp->noCode==0 ) continue; + if( rp->nrhs!=1 ) continue; +#if 1 + /* Only apply this optimization to non-terminals. It would be OK to + ** apply it to terminal symbols too, but that makes the parser tables + ** larger. */ + if( ap->sp->indexnterminal ) continue; +#endif + /* If we reach this point, it means the optimization can be applied */ + nextap = ap; + for(ap2=stp->ap; ap2 && (ap2==ap || ap2->sp!=rp->lhs); ap2=ap2->next){} + assert( ap2!=0 ); + ap->spOpt = ap2->sp; + ap->type = ap2->type; + ap->x = ap2->x; + } + } +} + + +/* +** Compare two states for sorting purposes. The smaller state is the +** one with the most non-terminal actions. If they have the same number +** of non-terminal actions, then the smaller is the one with the most +** token actions. +*/ +static int stateResortCompare(const void *a, const void *b){ + const struct state *pA = *(const struct state**)a; + const struct state *pB = *(const struct state**)b; + int n; + + n = pB->nNtAct - pA->nNtAct; + if( n==0 ){ + n = pB->nTknAct - pA->nTknAct; + if( n==0 ){ + n = pB->statenum - pA->statenum; + } + } + assert( n!=0 ); + return n; +} + + +/* +** Renumber and resort states so that states with fewer choices +** occur at the end. Except, keep state 0 as the first state. +*/ +void ResortStates(struct lemon *lemp) +{ + int i; + struct state *stp; + struct action *ap; + + for(i=0; instate; i++){ + stp = lemp->sorted[i]; + stp->nTknAct = stp->nNtAct = 0; + stp->iDfltReduce = lemp->nrule; /* Init dflt action to "syntax error" */ + stp->iTknOfst = NO_OFFSET; + stp->iNtOfst = NO_OFFSET; + for(ap=stp->ap; ap; ap=ap->next){ + int iAction = compute_action(lemp,ap); + if( iAction>=0 ){ + if( ap->sp->indexnterminal ){ + stp->nTknAct++; + }else if( ap->sp->indexnsymbol ){ + stp->nNtAct++; + }else{ + assert( stp->autoReduce==0 || stp->pDfltReduce==ap->x.rp ); + stp->iDfltReduce = iAction - lemp->nstate - lemp->nrule; + } + } + } + } + qsort(&lemp->sorted[1], lemp->nstate-1, sizeof(lemp->sorted[0]), + stateResortCompare); + for(i=0; instate; i++){ + lemp->sorted[i]->statenum = i; + } + lemp->nxstate = lemp->nstate; + while( lemp->nxstate>1 && lemp->sorted[lemp->nxstate-1]->autoReduce ){ + lemp->nxstate--; + } +} + + +/***************** From the file "set.c" ************************************/ +/* +** Set manipulation routines for the LEMON parser generator. +*/ + +static int size = 0; + +/* Set the set size */ +void SetSize(int n) +{ + size = n+1; +} + +/* Allocate a new set */ +char *SetNew(){ + char *s; + s = (char*)calloc( size, 1); + if( s==0 ){ + extern void memory_error(); + memory_error(); + } + return s; +} + +/* Deallocate a set */ +void SetFree(char *s) +{ + free(s); +} + +/* Add a new element to the set. Return TRUE if the element was added +** and FALSE if it was already there. */ +int SetAdd(char *s, int e) +{ + int rv; + assert( e>=0 && esize = 1024; + x1a->count = 0; + x1a->tbl = (x1node*)calloc(1024, sizeof(x1node) + sizeof(x1node*)); + if( x1a->tbl==0 ){ + free(x1a); + x1a = 0; + }else{ + int i; + x1a->ht = (x1node**)&(x1a->tbl[1024]); + for(i=0; i<1024; i++) x1a->ht[i] = 0; + } + } +} +/* Insert a new record into the array. Return TRUE if successful. +** Prior data with the same key is NOT overwritten */ +int Strsafe_insert(const char *data) +{ + x1node *np; + unsigned h; + unsigned ph; + + if( x1a==0 ) return 0; + ph = strhash(data); + h = ph & (x1a->size-1); + np = x1a->ht[h]; + while( np ){ + if( strcmp(np->data,data)==0 ){ + /* An existing entry with the same key is found. */ + /* Fail because overwrite is not allows. */ + return 0; + } + np = np->next; + } + if( x1a->count>=x1a->size ){ + /* Need to make the hash table bigger */ + int i,size; + struct s_x1 array; + array.size = size = x1a->size*2; + array.count = x1a->count; + array.tbl = (x1node*)calloc(size, sizeof(x1node) + sizeof(x1node*)); + if( array.tbl==0 ) return 0; /* Fail due to malloc failure */ + array.ht = (x1node**)&(array.tbl[size]); + for(i=0; icount; i++){ + x1node *oldnp, *newnp; + oldnp = &(x1a->tbl[i]); + h = strhash(oldnp->data) & (size-1); + newnp = &(array.tbl[i]); + if( array.ht[h] ) array.ht[h]->from = &(newnp->next); + newnp->next = array.ht[h]; + newnp->data = oldnp->data; + newnp->from = &(array.ht[h]); + array.ht[h] = newnp; + } + free(x1a->tbl); + *x1a = array; + } + /* Insert the new data */ + h = ph & (x1a->size-1); + np = &(x1a->tbl[x1a->count++]); + np->data = data; + if( x1a->ht[h] ) x1a->ht[h]->from = &(np->next); + np->next = x1a->ht[h]; + x1a->ht[h] = np; + np->from = &(x1a->ht[h]); + return 1; +} + +/* Return a pointer to data assigned to the given key. Return NULL +** if no such key. */ +const char *Strsafe_find(const char *key) +{ + unsigned h; + x1node *np; + + if( x1a==0 ) return 0; + h = strhash(key) & (x1a->size-1); + np = x1a->ht[h]; + while( np ){ + if( strcmp(np->data,key)==0 ) break; + np = np->next; + } + return np ? np->data : 0; +} + +/* Return a pointer to the (terminal or nonterminal) symbol "x". +** Create a new symbol if this is the first time "x" has been seen. +*/ +struct symbol *Symbol_new(const char *x) +{ + struct symbol *sp; + + sp = Symbol_find(x); + if( sp==0 ){ + sp = (struct symbol *)calloc(1, sizeof(struct symbol) ); + MemoryCheck(sp); + sp->name = Strsafe(x); + sp->type = ISUPPER(*x) ? TERMINAL : NONTERMINAL; + sp->rule = 0; + sp->fallback = 0; + sp->prec = -1; + sp->assoc = UNK; + sp->firstset = 0; + sp->lambda = LEMON_FALSE; + sp->destructor = 0; + sp->destLineno = 0; + sp->datatype = 0; + sp->useCnt = 0; + Symbol_insert(sp,sp->name); + } + sp->useCnt++; + return sp; +} + +/* Compare two symbols for sorting purposes. Return negative, +** zero, or positive if a is less then, equal to, or greater +** than b. +** +** Symbols that begin with upper case letters (terminals or tokens) +** must sort before symbols that begin with lower case letters +** (non-terminals). And MULTITERMINAL symbols (created using the +** %token_class directive) must sort at the very end. Other than +** that, the order does not matter. +** +** We find experimentally that leaving the symbols in their original +** order (the order they appeared in the grammar file) gives the +** smallest parser tables in SQLite. +*/ +int Symbolcmpp(const void *_a, const void *_b) +{ + const struct symbol *a = *(const struct symbol **) _a; + const struct symbol *b = *(const struct symbol **) _b; + int i1 = a->type==MULTITERMINAL ? 3 : a->name[0]>'Z' ? 2 : 1; + int i2 = b->type==MULTITERMINAL ? 3 : b->name[0]>'Z' ? 2 : 1; + return i1==i2 ? a->index - b->index : i1 - i2; +} + +/* There is one instance of the following structure for each +** associative array of type "x2". +*/ +struct s_x2 { + int size; /* The number of available slots. */ + /* Must be a power of 2 greater than or */ + /* equal to 1 */ + int count; /* Number of currently slots filled */ + struct s_x2node *tbl; /* The data stored here */ + struct s_x2node **ht; /* Hash table for lookups */ +}; + +/* There is one instance of this structure for every data element +** in an associative array of type "x2". +*/ +typedef struct s_x2node { + struct symbol *data; /* The data */ + const char *key; /* The key */ + struct s_x2node *next; /* Next entry with the same hash */ + struct s_x2node **from; /* Previous link */ +} x2node; + +/* There is only one instance of the array, which is the following */ +static struct s_x2 *x2a; + +/* Allocate a new associative array */ +void Symbol_init(){ + if( x2a ) return; + x2a = (struct s_x2*)malloc( sizeof(struct s_x2) ); + if( x2a ){ + x2a->size = 128; + x2a->count = 0; + x2a->tbl = (x2node*)calloc(128, sizeof(x2node) + sizeof(x2node*)); + if( x2a->tbl==0 ){ + free(x2a); + x2a = 0; + }else{ + int i; + x2a->ht = (x2node**)&(x2a->tbl[128]); + for(i=0; i<128; i++) x2a->ht[i] = 0; + } + } +} +/* Insert a new record into the array. Return TRUE if successful. +** Prior data with the same key is NOT overwritten */ +int Symbol_insert(struct symbol *data, const char *key) +{ + x2node *np; + unsigned h; + unsigned ph; + + if( x2a==0 ) return 0; + ph = strhash(key); + h = ph & (x2a->size-1); + np = x2a->ht[h]; + while( np ){ + if( strcmp(np->key,key)==0 ){ + /* An existing entry with the same key is found. */ + /* Fail because overwrite is not allows. */ + return 0; + } + np = np->next; + } + if( x2a->count>=x2a->size ){ + /* Need to make the hash table bigger */ + int i,size; + struct s_x2 array; + array.size = size = x2a->size*2; + array.count = x2a->count; + array.tbl = (x2node*)calloc(size, sizeof(x2node) + sizeof(x2node*)); + if( array.tbl==0 ) return 0; /* Fail due to malloc failure */ + array.ht = (x2node**)&(array.tbl[size]); + for(i=0; icount; i++){ + x2node *oldnp, *newnp; + oldnp = &(x2a->tbl[i]); + h = strhash(oldnp->key) & (size-1); + newnp = &(array.tbl[i]); + if( array.ht[h] ) array.ht[h]->from = &(newnp->next); + newnp->next = array.ht[h]; + newnp->key = oldnp->key; + newnp->data = oldnp->data; + newnp->from = &(array.ht[h]); + array.ht[h] = newnp; + } + free(x2a->tbl); + *x2a = array; + } + /* Insert the new data */ + h = ph & (x2a->size-1); + np = &(x2a->tbl[x2a->count++]); + np->key = key; + np->data = data; + if( x2a->ht[h] ) x2a->ht[h]->from = &(np->next); + np->next = x2a->ht[h]; + x2a->ht[h] = np; + np->from = &(x2a->ht[h]); + return 1; +} + +/* Return a pointer to data assigned to the given key. Return NULL +** if no such key. */ +struct symbol *Symbol_find(const char *key) +{ + unsigned h; + x2node *np; + + if( x2a==0 ) return 0; + h = strhash(key) & (x2a->size-1); + np = x2a->ht[h]; + while( np ){ + if( strcmp(np->key,key)==0 ) break; + np = np->next; + } + return np ? np->data : 0; +} + +/* Return the n-th data. Return NULL if n is out of range. */ +struct symbol *Symbol_Nth(int n) +{ + struct symbol *data; + if( x2a && n>0 && n<=x2a->count ){ + data = x2a->tbl[n-1].data; + }else{ + data = 0; + } + return data; +} + +/* Return the size of the array */ +int Symbol_count() +{ + return x2a ? x2a->count : 0; +} + +/* Return an array of pointers to all data in the table. +** The array is obtained from malloc. Return NULL if memory allocation +** problems, or if the array is empty. */ +struct symbol **Symbol_arrayof() +{ + struct symbol **array; + int i,size; + if( x2a==0 ) return 0; + size = x2a->count; + array = (struct symbol **)calloc(size, sizeof(struct symbol *)); + if( array ){ + for(i=0; itbl[i].data; + } + return array; +} + +/* Compare two configurations */ +int Configcmp(const char *_a,const char *_b) +{ + const struct config *a = (struct config *) _a; + const struct config *b = (struct config *) _b; + int x; + x = a->rp->index - b->rp->index; + if( x==0 ) x = a->dot - b->dot; + return x; +} + +/* Compare two states */ +PRIVATE int statecmp(struct config *a, struct config *b) +{ + int rc; + for(rc=0; rc==0 && a && b; a=a->bp, b=b->bp){ + rc = a->rp->index - b->rp->index; + if( rc==0 ) rc = a->dot - b->dot; + } + if( rc==0 ){ + if( a ) rc = 1; + if( b ) rc = -1; + } + return rc; +} + +/* Hash a state */ +PRIVATE unsigned statehash(struct config *a) +{ + unsigned h=0; + while( a ){ + h = h*571 + a->rp->index*37 + a->dot; + a = a->bp; + } + return h; +} + +/* Allocate a new state structure */ +struct state *State_new() +{ + struct state *newstate; + newstate = (struct state *)calloc(1, sizeof(struct state) ); + MemoryCheck(newstate); + return newstate; +} + +/* There is one instance of the following structure for each +** associative array of type "x3". +*/ +struct s_x3 { + int size; /* The number of available slots. */ + /* Must be a power of 2 greater than or */ + /* equal to 1 */ + int count; /* Number of currently slots filled */ + struct s_x3node *tbl; /* The data stored here */ + struct s_x3node **ht; /* Hash table for lookups */ +}; + +/* There is one instance of this structure for every data element +** in an associative array of type "x3". +*/ +typedef struct s_x3node { + struct state *data; /* The data */ + struct config *key; /* The key */ + struct s_x3node *next; /* Next entry with the same hash */ + struct s_x3node **from; /* Previous link */ +} x3node; + +/* There is only one instance of the array, which is the following */ +static struct s_x3 *x3a; + +/* Allocate a new associative array */ +void State_init(){ + if( x3a ) return; + x3a = (struct s_x3*)malloc( sizeof(struct s_x3) ); + if( x3a ){ + x3a->size = 128; + x3a->count = 0; + x3a->tbl = (x3node*)calloc(128, sizeof(x3node) + sizeof(x3node*)); + if( x3a->tbl==0 ){ + free(x3a); + x3a = 0; + }else{ + int i; + x3a->ht = (x3node**)&(x3a->tbl[128]); + for(i=0; i<128; i++) x3a->ht[i] = 0; + } + } +} +/* Insert a new record into the array. Return TRUE if successful. +** Prior data with the same key is NOT overwritten */ +int State_insert(struct state *data, struct config *key) +{ + x3node *np; + unsigned h; + unsigned ph; + + if( x3a==0 ) return 0; + ph = statehash(key); + h = ph & (x3a->size-1); + np = x3a->ht[h]; + while( np ){ + if( statecmp(np->key,key)==0 ){ + /* An existing entry with the same key is found. */ + /* Fail because overwrite is not allows. */ + return 0; + } + np = np->next; + } + if( x3a->count>=x3a->size ){ + /* Need to make the hash table bigger */ + int i,size; + struct s_x3 array; + array.size = size = x3a->size*2; + array.count = x3a->count; + array.tbl = (x3node*)calloc(size, sizeof(x3node) + sizeof(x3node*)); + if( array.tbl==0 ) return 0; /* Fail due to malloc failure */ + array.ht = (x3node**)&(array.tbl[size]); + for(i=0; icount; i++){ + x3node *oldnp, *newnp; + oldnp = &(x3a->tbl[i]); + h = statehash(oldnp->key) & (size-1); + newnp = &(array.tbl[i]); + if( array.ht[h] ) array.ht[h]->from = &(newnp->next); + newnp->next = array.ht[h]; + newnp->key = oldnp->key; + newnp->data = oldnp->data; + newnp->from = &(array.ht[h]); + array.ht[h] = newnp; + } + free(x3a->tbl); + *x3a = array; + } + /* Insert the new data */ + h = ph & (x3a->size-1); + np = &(x3a->tbl[x3a->count++]); + np->key = key; + np->data = data; + if( x3a->ht[h] ) x3a->ht[h]->from = &(np->next); + np->next = x3a->ht[h]; + x3a->ht[h] = np; + np->from = &(x3a->ht[h]); + return 1; +} + +/* Return a pointer to data assigned to the given key. Return NULL +** if no such key. */ +struct state *State_find(struct config *key) +{ + unsigned h; + x3node *np; + + if( x3a==0 ) return 0; + h = statehash(key) & (x3a->size-1); + np = x3a->ht[h]; + while( np ){ + if( statecmp(np->key,key)==0 ) break; + np = np->next; + } + return np ? np->data : 0; +} + +/* Return an array of pointers to all data in the table. +** The array is obtained from malloc. Return NULL if memory allocation +** problems, or if the array is empty. */ +struct state **State_arrayof() +{ + struct state **array; + int i,size; + if( x3a==0 ) return 0; + size = x3a->count; + array = (struct state **)calloc(size, sizeof(struct state *)); + if( array ){ + for(i=0; itbl[i].data; + } + return array; +} + +/* Hash a configuration */ +PRIVATE unsigned confighash(struct config *a) +{ + unsigned h=0; + h = h*571 + a->rp->index*37 + a->dot; + return h; +} + +/* There is one instance of the following structure for each +** associative array of type "x4". +*/ +struct s_x4 { + int size; /* The number of available slots. */ + /* Must be a power of 2 greater than or */ + /* equal to 1 */ + int count; /* Number of currently slots filled */ + struct s_x4node *tbl; /* The data stored here */ + struct s_x4node **ht; /* Hash table for lookups */ +}; + +/* There is one instance of this structure for every data element +** in an associative array of type "x4". +*/ +typedef struct s_x4node { + struct config *data; /* The data */ + struct s_x4node *next; /* Next entry with the same hash */ + struct s_x4node **from; /* Previous link */ +} x4node; + +/* There is only one instance of the array, which is the following */ +static struct s_x4 *x4a; + +/* Allocate a new associative array */ +void Configtable_init(){ + if( x4a ) return; + x4a = (struct s_x4*)malloc( sizeof(struct s_x4) ); + if( x4a ){ + x4a->size = 64; + x4a->count = 0; + x4a->tbl = (x4node*)calloc(64, sizeof(x4node) + sizeof(x4node*)); + if( x4a->tbl==0 ){ + free(x4a); + x4a = 0; + }else{ + int i; + x4a->ht = (x4node**)&(x4a->tbl[64]); + for(i=0; i<64; i++) x4a->ht[i] = 0; + } + } +} +/* Insert a new record into the array. Return TRUE if successful. +** Prior data with the same key is NOT overwritten */ +int Configtable_insert(struct config *data) +{ + x4node *np; + unsigned h; + unsigned ph; + + if( x4a==0 ) return 0; + ph = confighash(data); + h = ph & (x4a->size-1); + np = x4a->ht[h]; + while( np ){ + if( Configcmp((const char *) np->data,(const char *) data)==0 ){ + /* An existing entry with the same key is found. */ + /* Fail because overwrite is not allows. */ + return 0; + } + np = np->next; + } + if( x4a->count>=x4a->size ){ + /* Need to make the hash table bigger */ + int i,size; + struct s_x4 array; + array.size = size = x4a->size*2; + array.count = x4a->count; + array.tbl = (x4node*)calloc(size, sizeof(x4node) + sizeof(x4node*)); + if( array.tbl==0 ) return 0; /* Fail due to malloc failure */ + array.ht = (x4node**)&(array.tbl[size]); + for(i=0; icount; i++){ + x4node *oldnp, *newnp; + oldnp = &(x4a->tbl[i]); + h = confighash(oldnp->data) & (size-1); + newnp = &(array.tbl[i]); + if( array.ht[h] ) array.ht[h]->from = &(newnp->next); + newnp->next = array.ht[h]; + newnp->data = oldnp->data; + newnp->from = &(array.ht[h]); + array.ht[h] = newnp; + } + free(x4a->tbl); + *x4a = array; + } + /* Insert the new data */ + h = ph & (x4a->size-1); + np = &(x4a->tbl[x4a->count++]); + np->data = data; + if( x4a->ht[h] ) x4a->ht[h]->from = &(np->next); + np->next = x4a->ht[h]; + x4a->ht[h] = np; + np->from = &(x4a->ht[h]); + return 1; +} + +/* Return a pointer to data assigned to the given key. Return NULL +** if no such key. */ +struct config *Configtable_find(struct config *key) +{ + int h; + x4node *np; + + if( x4a==0 ) return 0; + h = confighash(key) & (x4a->size-1); + np = x4a->ht[h]; + while( np ){ + if( Configcmp((const char *) np->data,(const char *) key)==0 ) break; + np = np->next; + } + return np ? np->data : 0; +} + +/* Remove all data from the table. Pass each data to the function "f" +** as it is removed. ("f" may be null to avoid this step.) */ +void Configtable_clear(int(*f)(struct config *)) +{ + int i; + if( x4a==0 || x4a->count==0 ) return; + if( f ) for(i=0; icount; i++) (*f)(x4a->tbl[i].data); + for(i=0; isize; i++) x4a->ht[i] = 0; + x4a->count = 0; + return; +} diff --git a/tools/lemon/lemon.html b/tools/lemon/lemon.html new file mode 100644 index 000000000..9b4648f46 --- /dev/null +++ b/tools/lemon/lemon.html @@ -0,0 +1,861 @@ + + +The Lemon Parser Generator + + +

The Lemon Parser Generator

+ +

Lemon is an LALR(1) parser generator for C or C++. +It does the same job as ``bison'' and ``yacc''. +But lemon is not another bison or yacc clone. It +uses a different grammar syntax which is designed to +reduce the number of coding errors. Lemon also uses a more +sophisticated parsing engine that is faster than yacc and +bison and which is both reentrant and thread-safe. +Furthermore, Lemon implements features that can be used +to eliminate resource leaks, making is suitable for use +in long-running programs such as graphical user interfaces +or embedded controllers.

+ +

This document is an introduction to the Lemon +parser generator.

+ +

Theory of Operation

+ +

The main goal of Lemon is to translate a context free grammar (CFG) +for a particular language into C code that implements a parser for +that language. +The program has two inputs: +

    +
  • The grammar specification. +
  • A parser template file. +
+Typically, only the grammar specification is supplied by the programmer. +Lemon comes with a default parser template which works fine for most +applications. But the user is free to substitute a different parser +template if desired.

+ +

Depending on command-line options, Lemon will generate between +one and three files of outputs. +

    +
  • C code to implement the parser. +
  • A header file defining an integer ID for each terminal symbol. +
  • An information file that describes the states of the generated parser + automaton. +
+By default, all three of these output files are generated. +The header file is suppressed if the ``-m'' command-line option is +used and the report file is omitted when ``-q'' is selected.

+ +

The grammar specification file uses a ``.y'' suffix, by convention. +In the examples used in this document, we'll assume the name of the +grammar file is ``gram.y''. A typical use of Lemon would be the +following command: +

+   lemon gram.y
+
+This command will generate three output files named ``gram.c'', +``gram.h'' and ``gram.out''. +The first is C code to implement the parser. The second +is the header file that defines numerical values for all +terminal symbols, and the last is the report that explains +the states used by the parser automaton.

+ +

Command Line Options

+ +

The behavior of Lemon can be modified using command-line options. +You can obtain a list of the available command-line options together +with a brief explanation of what each does by typing +

+   lemon -?
+
+As of this writing, the following command-line options are supported: +
    +
  • -b +
  • -c +
  • -g +
  • -m +
  • -q +
  • -s +
  • -x +
+The ``-b'' option reduces the amount of text in the report file by +printing only the basis of each parser state, rather than the full +configuration. +The ``-c'' option suppresses action table compression. Using -c +will make the parser a little larger and slower but it will detect +syntax errors sooner. +The ``-g'' option causes no output files to be generated at all. +Instead, the input grammar file is printed on standard output but +with all comments, actions and other extraneous text deleted. This +is a useful way to get a quick summary of a grammar. +The ``-m'' option causes the output C source file to be compatible +with the ``makeheaders'' program. +Makeheaders is a program that automatically generates header files +from C source code. When the ``-m'' option is used, the header +file is not output since the makeheaders program will take care +of generated all header files automatically. +The ``-q'' option suppresses the report file. +Using ``-s'' causes a brief summary of parser statistics to be +printed. Like this: +
+   Parser statistics: 74 terminals, 70 nonterminals, 179 rules
+                      340 states, 2026 parser table entries, 0 conflicts
+
+Finally, the ``-x'' option causes Lemon to print its version number +and copyright information +and then stop without attempting to read the grammar or generate a parser.

+ +

The Parser Interface

+ +

Lemon doesn't generate a complete, working program. It only generates +a few subroutines that implement a parser. This section describes +the interface to those subroutines. It is up to the programmer to +call these subroutines in an appropriate way in order to produce a +complete system.

+ +

Before a program begins using a Lemon-generated parser, the program +must first create the parser. +A new parser is created as follows: +

+   void *pParser = ParseAlloc( malloc );
+
+The ParseAlloc() routine allocates and initializes a new parser and +returns a pointer to it. +The actual data structure used to represent a parser is opaque -- +its internal structure is not visible or usable by the calling routine. +For this reason, the ParseAlloc() routine returns a pointer to void +rather than a pointer to some particular structure. +The sole argument to the ParseAlloc() routine is a pointer to the +subroutine used to allocate memory. Typically this means ``malloc()''.

+ +

After a program is finished using a parser, it can reclaim all +memory allocated by that parser by calling +

+   ParseFree(pParser, free);
+
+The first argument is the same pointer returned by ParseAlloc(). The +second argument is a pointer to the function used to release bulk +memory back to the system.

+ +

After a parser has been allocated using ParseAlloc(), the programmer +must supply the parser with a sequence of tokens (terminal symbols) to +be parsed. This is accomplished by calling the following function +once for each token: +

+   Parse(pParser, hTokenID, sTokenData, pArg);
+
+The first argument to the Parse() routine is the pointer returned by +ParseAlloc(). +The second argument is a small positive integer that tells the parse the +type of the next token in the data stream. +There is one token type for each terminal symbol in the grammar. +The gram.h file generated by Lemon contains #define statements that +map symbolic terminal symbol names into appropriate integer values. +(A value of 0 for the second argument is a special flag to the +parser to indicate that the end of input has been reached.) +The third argument is the value of the given token. By default, +the type of the third argument is integer, but the grammar will +usually redefine this type to be some kind of structure. +Typically the second argument will be a broad category of tokens +such as ``identifier'' or ``number'' and the third argument will +be the name of the identifier or the value of the number.

+ +

The Parse() function may have either three or four arguments, +depending on the grammar. If the grammar specification file request +it, the Parse() function will have a fourth parameter that can be +of any type chosen by the programmer. The parser doesn't do anything +with this argument except to pass it through to action routines. +This is a convenient mechanism for passing state information down +to the action routines without having to use global variables.

+ +

A typical use of a Lemon parser might look something like the +following: +

+   01 ParseTree *ParseFile(const char *zFilename){
+   02    Tokenizer *pTokenizer;
+   03    void *pParser;
+   04    Token sToken;
+   05    int hTokenId;
+   06    ParserState sState;
+   07
+   08    pTokenizer = TokenizerCreate(zFilename);
+   09    pParser = ParseAlloc( malloc );
+   10    InitParserState(&sState);
+   11    while( GetNextToken(pTokenizer, &hTokenId, &sToken) ){
+   12       Parse(pParser, hTokenId, sToken, &sState);
+   13    }
+   14    Parse(pParser, 0, sToken, &sState);
+   15    ParseFree(pParser, free );
+   16    TokenizerFree(pTokenizer);
+   17    return sState.treeRoot;
+   18 }
+
+This example shows a user-written routine that parses a file of +text and returns a pointer to the parse tree. +(We've omitted all error-handling from this example to keep it +simple.) +We assume the existence of some kind of tokenizer which is created +using TokenizerCreate() on line 8 and deleted by TokenizerFree() +on line 16. The GetNextToken() function on line 11 retrieves the +next token from the input file and puts its type in the +integer variable hTokenId. The sToken variable is assumed to be +some kind of structure that contains details about each token, +such as its complete text, what line it occurs on, etc.

+ +

This example also assumes the existence of structure of type +ParserState that holds state information about a particular parse. +An instance of such a structure is created on line 6 and initialized +on line 10. A pointer to this structure is passed into the Parse() +routine as the optional 4th argument. +The action routine specified by the grammar for the parser can use +the ParserState structure to hold whatever information is useful and +appropriate. In the example, we note that the treeRoot field of +the ParserState structure is left pointing to the root of the parse +tree.

+ +

The core of this example as it relates to Lemon is as follows: +

+   ParseFile(){
+      pParser = ParseAlloc( malloc );
+      while( GetNextToken(pTokenizer,&hTokenId, &sToken) ){
+         Parse(pParser, hTokenId, sToken);
+      }
+      Parse(pParser, 0, sToken);
+      ParseFree(pParser, free );
+   }
+
+Basically, what a program has to do to use a Lemon-generated parser +is first create the parser, then send it lots of tokens obtained by +tokenizing an input source. When the end of input is reached, the +Parse() routine should be called one last time with a token type +of 0. This step is necessary to inform the parser that the end of +input has been reached. Finally, we reclaim memory used by the +parser by calling ParseFree().

+ +

There is one other interface routine that should be mentioned +before we move on. +The ParseTrace() function can be used to generate debugging output +from the parser. A prototype for this routine is as follows: +

+   ParseTrace(FILE *stream, char *zPrefix);
+
+After this routine is called, a short (one-line) message is written +to the designated output stream every time the parser changes states +or calls an action routine. Each such message is prefaced using +the text given by zPrefix. This debugging output can be turned off +by calling ParseTrace() again with a first argument of NULL (0).

+ +

Differences With YACC and BISON

+ +

Programmers who have previously used the yacc or bison parser +generator will notice several important differences between yacc and/or +bison and Lemon. +

    +
  • In yacc and bison, the parser calls the tokenizer. In Lemon, + the tokenizer calls the parser. +
  • Lemon uses no global variables. Yacc and bison use global variables + to pass information between the tokenizer and parser. +
  • Lemon allows multiple parsers to be running simultaneously. Yacc + and bison do not. +
+These differences may cause some initial confusion for programmers +with prior yacc and bison experience. +But after years of experience using Lemon, I firmly +believe that the Lemon way of doing things is better.

+ +

Input File Syntax

+ +

The main purpose of the grammar specification file for Lemon is +to define the grammar for the parser. But the input file also +specifies additional information Lemon requires to do its job. +Most of the work in using Lemon is in writing an appropriate +grammar file.

+ +

The grammar file for lemon is, for the most part, free format. +It does not have sections or divisions like yacc or bison. Any +declaration can occur at any point in the file. +Lemon ignores whitespace (except where it is needed to separate +tokens) and it honors the same commenting conventions as C and C++.

+ +

Terminals and Nonterminals

+ +

A terminal symbol (token) is any string of alphanumeric +and underscore characters +that begins with an upper case letter. +A terminal can contain lower class letters after the first character, +but the usual convention is to make terminals all upper case. +A nonterminal, on the other hand, is any string of alphanumeric +and underscore characters than begins with a lower case letter. +Again, the usual convention is to make nonterminals use all lower +case letters.

+ +

In Lemon, terminal and nonterminal symbols do not need to +be declared or identified in a separate section of the grammar file. +Lemon is able to generate a list of all terminals and nonterminals +by examining the grammar rules, and it can always distinguish a +terminal from a nonterminal by checking the case of the first +character of the name.

+ +

Yacc and bison allow terminal symbols to have either alphanumeric +names or to be individual characters included in single quotes, like +this: ')' or '$'. Lemon does not allow this alternative form for +terminal symbols. With Lemon, all symbols, terminals and nonterminals, +must have alphanumeric names.

+ +

Grammar Rules

+ +

The main component of a Lemon grammar file is a sequence of grammar +rules. +Each grammar rule consists of a nonterminal symbol followed by +the special symbol ``::='' and then a list of terminals and/or nonterminals. +The rule is terminated by a period. +The list of terminals and nonterminals on the right-hand side of the +rule can be empty. +Rules can occur in any order, except that the left-hand side of the +first rule is assumed to be the start symbol for the grammar (unless +specified otherwise using the %start directive described below.) +A typical sequence of grammar rules might look something like this: +

+  expr ::= expr PLUS expr.
+  expr ::= expr TIMES expr.
+  expr ::= LPAREN expr RPAREN.
+  expr ::= VALUE.
+
+

+ +

There is one non-terminal in this example, ``expr'', and five +terminal symbols or tokens: ``PLUS'', ``TIMES'', ``LPAREN'', +``RPAREN'' and ``VALUE''.

+ +

Like yacc and bison, Lemon allows the grammar to specify a block +of C code that will be executed whenever a grammar rule is reduced +by the parser. +In Lemon, this action is specified by putting the C code (contained +within curly braces {...}) immediately after the +period that closes the rule. +For example: +

+  expr ::= expr PLUS expr.   { printf("Doing an addition...\n"); }
+
+

+ +

In order to be useful, grammar actions must normally be linked to +their associated grammar rules. +In yacc and bison, this is accomplished by embedding a ``$$'' in the +action to stand for the value of the left-hand side of the rule and +symbols ``$1'', ``$2'', and so forth to stand for the value of +the terminal or nonterminal at position 1, 2 and so forth on the +right-hand side of the rule. +This idea is very powerful, but it is also very error-prone. The +single most common source of errors in a yacc or bison grammar is +to miscount the number of symbols on the right-hand side of a grammar +rule and say ``$7'' when you really mean ``$8''.

+ +

Lemon avoids the need to count grammar symbols by assigning symbolic +names to each symbol in a grammar rule and then using those symbolic +names in the action. +In yacc or bison, one would write this: +

+  expr -> expr PLUS expr  { $$ = $1 + $3; };
+
+But in Lemon, the same rule becomes the following: +
+  expr(A) ::= expr(B) PLUS expr(C).  { A = B+C; }
+
+In the Lemon rule, any symbol in parentheses after a grammar rule +symbol becomes a place holder for that symbol in the grammar rule. +This place holder can then be used in the associated C action to +stand for the value of that symbol.

+ +

The Lemon notation for linking a grammar rule with its reduce +action is superior to yacc/bison on several counts. +First, as mentioned above, the Lemon method avoids the need to +count grammar symbols. +Secondly, if a terminal or nonterminal in a Lemon grammar rule +includes a linking symbol in parentheses but that linking symbol +is not actually used in the reduce action, then an error message +is generated. +For example, the rule +

+  expr(A) ::= expr(B) PLUS expr(C).  { A = B; }
+
+will generate an error because the linking symbol ``C'' is used +in the grammar rule but not in the reduce action.

+ +

The Lemon notation for linking grammar rules to reduce actions +also facilitates the use of destructors for reclaiming memory +allocated by the values of terminals and nonterminals on the +right-hand side of a rule.

+ +

Precedence Rules

+ +

Lemon resolves parsing ambiguities in exactly the same way as +yacc and bison. A shift-reduce conflict is resolved in favor +of the shift, and a reduce-reduce conflict is resolved by reducing +whichever rule comes first in the grammar file.

+ +

Just like in +yacc and bison, Lemon allows a measure of control +over the resolution of paring conflicts using precedence rules. +A precedence value can be assigned to any terminal symbol +using the %left, %right or %nonassoc directives. Terminal symbols +mentioned in earlier directives have a lower precedence that +terminal symbols mentioned in later directives. For example:

+ +

+   %left AND.
+   %left OR.
+   %nonassoc EQ NE GT GE LT LE.
+   %left PLUS MINUS.
+   %left TIMES DIVIDE MOD.
+   %right EXP NOT.
+

+ +

In the preceding sequence of directives, the AND operator is +defined to have the lowest precedence. The OR operator is one +precedence level higher. And so forth. Hence, the grammar would +attempt to group the ambiguous expression +

+     a AND b OR c
+
+like this +
+     a AND (b OR c).
+
+The associativity (left, right or nonassoc) is used to determine +the grouping when the precedence is the same. AND is left-associative +in our example, so +
+     a AND b AND c
+
+is parsed like this +
+     (a AND b) AND c.
+
+The EXP operator is right-associative, though, so +
+     a EXP b EXP c
+
+is parsed like this +
+     a EXP (b EXP c).
+
+The nonassoc precedence is used for non-associative operators. +So +
+     a EQ b EQ c
+
+is an error.

+ +

The precedence of non-terminals is transferred to rules as follows: +The precedence of a grammar rule is equal to the precedence of the +left-most terminal symbol in the rule for which a precedence is +defined. This is normally what you want, but in those cases where +you want to precedence of a grammar rule to be something different, +you can specify an alternative precedence symbol by putting the +symbol in square braces after the period at the end of the rule and +before any C-code. For example:

+ +

+   expr = MINUS expr.  [NOT]
+

+ +

This rule has a precedence equal to that of the NOT symbol, not the +MINUS symbol as would have been the case by default.

+ +

With the knowledge of how precedence is assigned to terminal +symbols and individual +grammar rules, we can now explain precisely how parsing conflicts +are resolved in Lemon. Shift-reduce conflicts are resolved +as follows: +

    +
  • If either the token to be shifted or the rule to be reduced + lacks precedence information, then resolve in favor of the + shift, but report a parsing conflict. +
  • If the precedence of the token to be shifted is greater than + the precedence of the rule to reduce, then resolve in favor + of the shift. No parsing conflict is reported. +
  • If the precedence of the token it be shifted is less than the + precedence of the rule to reduce, then resolve in favor of the + reduce action. No parsing conflict is reported. +
  • If the precedences are the same and the shift token is + right-associative, then resolve in favor of the shift. + No parsing conflict is reported. +
  • If the precedences are the same the the shift token is + left-associative, then resolve in favor of the reduce. + No parsing conflict is reported. +
  • Otherwise, resolve the conflict by doing the shift and + report the parsing conflict. +
+Reduce-reduce conflicts are resolved this way: +
    +
  • If either reduce rule + lacks precedence information, then resolve in favor of the + rule that appears first in the grammar and report a parsing + conflict. +
  • If both rules have precedence and the precedence is different + then resolve the dispute in favor of the rule with the highest + precedence and do not report a conflict. +
  • Otherwise, resolve the conflict by reducing by the rule that + appears first in the grammar and report a parsing conflict. +
+ +

Special Directives

+ +

The input grammar to Lemon consists of grammar rules and special +directives. We've described all the grammar rules, so now we'll +talk about the special directives.

+ +

Directives in lemon can occur in any order. You can put them before +the grammar rules, or after the grammar rules, or in the mist of the +grammar rules. It doesn't matter. The relative order of +directives used to assign precedence to terminals is important, but +other than that, the order of directives in Lemon is arbitrary.

+ +

Lemon supports the following special directives: +

    +
  • %destructor +
  • %extra_argument +
  • %include +
  • %left +
  • %name +
  • %nonassoc +
  • %parse_accept +
  • %parse_failure +
  • %right +
  • %stack_overflow +
  • %stack_size +
  • %start_symbol +
  • %syntax_error +
  • %token_destructor +
  • %token_prefix +
  • %token_type +
  • %type +
+Each of these directives will be described separately in the +following sections:

+ +

The %destructor directive

+ +

The %destructor directive is used to specify a destructor for +a non-terminal symbol. +(See also the %token_destructor directive which is used to +specify a destructor for terminal symbols.)

+ +

A non-terminal's destructor is called to dispose of the +non-terminal's value whenever the non-terminal is popped from +the stack. This includes all of the following circumstances: +

    +
  • When a rule reduces and the value of a non-terminal on + the right-hand side is not linked to C code. +
  • When the stack is popped during error processing. +
  • When the ParseFree() function runs. +
+The destructor can do whatever it wants with the value of +the non-terminal, but its design is to deallocate memory +or other resources held by that non-terminal.

+ +

Consider an example: +

+   %type nt {void*}
+   %destructor nt { free($$); }
+   nt(A) ::= ID NUM.   { A = malloc( 100 ); }
+
+This example is a bit contrived but it serves to illustrate how +destructors work. The example shows a non-terminal named +``nt'' that holds values of type ``void*''. When the rule for +an ``nt'' reduces, it sets the value of the non-terminal to +space obtained from malloc(). Later, when the nt non-terminal +is popped from the stack, the destructor will fire and call +free() on this malloced space, thus avoiding a memory leak. +(Note that the symbol ``$$'' in the destructor code is replaced +by the value of the non-terminal.)

+ +

It is important to note that the value of a non-terminal is passed +to the destructor whenever the non-terminal is removed from the +stack, unless the non-terminal is used in a C-code action. If +the non-terminal is used by C-code, then it is assumed that the +C-code will take care of destroying it if it should really +be destroyed. More commonly, the value is used to build some +larger structure and we don't want to destroy it, which is why +the destructor is not called in this circumstance.

+ +

By appropriate use of destructors, it is possible to +build a parser using Lemon that can be used within a long-running +program, such as a GUI, that will not leak memory or other resources. +To do the same using yacc or bison is much more difficult.

+ +

The %extra_argument directive

+ +The %extra_argument directive instructs Lemon to add a 4th parameter +to the parameter list of the Parse() function it generates. Lemon +doesn't do anything itself with this extra argument, but it does +make the argument available to C-code action routines, destructors, +and so forth. For example, if the grammar file contains:

+ +

+    %extra_argument { MyStruct *pAbc }
+

+ +

Then the Parse() function generated will have an 4th parameter +of type ``MyStruct*'' and all action routines will have access to +a variable named ``pAbc'' that is the value of the 4th parameter +in the most recent call to Parse().

+ +

The %include directive

+ +

The %include directive specifies C code that is included at the +top of the generated parser. You can include any text you want -- +the Lemon parser generator copies to blindly. If you have multiple +%include directives in your grammar file, their values are concatenated +before being put at the beginning of the generated parser.

+ +

The %include directive is very handy for getting some extra #include +preprocessor statements at the beginning of the generated parser. +For example:

+ +

+   %include {#include <unistd.h>}
+

+ +

This might be needed, for example, if some of the C actions in the +grammar call functions that are prototyed in unistd.h.

+ +

The %left directive

+ +The %left directive is used (along with the %right and +%nonassoc directives) to declare precedences of terminal +symbols. Every terminal symbol whose name appears after +a %left directive but before the next period (``.'') is +given the same left-associative precedence value. Subsequent +%left directives have higher precedence. For example:

+ +

+   %left AND.
+   %left OR.
+   %nonassoc EQ NE GT GE LT LE.
+   %left PLUS MINUS.
+   %left TIMES DIVIDE MOD.
+   %right EXP NOT.
+

+ +

Note the period that terminates each %left, %right or %nonassoc +directive.

+ +

LALR(1) grammars can get into a situation where they require +a large amount of stack space if you make heavy use or right-associative +operators. For this reason, it is recommended that you use %left +rather than %right whenever possible.

+ +

The %name directive

+ +

By default, the functions generated by Lemon all begin with the +five-character string ``Parse''. You can change this string to something +different using the %name directive. For instance:

+ +

+   %name Abcde
+

+ +

Putting this directive in the grammar file will cause Lemon to generate +functions named +

    +
  • AbcdeAlloc(), +
  • AbcdeFree(), +
  • AbcdeTrace(), and +
  • Abcde(). +
+The %name directive allows you to generator two or more different +parsers and link them all into the same executable. +

+ +

The %nonassoc directive

+ +

This directive is used to assign non-associative precedence to +one or more terminal symbols. See the section on precedence rules +or on the %left directive for additional information.

+ +

The %parse_accept directive

+ +

The %parse_accept directive specifies a block of C code that is +executed whenever the parser accepts its input string. To ``accept'' +an input string means that the parser was able to process all tokens +without error.

+ +

For example:

+ +

+   %parse_accept {
+      printf("parsing complete!\n");
+   }
+

+ + +

The %parse_failure directive

+ +

The %parse_failure directive specifies a block of C code that +is executed whenever the parser fails complete. This code is not +executed until the parser has tried and failed to resolve an input +error using is usual error recovery strategy. The routine is +only invoked when parsing is unable to continue.

+ +

+   %parse_failure {
+     fprintf(stderr,"Giving up.  Parser is hopelessly lost...\n");
+   }
+

+ +

The %right directive

+ +

This directive is used to assign right-associative precedence to +one or more terminal symbols. See the section on precedence rules +or on the %left directive for additional information.

+ +

The %stack_overflow directive

+ +

The %stack_overflow directive specifies a block of C code that +is executed if the parser's internal stack ever overflows. Typically +this just prints an error message. After a stack overflow, the parser +will be unable to continue and must be reset.

+ +

+   %stack_overflow {
+     fprintf(stderr,"Giving up.  Parser stack overflow\n");
+   }
+

+ +

You can help prevent parser stack overflows by avoiding the use +of right recursion and right-precedence operators in your grammar. +Use left recursion and and left-precedence operators instead, to +encourage rules to reduce sooner and keep the stack size down. +For example, do rules like this: +

+   list ::= list element.      // left-recursion.  Good!
+   list ::= .
+
+Not like this: +
+   list ::= element list.      // right-recursion.  Bad!
+   list ::= .
+
+ +

The %stack_size directive

+ +

If stack overflow is a problem and you can't resolve the trouble +by using left-recursion, then you might want to increase the size +of the parser's stack using this directive. Put an positive integer +after the %stack_size directive and Lemon will generate a parse +with a stack of the requested size. The default value is 100.

+ +

+   %stack_size 2000
+

+ +

The %start_symbol directive

+ +

By default, the start-symbol for the grammar that Lemon generates +is the first non-terminal that appears in the grammar file. But you +can choose a different start-symbol using the %start_symbol directive.

+ +

+   %start_symbol  prog
+

+ +

The %token_destructor directive

+ +

The %destructor directive assigns a destructor to a non-terminal +symbol. (See the description of the %destructor directive above.) +This directive does the same thing for all terminal symbols.

+ +

Unlike non-terminal symbols which may each have a different data type +for their values, terminals all use the same data type (defined by +the %token_type directive) and so they use a common destructor. Other +than that, the token destructor works just like the non-terminal +destructors.

+ +

The %token_prefix directive

+ +

Lemon generates #defines that assign small integer constants +to each terminal symbol in the grammar. If desired, Lemon will +add a prefix specified by this directive +to each of the #defines it generates. +So if the default output of Lemon looked like this: +

+    #define AND              1
+    #define MINUS            2
+    #define OR               3
+    #define PLUS             4
+
+You can insert a statement into the grammar like this: +
+    %token_prefix    TOKEN_
+
+to cause Lemon to produce these symbols instead: +
+    #define TOKEN_AND        1
+    #define TOKEN_MINUS      2
+    #define TOKEN_OR         3
+    #define TOKEN_PLUS       4
+
+ +

The %token_type and %type directives

+ +

These directives are used to specify the data types for values +on the parser's stack associated with terminal and non-terminal +symbols. The values of all terminal symbols must be of the same +type. This turns out to be the same data type as the 3rd parameter +to the Parse() function generated by Lemon. Typically, you will +make the value of a terminal symbol by a pointer to some kind of +token structure. Like this:

+ +

+   %token_type    {Token*}
+

+ +

If the data type of terminals is not specified, the default value +is ``int''.

+ +

Non-terminal symbols can each have their own data types. Typically +the data type of a non-terminal is a pointer to the root of a parse-tree +structure that contains all information about that non-terminal. +For example:

+ +

+   %type   expr  {Expr*}
+

+ +

Each entry on the parser's stack is actually a union containing +instances of all data types for every non-terminal and terminal symbol. +Lemon will automatically use the correct element of this union depending +on what the corresponding non-terminal or terminal symbol is. But +the grammar designer should keep in mind that the size of the union +will be the size of its largest element. So if you have a single +non-terminal whose data type requires 1K of storage, then your 100 +entry parser stack will require 100K of heap space. If you are willing +and able to pay that price, fine. You just need to know.

+ +

Error Processing

+ +

After extensive experimentation over several years, it has been +discovered that the error recovery strategy used by yacc is about +as good as it gets. And so that is what Lemon uses.

+ +

When a Lemon-generated parser encounters a syntax error, it +first invokes the code specified by the %syntax_error directive, if +any. It then enters its error recovery strategy. The error recovery +strategy is to begin popping the parsers stack until it enters a +state where it is permitted to shift a special non-terminal symbol +named ``error''. It then shifts this non-terminal and continues +parsing. But the %syntax_error routine will not be called again +until at least three new tokens have been successfully shifted.

+ +

If the parser pops its stack until the stack is empty, and it still +is unable to shift the error symbol, then the %parse_failed routine +is invoked and the parser resets itself to its start state, ready +to begin parsing a new file. This is what will happen at the very +first syntax error, of course, if there are no instances of the +``error'' non-terminal in your grammar.

+ + + diff --git a/tools/lemon/lempar.c b/tools/lemon/lempar.c new file mode 100644 index 000000000..59708e222 --- /dev/null +++ b/tools/lemon/lempar.c @@ -0,0 +1,972 @@ +/* +** 2000-05-29 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** Driver template for the LEMON parser generator. +** +** The "lemon" program processes an LALR(1) input grammar file, then uses +** this template to construct a parser. The "lemon" program inserts text +** at each "%%" line. Also, any "P-a-r-s-e" identifer prefix (without the +** interstitial "-" characters) contained in this template is changed into +** the value of the %name directive from the grammar. Otherwise, the content +** of this template is copied straight through into the generate parser +** source file. +** +** The following is the concatenation of all %include directives from the +** input grammar file: +*/ +#include +#include +#include + +#ifdef _MSC_VER +#define CDECL __cdecl +#else +#define CDECL +#endif + +/************ Begin %include sections from the grammar ************************/ +%% +/**************** End of %include directives **********************************/ +/* These constants specify the various numeric values for terminal symbols +** in a format understandable to "makeheaders". This section is blank unless +** "lemon" is run with the "-m" command-line option. +***************** Begin makeheaders token definitions *************************/ +%% +/**************** End makeheaders token definitions ***************************/ + +/* The next section is a series of control #defines. +** various aspects of the generated parser. +** YYCODETYPE is the data type used to store the integer codes +** that represent terminal and non-terminal symbols. +** "unsigned char" is used if there are fewer than +** 256 symbols. Larger types otherwise. +** YYNOCODE is a number of type YYCODETYPE that is not used for +** any terminal or nonterminal symbol. +** YYFALLBACK If defined, this indicates that one or more tokens +** (also known as: "terminal symbols") have fall-back +** values which should be used if the original symbol +** would not parse. This permits keywords to sometimes +** be used as identifiers, for example. +** YYACTIONTYPE is the data type used for "action codes" - numbers +** that indicate what to do in response to the next +** token. +** ParseTOKENTYPE is the data type used for minor type for terminal +** symbols. Background: A "minor type" is a semantic +** value associated with a terminal or non-terminal +** symbols. For example, for an "ID" terminal symbol, +** the minor type might be the name of the identifier. +** Each non-terminal can have a different minor type. +** Terminal symbols all have the same minor type, though. +** This macros defines the minor type for terminal +** symbols. +** YYMINORTYPE is the data type used for all minor types. +** This is typically a union of many types, one of +** which is ParseTOKENTYPE. The entry in the union +** for terminal symbols is called "yy0". +** YYSTACKDEPTH is the maximum depth of the parser's stack. If +** zero the stack is dynamically sized using realloc() +** ParseARG_SDECL A static variable declaration for the %extra_argument +** ParseARG_PDECL A parameter declaration for the %extra_argument +** ParseARG_STORE Code to store %extra_argument into yypParser +** ParseARG_FETCH Code to extract %extra_argument from yypParser +** YYERRORSYMBOL is the code number of the error symbol. If not +** defined, then do no error processing. +** YYNSTATE the combined number of states. +** YYNRULE the number of rules in the grammar +** YY_MAX_SHIFT Maximum value for shift actions +** YY_MIN_SHIFTREDUCE Minimum value for shift-reduce actions +** YY_MAX_SHIFTREDUCE Maximum value for shift-reduce actions +** YY_MIN_REDUCE Maximum value for reduce actions +** YY_ERROR_ACTION The yy_action[] code for syntax error +** YY_ACCEPT_ACTION The yy_action[] code for accept +** YY_NO_ACTION The yy_action[] code for no-op +*/ +#ifndef INTERFACE +# define INTERFACE 1 +#endif +/************* Begin control #defines *****************************************/ +%% +/************* End control #defines *******************************************/ + +/* Define the yytestcase() macro to be a no-op if is not already defined +** otherwise. +** +** Applications can choose to define yytestcase() in the %include section +** to a macro that can assist in verifying code coverage. For production +** code the yytestcase() macro should be turned off. But it is useful +** for testing. +*/ +#ifndef yytestcase +# define yytestcase(X) +#endif + + +/* Next are the tables used to determine what action to take based on the +** current state and lookahead token. These tables are used to implement +** functions that take a state number and lookahead value and return an +** action integer. +** +** Suppose the action integer is N. Then the action is determined as +** follows +** +** 0 <= N <= YY_MAX_SHIFT Shift N. That is, push the lookahead +** token onto the stack and goto state N. +** +** N between YY_MIN_SHIFTREDUCE Shift to an arbitrary state then +** and YY_MAX_SHIFTREDUCE reduce by rule N-YY_MIN_SHIFTREDUCE. +** +** N between YY_MIN_REDUCE Reduce by rule N-YY_MIN_REDUCE +** and YY_MAX_REDUCE +** +** N == YY_ERROR_ACTION A syntax error has occurred. +** +** N == YY_ACCEPT_ACTION The parser accepts its input. +** +** N == YY_NO_ACTION No such action. Denotes unused +** slots in the yy_action[] table. +** +** The action table is constructed as a single large table named yy_action[]. +** Given state S and lookahead X, the action is computed as either: +** +** (A) N = yy_action[ yy_shift_ofst[S] + X ] +** (B) N = yy_default[S] +** +** The (A) formula is preferred. The B formula is used instead if: +** (1) The yy_shift_ofst[S]+X value is out of range, or +** (2) yy_lookahead[yy_shift_ofst[S]+X] is not equal to X, or +** (3) yy_shift_ofst[S] equal YY_SHIFT_USE_DFLT. +** (Implementation note: YY_SHIFT_USE_DFLT is chosen so that +** YY_SHIFT_USE_DFLT+X will be out of range for all possible lookaheads X. +** Hence only tests (1) and (2) need to be evaluated.) +** +** The formulas above are for computing the action when the lookahead is +** a terminal symbol. If the lookahead is a non-terminal (as occurs after +** a reduce action) then the yy_reduce_ofst[] array is used in place of +** the yy_shift_ofst[] array and YY_REDUCE_USE_DFLT is used in place of +** YY_SHIFT_USE_DFLT. +** +** The following are the tables generated in this section: +** +** yy_action[] A single table containing all actions. +** yy_lookahead[] A table containing the lookahead for each entry in +** yy_action. Used to detect hash collisions. +** yy_shift_ofst[] For each state, the offset into yy_action for +** shifting terminals. +** yy_reduce_ofst[] For each state, the offset into yy_action for +** shifting non-terminals after a reduce. +** yy_default[] Default action for each state. +** +*********** Begin parsing tables **********************************************/ +%% +/********** End of lemon-generated parsing tables *****************************/ + +/* The next table maps tokens (terminal symbols) into fallback tokens. +** If a construct like the following: +** +** %fallback ID X Y Z. +** +** appears in the grammar, then ID becomes a fallback token for X, Y, +** and Z. Whenever one of the tokens X, Y, or Z is input to the parser +** but it does not parse, the type of the token is changed to ID and +** the parse is retried before an error is thrown. +** +** This feature can be used, for example, to cause some keywords in a language +** to revert to identifiers if they keyword does not apply in the context where +** it appears. +*/ +#ifdef YYFALLBACK +static const YYCODETYPE yyFallback[] = { +%% +}; +#endif /* YYFALLBACK */ + +/* The following structure represents a single element of the +** parser's stack. Information stored includes: +** +** + The state number for the parser at this level of the stack. +** +** + The value of the token stored at this level of the stack. +** (In other words, the "major" token.) +** +** + The semantic value stored at this level of the stack. This is +** the information used by the action routines in the grammar. +** It is sometimes called the "minor" token. +** +** After the "shift" half of a SHIFTREDUCE action, the stateno field +** actually contains the reduce action for the second half of the +** SHIFTREDUCE. +*/ +struct yyStackEntry { + YYACTIONTYPE stateno; /* The state-number, or reduce action in SHIFTREDUCE */ + YYCODETYPE major; /* The major token value. This is the code + ** number for the token at this stack level */ + YYMINORTYPE minor; /* The user-supplied minor token value. This + ** is the value of the token */ +}; +typedef struct yyStackEntry yyStackEntry; + +/* The state of the parser is completely contained in an instance of +** the following structure */ +struct yyParser { + yyStackEntry *yytos; /* Pointer to top element of the stack */ +#ifdef YYTRACKMAXSTACKDEPTH + int yyhwm; /* High-water mark of the stack */ +#endif +#ifndef YYNOERRORRECOVERY + int yyerrcnt; /* Shifts left before out of the error */ +#endif + ParseARG_SDECL /* A place to hold %extra_argument */ +#if YYSTACKDEPTH<=0 + int yystksz; /* Current side of the stack */ + yyStackEntry *yystack; /* The parser's stack */ + yyStackEntry yystk0; /* First stack entry */ +#else + yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's stack */ +#endif +}; +typedef struct yyParser yyParser; + +#ifndef NDEBUG +#include +static FILE *yyTraceFILE = 0; +static char *yyTracePrompt = 0; +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* +** Turn parser tracing on by giving a stream to which to write the trace +** and a prompt to preface each trace message. Tracing is turned off +** by making either argument NULL +** +** Inputs: +**
    +**
  • A FILE* to which trace output should be written. +** If NULL, then tracing is turned off. +**
  • A prefix string written at the beginning of every +** line of trace output. If NULL, then tracing is +** turned off. +**
+** +** Outputs: +** None. +*/ +void ParseTrace(FILE *TraceFILE, char *zTracePrompt){ + yyTraceFILE = TraceFILE; + yyTracePrompt = zTracePrompt; + if( yyTraceFILE==0 ) yyTracePrompt = 0; + else if( yyTracePrompt==0 ) yyTraceFILE = 0; +} +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* For tracing shifts, the names of all terminals and nonterminals +** are required. The following table supplies these names */ +static const char *const yyTokenName[] = { +%% +}; +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* For tracing reduce actions, the names of all rules are required. +*/ +static const char *const yyRuleName[] = { +%% +}; +#endif /* NDEBUG */ + + +#if YYSTACKDEPTH<=0 +/* +** Try to increase the size of the parser stack. Return the number +** of errors. Return 0 on success. +*/ +static int yyGrowStack(yyParser *p){ + int newSize; + int idx; + yyStackEntry *pNew; + + newSize = p->yystksz*2 + 100; + idx = p->yytos ? (int)(p->yytos - p->yystack) : 0; + if( p->yystack==&p->yystk0 ){ + pNew = (yyStackEntry *)malloc(newSize*sizeof(pNew[0])); + if( pNew ) pNew[0] = p->yystk0; + }else{ + pNew = (yyStackEntry *)realloc(p->yystack, newSize*sizeof(pNew[0])); + } + if( pNew ){ + p->yystack = pNew; + p->yytos = &p->yystack[idx]; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sStack grows from %d to %d entries.\n", + yyTracePrompt, p->yystksz, newSize); + fflush(yyTraceFILE); + } +#endif + p->yystksz = newSize; + } + return pNew==0; +} +#endif + +/* Datatype of the argument to the memory allocated passed as the +** second argument to ParseAlloc() below. This can be changed by +** putting an appropriate #define in the %include section of the input +** grammar. +*/ +#ifndef YYMALLOCARGTYPE +# define YYMALLOCARGTYPE size_t +#endif + +/* +** This function allocates a new parser. +** The only argument is a pointer to a function which works like +** malloc. +** +** Inputs: +** A pointer to the function used to allocate memory. +** +** Outputs: +** A pointer to a parser. This pointer is used in subsequent calls +** to Parse and ParseFree. +*/ +void *ParseAlloc(void *(CDECL *mallocProc)(YYMALLOCARGTYPE)){ + yyParser *pParser; + pParser = (yyParser*)(*mallocProc)( (YYMALLOCARGTYPE)sizeof(yyParser) ); + if( pParser ){ +#ifdef YYTRACKMAXSTACKDEPTH + pParser->yyhwm = 0; +#endif +#if YYSTACKDEPTH<=0 + pParser->yytos = NULL; + pParser->yystack = NULL; + pParser->yystksz = 0; + if( yyGrowStack(pParser) ){ + pParser->yystack = &pParser->yystk0; + pParser->yystksz = 1; + } +#endif +#ifndef YYNOERRORRECOVERY + pParser->yyerrcnt = -1; +#endif + pParser->yytos = pParser->yystack; + pParser->yystack[0].stateno = 0; + pParser->yystack[0].major = 0; + } + return pParser; +} + +/* The following function deletes the "minor type" or semantic value +** associated with a symbol. The symbol can be either a terminal +** or nonterminal. "yymajor" is the symbol code, and "yypminor" is +** a pointer to the value to be deleted. The code used to do the +** deletions is derived from the %destructor and/or %token_destructor +** directives of the input grammar. +*/ +static void yy_destructor( + yyParser *yypParser, /* The parser */ + YYCODETYPE yymajor, /* Type code for object to destroy */ + YYMINORTYPE *yypminor /* The object to be destroyed */ +){ + ParseARG_FETCH; + switch( yymajor ){ + /* Here is inserted the actions which take place when a + ** terminal or non-terminal is destroyed. This can happen + ** when the symbol is popped from the stack during a + ** reduce or during error processing or when a parser is + ** being destroyed before it is finished parsing. + ** + ** Note: during a reduce, the only symbols destroyed are those + ** which appear on the RHS of the rule, but which are *not* used + ** inside the C code. + */ +/********* Begin destructor definitions ***************************************/ +%% +/********* End destructor definitions *****************************************/ + default: break; /* If no destructor action specified: do nothing */ + } +} + +/* +** Pop the parser's stack once. +** +** If there is a destructor routine associated with the token which +** is popped from the stack, then call it. +*/ +static void yy_pop_parser_stack(yyParser *pParser){ + yyStackEntry *yytos; + assert( pParser->yytos!=0 ); + assert( pParser->yytos > pParser->yystack ); + yytos = pParser->yytos--; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sPopping %s\n", + yyTracePrompt, + yyTokenName[yytos->major]); + fflush(yyTraceFILE); + } +#endif + yy_destructor(pParser, yytos->major, &yytos->minor); +} + +/* +** Deallocate and destroy a parser. Destructors are called for +** all stack elements before shutting the parser down. +** +** If the YYPARSEFREENEVERNULL macro exists (for example because it +** is defined in a %include section of the input grammar) then it is +** assumed that the input pointer is never NULL. +*/ +void ParseFree( + void *p, /* The parser to be deleted */ + void (CDECL *freeProc)(void*) /* Function used to reclaim memory */ +){ + yyParser *pParser = (yyParser*)p; +#ifndef YYPARSEFREENEVERNULL + if( pParser==0 ) return; +#endif + while( pParser->yytos>pParser->yystack ) yy_pop_parser_stack(pParser); +#if YYSTACKDEPTH<=0 + if( pParser->yystack!=&pParser->yystk0 ) free(pParser->yystack); +#endif + (*freeProc)((void*)pParser); +} + +/* +** Return the peak depth of the stack for a parser. +*/ +#ifdef YYTRACKMAXSTACKDEPTH +int ParseStackPeak(void *p){ + yyParser *pParser = (yyParser*)p; + return pParser->yyhwm; +} +#endif + +/* +** Find the appropriate action for a parser given the terminal +** look-ahead token iLookAhead. +*/ +static unsigned int yy_find_shift_action( + yyParser *pParser, /* The parser */ + YYCODETYPE iLookAhead /* The look-ahead token */ +){ + int i; + int stateno = pParser->yytos->stateno; + + if( stateno>=YY_MIN_REDUCE ) return stateno; + assert( stateno <= YY_SHIFT_COUNT ); + do{ + i = yy_shift_ofst[stateno]; + assert( iLookAhead!=YYNOCODE ); + i += iLookAhead; + if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){ +#ifdef YYFALLBACK + YYCODETYPE iFallback; /* Fallback token */ + if( iLookAhead %s\n", + yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]); + fflush(yyTraceFILE); + } +#endif + assert( yyFallback[iFallback]==0 ); /* Fallback loop must terminate */ + iLookAhead = iFallback; + continue; + } +#endif +#ifdef YYWILDCARD + { + int j = i - iLookAhead + YYWILDCARD; + if( +#if YY_SHIFT_MIN+YYWILDCARD<0 + j>=0 && +#endif +#if YY_SHIFT_MAX+YYWILDCARD>=YY_ACTTAB_COUNT + j0 + ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE, "%sWILDCARD %s => %s\n", + yyTracePrompt, yyTokenName[iLookAhead], + yyTokenName[YYWILDCARD]); + fflush(yyTraceFILE); + } +#endif /* NDEBUG */ + return yy_action[j]; + } + } +#endif /* YYWILDCARD */ + return yy_default[stateno]; + }else{ + return yy_action[i]; + } + }while(1); +} + +/* +** Find the appropriate action for a parser given the non-terminal +** look-ahead token iLookAhead. +*/ +static int yy_find_reduce_action( + int stateno, /* Current state number */ + YYCODETYPE iLookAhead /* The look-ahead token */ +){ + int i; +#ifdef YYERRORSYMBOL + if( stateno>YY_REDUCE_COUNT ){ + return yy_default[stateno]; + } +#else + assert( stateno<=YY_REDUCE_COUNT ); +#endif + i = yy_reduce_ofst[stateno]; + assert( i!=YY_REDUCE_USE_DFLT ); + assert( iLookAhead!=YYNOCODE ); + i += iLookAhead; +#ifdef YYERRORSYMBOL + if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){ + return yy_default[stateno]; + } +#else + assert( i>=0 && iyytos--; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt); + fflush(yyTraceFILE); + } +#endif + while( yypParser->yytos>yypParser->yystack ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will execute if the parser + ** stack every overflows */ +/******** Begin %stack_overflow code ******************************************/ +%% +/******** End %stack_overflow code ********************************************/ + ParseARG_STORE; /* Suppress warning about unused %extra_argument var */ +} + +/* +** Print tracing information for a SHIFT action +*/ +#ifndef NDEBUG +static void yyTraceShift(yyParser *yypParser, int yyNewState){ + if( yyTraceFILE ){ + if( yyNewStateyytos->major], + yyNewState); + }else{ + fprintf(yyTraceFILE,"%sShift '%s'\n", + yyTracePrompt,yyTokenName[yypParser->yytos->major]); + } + fflush(yyTraceFILE); + } +} +#else +# define yyTraceShift(X,Y) +#endif + +/* +** Perform a shift action. +*/ +static void yy_shift( + yyParser *yypParser, /* The parser to be shifted */ + int yyNewState, /* The new state to shift in */ + int yyMajor, /* The major token to shift in */ + ParseTOKENTYPE yyMinor /* The minor token to shift in */ +){ + yyStackEntry *yytos; + yypParser->yytos++; +#ifdef YYTRACKMAXSTACKDEPTH + if( (int)(yypParser->yytos - yypParser->yystack)>yypParser->yyhwm ){ + yypParser->yyhwm++; + assert( yypParser->yyhwm == (int)(yypParser->yytos - yypParser->yystack) ); + } +#endif +#if YYSTACKDEPTH>0 + if( yypParser->yytos>=&yypParser->yystack[YYSTACKDEPTH] ){ + yyStackOverflow(yypParser); + return; + } +#else + if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz] ){ + if( yyGrowStack(yypParser) ){ + yyStackOverflow(yypParser); + return; + } + } +#endif + if( yyNewState > YY_MAX_SHIFT ){ + yyNewState += YY_MIN_REDUCE - YY_MIN_SHIFTREDUCE; + } + yytos = yypParser->yytos; + yytos->stateno = (YYACTIONTYPE)yyNewState; + yytos->major = (YYCODETYPE)yyMajor; + yytos->minor.yy0 = yyMinor; + yyTraceShift(yypParser, yyNewState); +} + +/* The following table contains information about every rule that +** is used during the reduce. +*/ +static const struct { + YYCODETYPE lhs; /* Symbol on the left-hand side of the rule */ + unsigned char nrhs; /* Number of right-hand side symbols in the rule */ +} yyRuleInfo[] = { +%% +}; + +static void yy_accept(yyParser*); /* Forward Declaration */ + +/* +** Perform a reduce action and the shift that must immediately +** follow the reduce. +*/ +static void yy_reduce( + yyParser *yypParser, /* The parser */ + unsigned int yyruleno /* Number of the rule by which to reduce */ +){ + int yygoto; /* The next state */ + int yyact; /* The next action */ + yyStackEntry *yymsp; /* The top of the parser's stack */ + int yysize; /* Amount to pop the stack */ + ParseARG_FETCH; + yymsp = yypParser->yytos; +#ifndef NDEBUG + if( yyTraceFILE && yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ){ + yysize = yyRuleInfo[yyruleno].nrhs; + fprintf(yyTraceFILE, "%sReduce [%s], go to state %d.\n", yyTracePrompt, + yyRuleName[yyruleno], yymsp[-yysize].stateno); + fflush(yyTraceFILE); + } +#endif /* NDEBUG */ + + /* Check that the stack is large enough to grow by a single entry + ** if the RHS of the rule is empty. This ensures that there is room + ** enough on the stack to push the LHS value */ + if( yyRuleInfo[yyruleno].nrhs==0 ){ +#ifdef YYTRACKMAXSTACKDEPTH + if( (int)(yypParser->yytos - yypParser->yystack)>yypParser->yyhwm ){ + yypParser->yyhwm++; + assert( yypParser->yyhwm == (int)(yypParser->yytos - yypParser->yystack)); + } +#endif +#if YYSTACKDEPTH>0 + if( yypParser->yytos>=&yypParser->yystack[YYSTACKDEPTH-1] ){ + yyStackOverflow(yypParser); + return; + } +#else + if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz-1] ){ + if( yyGrowStack(yypParser) ){ + yyStackOverflow(yypParser); + return; + } + yymsp = yypParser->yytos; + } +#endif + } + + switch( yyruleno ){ + /* Beginning here are the reduction cases. A typical example + ** follows: + ** case 0: + ** #line + ** { ... } // User supplied code + ** #line + ** break; + */ +/********** Begin reduce actions **********************************************/ +%% +/********** End reduce actions ************************************************/ + }; + assert( yyrulenoYY_MAX_SHIFT ){ + yyact += YY_MIN_REDUCE - YY_MIN_SHIFTREDUCE; + } + yymsp -= yysize-1; + yypParser->yytos = yymsp; + yymsp->stateno = (YYACTIONTYPE)yyact; + yymsp->major = (YYCODETYPE)yygoto; + yyTraceShift(yypParser, yyact); + }else{ + assert( yyact == YY_ACCEPT_ACTION ); + yypParser->yytos -= yysize; + yy_accept(yypParser); + } +} + +/* +** The following code executes when the parse fails +*/ +#ifndef YYNOERRORRECOVERY +static void yy_parse_failed( + yyParser *yypParser /* The parser */ +){ + ParseARG_FETCH; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt); + fflush(yyTraceFILE); + } +#endif + while( yypParser->yytos>yypParser->yystack ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will be executed whenever the + ** parser fails */ +/************ Begin %parse_failure code ***************************************/ +%% +/************ End %parse_failure code *****************************************/ + ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} +#endif /* YYNOERRORRECOVERY */ + +/* +** The following code executes when a syntax error first occurs. +*/ +static void yy_syntax_error( + yyParser *yypParser, /* The parser */ + int yymajor, /* The major type of the error token */ + ParseTOKENTYPE yyminor /* The minor type of the error token */ +){ + ParseARG_FETCH; +#define TOKEN yyminor +/************ Begin %syntax_error code ****************************************/ +%% +/************ End %syntax_error code ******************************************/ + ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* +** The following is executed when the parser accepts +*/ +static void yy_accept( + yyParser *yypParser /* The parser */ +){ + ParseARG_FETCH; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt); + fflush(yyTraceFILE); + } +#endif +#ifndef YYNOERRORRECOVERY + yypParser->yyerrcnt = -1; +#endif +#if 0 + assert( yypParser->yytos==yypParser->yystack ); +#else + while (yypParser->yytos>yypParser->yystack) yy_pop_parser_stack(yypParser); +#endif + /* Here code is inserted which will be executed whenever the + ** parser accepts */ +/*********** Begin %parse_accept code *****************************************/ +%% +/*********** End %parse_accept code *******************************************/ + ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */ +} + +/* The main parser program. +** The first argument is a pointer to a structure obtained from +** "ParseAlloc" which describes the current state of the parser. +** The second argument is the major token number. The third is +** the minor token. The fourth optional argument is whatever the +** user wants (and specified in the grammar) and is available for +** use by the action routines. +** +** Inputs: +**
    +**
  • A pointer to the parser (an opaque structure.) +**
  • The major token number. +**
  • The minor token number. +**
  • An option argument of a grammar-specified type. +**
+** +** Outputs: +** None. +*/ +void Parse( + void *yyp, /* The parser */ + int yymajor, /* The major token code number */ + ParseTOKENTYPE yyminor /* The value for the token */ + ParseARG_PDECL /* Optional %extra_argument parameter */ +){ + YYMINORTYPE yyminorunion; + unsigned int yyact; /* The parser action. */ +#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY) + int yyendofinput; /* True if we are at the end of input */ +#endif +#ifdef YYERRORSYMBOL + int yyerrorhit = 0; /* True if yymajor has invoked an error */ +#endif + yyParser *yypParser; /* The parser */ + + yypParser = (yyParser*)yyp; + assert( yypParser->yytos!=0 ); +#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY) + yyendofinput = (yymajor==0); +#endif + ParseARG_STORE; + +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sInput '%s'\n",yyTracePrompt,yyTokenName[yymajor]); + fflush(yyTraceFILE); + } +#endif + + do{ + yyact = yy_find_shift_action(yypParser,(YYCODETYPE)yymajor); + if( yyact <= YY_MAX_SHIFTREDUCE ){ + yy_shift(yypParser,yyact,yymajor,yyminor); +#ifndef YYNOERRORRECOVERY + yypParser->yyerrcnt--; +#endif + yymajor = YYNOCODE; + }else if( yyact <= YY_MAX_REDUCE ){ + yy_reduce(yypParser,yyact-YY_MIN_REDUCE); + }else{ + assert( yyact == YY_ERROR_ACTION ); + yyminorunion.yy0 = yyminor; +#ifdef YYERRORSYMBOL + int yymx; +#endif +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sSyntax Error!\n",yyTracePrompt); + fflush(yyTraceFILE); + } +#endif +#ifdef YYERRORSYMBOL + /* A syntax error has occurred. + ** The response to an error depends upon whether or not the + ** grammar defines an error token "ERROR". + ** + ** This is what we do if the grammar does define ERROR: + ** + ** * Call the %syntax_error function. + ** + ** * Begin popping the stack until we enter a state where + ** it is legal to shift the error symbol, then shift + ** the error symbol. + ** + ** * Set the error count to three. + ** + ** * Begin accepting and shifting new tokens. No new error + ** processing will occur until three tokens have been + ** shifted successfully. + ** + */ + if( yypParser->yyerrcnt<0 ){ + yy_syntax_error(yypParser,yymajor,yyminor); + } + yymx = yypParser->yytos->major; + if( yymx==YYERRORSYMBOL || yyerrorhit ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sDiscard input token %s\n", + yyTracePrompt,yyTokenName[yymajor]); + fflush(yyTraceFILE); + } +#endif + yy_destructor(yypParser, (YYCODETYPE)yymajor, &yyminorunion); + yymajor = YYNOCODE; + }else{ + while( yypParser->yytos >= yypParser->yystack + && yymx != YYERRORSYMBOL + && (yyact = yy_find_reduce_action( + yypParser->yytos->stateno, + YYERRORSYMBOL)) >= YY_MIN_REDUCE + ){ + yy_pop_parser_stack(yypParser); + } + if( yypParser->yytos < yypParser->yystack || yymajor==0 ){ + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + yy_parse_failed(yypParser); +#ifndef YYNOERRORRECOVERY + yypParser->yyerrcnt = -1; +#endif + yymajor = YYNOCODE; + }else if( yymx!=YYERRORSYMBOL ){ + yy_shift(yypParser,yyact,YYERRORSYMBOL,yyminor); + } + } + yypParser->yyerrcnt = 3; + yyerrorhit = 1; +#elif defined(YYNOERRORRECOVERY) + /* If the YYNOERRORRECOVERY macro is defined, then do not attempt to + ** do any kind of error recovery. Instead, simply invoke the syntax + ** error routine and continue going as if nothing had happened. + ** + ** Applications can set this macro (for example inside %include) if + ** they intend to abandon the parse upon the first syntax error seen. + */ + yy_syntax_error(yypParser,yymajor, yyminor); + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + yymajor = YYNOCODE; + +#else /* YYERRORSYMBOL is not defined */ + /* This is what we do if the grammar does not define ERROR: + ** + ** * Report an error message, and throw away the input token. + ** + ** * If the input token is $, then fail the parse. + ** + ** As before, subsequent error messages are suppressed until + ** three input tokens have been successfully shifted. + */ + if( yypParser->yyerrcnt<=0 ){ + yy_syntax_error(yypParser,yymajor, yyminor); + } + yypParser->yyerrcnt = 3; + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + if( yyendofinput ){ + yy_parse_failed(yypParser); +#ifndef YYNOERRORRECOVERY + yypParser->yyerrcnt = -1; +#endif + } + yymajor = YYNOCODE; +#endif + } + }while( yymajor!=YYNOCODE && yypParser->yytos>yypParser->yystack ); +#ifndef NDEBUG + if( yyTraceFILE ){ + yyStackEntry *i; + char cDiv = '['; + fprintf(yyTraceFILE,"%sReturn. Stack=",yyTracePrompt); + for(i=&yypParser->yystack[1]; i<=yypParser->yytos; i++){ + fprintf(yyTraceFILE,"%c%s", cDiv, yyTokenName[i->major]); + cDiv = ' '; + } + fprintf(yyTraceFILE,"]\n"); + fflush(yyTraceFILE); + } +#endif + return; +}