]> git.xonotic.org Git - xonotic/darkplaces.git/blob - parser.c
Parser is now tail recursive. Implemented parse tree, self-cleaning on failure
[xonotic/darkplaces.git] / parser.c
1 /*
2 Copyright (C) 2021 David Knapp (Cloudwalk)
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20
21 #include "darkplaces.h"
22 #include "token.h"
23 #include "parser.h"
24
25 jmp_buf parse_error;
26
27 // Tell the user that their stuff is broken, why it's broken, and where it's broken, so hopefully they fix it.
28 DP_FUNC_NORETURN void Parse_Error(struct qparser_state_s *state, qparser_err_t error, const char *expected)
29 {
30         switch (error)
31         {
32         case PARSE_ERR_INVAL:
33                 if(!expected)
34                         Sys_Error("Parse_Error: Expected to expect something (expected == NULL)! Your parser is broken.");
35                 Con_Printf(CON_ERROR "Parse Error: %s: Unexpected token '%c', expected %s. Line %i, column %i\n", state->name, *state->pos, expected, state->line, state->col);
36                 break;
37         case PARSE_ERR_EOF:
38                 if(expected)
39                         Sys_Error("Parse_Error: expected != NULL when it should be NULL. Your parser is broken.");
40                 Con_Printf(CON_ERROR "Parse Error: %s: Unexpected end-of-file\n", state->name);
41                 break;
42         case PARSE_ERR_DEPTH:
43                 Con_Printf(CON_ERROR "Parse Error: %s: This file is nested too deep. Max depth of %i reached.\n", state->name, PARSER_MAX_DEPTH);
44                 break;
45         default:
46                 Sys_Error("Parse_Error: Invalid error number %i. Your parser is broken.", error);
47         }
48
49         longjmp(parse_error, 1);
50 }
51
52 // Advance forward in the stream as many times as 'count', cleanly.
53 void Parse_Next(struct qparser_state_s *state, int count)
54 {
55         const char *next = Token_Next(state->pos, count);
56
57         if(next)
58         {
59                 state->pos = next;
60                 state->col += count;
61         }
62         else
63                 Parse_Error(state, PARSE_ERR_EOF, NULL);
64 }
65
66 // Skips newlines, and handles different line endings.
67 static qbool Parse_Newline(struct qparser_state_s *state)
68 {
69         if(Token_Newline(&state->pos))
70         {
71                 state->col = 1;
72                 state->line++;
73                 return true;
74         }
75         return false;
76 }
77
78 // Skip all whitespace, as we normally know it.
79 static inline qbool Parse_Skip_Whitespace(struct qparser_state_s *state)
80 {
81         qbool ret = false;
82         // TODO: Some languages enforce indentation style. Add a callback to override this.
83         while(*state->pos == ' ' || *state->pos == '\t')
84         {
85                 Parse_Next(state, 1);
86                 ret = true;
87         }
88         return ret;
89 }
90
91 // Skips the current line. Only useful for comments.
92 static inline void Parse_Skip_Line(struct qparser_state_s *state)
93 {
94         while(!Parse_Newline(state))
95                 Parse_Next(state, 1);
96 }
97
98 static inline qbool Parse_Skip_Comments(struct qparser_state_s *state)
99 {
100         // Make sure these are both defined (or both not defined)
101         if((state->callback.CheckComment_Multiline_Start != NULL) ^ (state->callback.CheckComment_Multiline_End != NULL))
102                 Sys_Error("Parse_Skip_Comments: CheckComment_Multiline_Start (or _End) == NULL. Your parser is broken.");
103
104         // Assume language doesn't support the respective comment types if one of these are NULL.
105         if(state->callback.CheckComment_SingleLine && state->callback.CheckComment_SingleLine(state))
106                 Parse_Skip_Line(state);
107         else if(state->callback.CheckComment_Multiline_Start && state->callback.CheckComment_Multiline_Start(state))
108         {
109                 do
110                 {
111                         Parse_Next(state, 1);
112                         Parse_Newline(state);
113                 } while (state->callback.CheckComment_Multiline_End(state));
114         }
115         else
116                 return false;
117
118         return true;
119 }
120
121 // Skip all whitespace.
122 static inline void Parse_SkipToToken(struct qparser_state_s *state)
123 {
124         /*
125          * Repeat this until we run out of whitespace, newlines, and comments.
126          * state->pos should be left on non-whitespace when this returns.
127          */
128         while(Parse_Skip_Comments(state) || Parse_Skip_Whitespace(state) || Parse_Newline(state));
129 }
130
131 // Skip to the next token. Advance the pointer at least 1 if we're not sitting on whitespace.
132 char Parse_NextToken(struct qparser_state_s *state, int skip)
133 {
134         if(!state->pos)
135                 state->pos = state->buf;
136         else
137                 Parse_Next(state, 1 + skip);
138
139         Parse_SkipToToken(state);
140
141         return *state->pos;
142 }
143
144 qparser_state_t *Parse_New(const unsigned char *in)
145 {
146         qparser_state_t *out;
147
148         if(!in)
149         {
150                 Con_Printf("Parse_New: FS_LoadFile() failed");
151                 return NULL;
152         }
153
154         out = (qparser_state_t *)Z_Malloc(sizeof(qparser_state_t));
155
156         out->buf = (const char *)in;
157         out->pos = NULL;
158         out->line = 1;
159         out->col = 1;
160         out->depth = 0;
161
162         return out;
163 }
164
165 qparser_state_t *Parse_LoadFile(const char *file)
166 {
167         return Parse_New(FS_LoadFile(file, tempmempool, false, NULL));
168 }