diff options
Diffstat (limited to 'common/unqlite/jx9_compile.c')
-rw-r--r-- | common/unqlite/jx9_compile.c | 3671 |
1 files changed, 3671 insertions, 0 deletions
diff --git a/common/unqlite/jx9_compile.c b/common/unqlite/jx9_compile.c new file mode 100644 index 0000000..a7c9916 --- /dev/null +++ b/common/unqlite/jx9_compile.c | |||
@@ -0,0 +1,3671 @@ | |||
1 | /* | ||
2 | * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON. | ||
3 | * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ | ||
4 | * Version 1.7.2 | ||
5 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES | ||
6 | * please contact Symisc Systems via: | ||
7 | * legal@symisc.net | ||
8 | * licensing@symisc.net | ||
9 | * contact@symisc.net | ||
10 | * or visit: | ||
11 | * http://jx9.symisc.net/ | ||
12 | */ | ||
13 | /* $SymiscID: compile.c v1.7 FreeBSD 2012-12-11 21:46 stable <chm@symisc.net> $ */ | ||
14 | #ifndef JX9_AMALGAMATION | ||
15 | #include "jx9Int.h" | ||
16 | #endif | ||
17 | /* | ||
18 | * This file implement a thread-safe and full-reentrant compiler for the JX9 engine. | ||
19 | * That is, routines defined in this file takes a stream of tokens and output | ||
20 | * JX9 bytecode instructions. | ||
21 | */ | ||
22 | /* Forward declaration */ | ||
23 | typedef struct LangConstruct LangConstruct; | ||
24 | typedef struct JumpFixup JumpFixup; | ||
25 | /* Block [i.e: set of statements] control flags */ | ||
26 | #define GEN_BLOCK_LOOP 0x001 /* Loop block [i.e: for, while, ...] */ | ||
27 | #define GEN_BLOCK_PROTECTED 0x002 /* Protected block */ | ||
28 | #define GEN_BLOCK_COND 0x004 /* Conditional block [i.e: if(condition){} ]*/ | ||
29 | #define GEN_BLOCK_FUNC 0x008 /* Function body */ | ||
30 | #define GEN_BLOCK_GLOBAL 0x010 /* Global block (always set)*/ | ||
31 | #define GEN_BLOC_NESTED_FUNC 0x020 /* Nested function body */ | ||
32 | #define GEN_BLOCK_EXPR 0x040 /* Expression */ | ||
33 | #define GEN_BLOCK_STD 0x080 /* Standard block */ | ||
34 | #define GEN_BLOCK_SWITCH 0x100 /* Switch statement */ | ||
35 | /* | ||
36 | * Compilation of some JX9 constructs such as if, for, while, the logical or | ||
37 | * (||) and logical and (&&) operators in expressions requires the | ||
38 | * generation of forward jumps. | ||
39 | * Since the destination PC target of these jumps isn't known when the jumps | ||
40 | * are emitted, we record each forward jump in an instance of the following | ||
41 | * structure. Those jumps are fixed later when the jump destination is resolved. | ||
42 | */ | ||
43 | struct JumpFixup | ||
44 | { | ||
45 | sxi32 nJumpType; /* Jump type. Either TRUE jump, FALSE jump or Unconditional jump */ | ||
46 | sxu32 nInstrIdx; /* Instruction index to fix later when the jump destination is resolved. */ | ||
47 | }; | ||
48 | /* | ||
49 | * Each language construct is represented by an instance | ||
50 | * of the following structure. | ||
51 | */ | ||
52 | struct LangConstruct | ||
53 | { | ||
54 | sxu32 nID; /* Language construct ID [i.e: JX9_TKWRD_WHILE, JX9_TKWRD_FOR, JX9_TKWRD_IF...] */ | ||
55 | ProcLangConstruct xConstruct; /* C function implementing the language construct */ | ||
56 | }; | ||
57 | /* Compilation flags */ | ||
58 | #define JX9_COMPILE_SINGLE_STMT 0x001 /* Compile a single statement */ | ||
59 | /* Token stream synchronization macros */ | ||
60 | #define SWAP_TOKEN_STREAM(GEN, START, END)\ | ||
61 | pTmp = GEN->pEnd;\ | ||
62 | pGen->pIn = START;\ | ||
63 | pGen->pEnd = END | ||
64 | #define UPDATE_TOKEN_STREAM(GEN)\ | ||
65 | if( GEN->pIn < pTmp ){\ | ||
66 | GEN->pIn++;\ | ||
67 | }\ | ||
68 | GEN->pEnd = pTmp | ||
69 | #define SWAP_DELIMITER(GEN, START, END)\ | ||
70 | pTmpIn = GEN->pIn;\ | ||
71 | pTmpEnd = GEN->pEnd;\ | ||
72 | GEN->pIn = START;\ | ||
73 | GEN->pEnd = END | ||
74 | #define RE_SWAP_DELIMITER(GEN)\ | ||
75 | GEN->pIn = pTmpIn;\ | ||
76 | GEN->pEnd = pTmpEnd | ||
77 | /* Flags related to expression compilation */ | ||
78 | #define EXPR_FLAG_LOAD_IDX_STORE 0x001 /* Set the iP2 flag when dealing with the LOAD_IDX instruction */ | ||
79 | #define EXPR_FLAG_RDONLY_LOAD 0x002 /* Read-only load, refer to the 'JX9_OP_LOAD' VM instruction for more information */ | ||
80 | #define EXPR_FLAG_COMMA_STATEMENT 0x004 /* Treat comma expression as a single statement (used by object attributes) */ | ||
81 | /* Forward declaration */ | ||
82 | static sxi32 jx9CompileExpr( | ||
83 | jx9_gen_state *pGen, /* Code generator state */ | ||
84 | sxi32 iFlags, /* Control flags */ | ||
85 | sxi32 (*xTreeValidator)(jx9_gen_state *, jx9_expr_node *) /* Node validator callback.NULL otherwise */ | ||
86 | ); | ||
87 | |||
88 | /* | ||
89 | * Recover from a compile-time error. In other words synchronize | ||
90 | * the token stream cursor with the first semi-colon seen. | ||
91 | */ | ||
92 | static sxi32 jx9ErrorRecover(jx9_gen_state *pGen) | ||
93 | { | ||
94 | /* Synchronize with the next-semi-colon and avoid compiling this erroneous statement */ | ||
95 | while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI /*';'*/) == 0){ | ||
96 | pGen->pIn++; | ||
97 | } | ||
98 | return SXRET_OK; | ||
99 | } | ||
100 | /* | ||
101 | * Check if the given identifier name is reserved or not. | ||
102 | * Return TRUE if reserved.FALSE otherwise. | ||
103 | */ | ||
104 | static int GenStateIsReservedID(SyString *pName) | ||
105 | { | ||
106 | if( pName->nByte == sizeof("null") - 1 ){ | ||
107 | if( SyStrnicmp(pName->zString, "null", sizeof("null")-1) == 0 ){ | ||
108 | return TRUE; | ||
109 | }else if( SyStrnicmp(pName->zString, "true", sizeof("true")-1) == 0 ){ | ||
110 | return TRUE; | ||
111 | } | ||
112 | }else if( pName->nByte == sizeof("false") - 1 ){ | ||
113 | if( SyStrnicmp(pName->zString, "false", sizeof("false")-1) == 0 ){ | ||
114 | return TRUE; | ||
115 | } | ||
116 | } | ||
117 | /* Not a reserved constant */ | ||
118 | return FALSE; | ||
119 | } | ||
120 | /* | ||
121 | * Check if a given token value is installed in the literal table. | ||
122 | */ | ||
123 | static sxi32 GenStateFindLiteral(jx9_gen_state *pGen, const SyString *pValue, sxu32 *pIdx) | ||
124 | { | ||
125 | SyHashEntry *pEntry; | ||
126 | pEntry = SyHashGet(&pGen->hLiteral, (const void *)pValue->zString, pValue->nByte); | ||
127 | if( pEntry == 0 ){ | ||
128 | return SXERR_NOTFOUND; | ||
129 | } | ||
130 | *pIdx = (sxu32)SX_PTR_TO_INT(pEntry->pUserData); | ||
131 | return SXRET_OK; | ||
132 | } | ||
133 | /* | ||
134 | * Install a given constant index in the literal table. | ||
135 | * In order to be installed, the jx9_value must be of type string. | ||
136 | */ | ||
137 | static sxi32 GenStateInstallLiteral(jx9_gen_state *pGen,jx9_value *pObj, sxu32 nIdx) | ||
138 | { | ||
139 | if( SyBlobLength(&pObj->sBlob) > 0 ){ | ||
140 | SyHashInsert(&pGen->hLiteral, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob), SX_INT_TO_PTR(nIdx)); | ||
141 | } | ||
142 | return SXRET_OK; | ||
143 | } | ||
144 | /* | ||
145 | * Generate a fatal error. | ||
146 | */ | ||
147 | static sxi32 GenStateOutOfMem(jx9_gen_state *pGen) | ||
148 | { | ||
149 | jx9GenCompileError(pGen,E_ERROR,1,"Fatal, Jx9 compiler is running out of memory"); | ||
150 | /* Abort compilation immediately */ | ||
151 | return SXERR_ABORT; | ||
152 | } | ||
153 | /* | ||
154 | * Fetch a block that correspond to the given criteria from the stack of | ||
155 | * compiled blocks. | ||
156 | * Return a pointer to that block on success. NULL otherwise. | ||
157 | */ | ||
158 | static GenBlock * GenStateFetchBlock(GenBlock *pCurrent, sxi32 iBlockType, sxi32 iCount) | ||
159 | { | ||
160 | GenBlock *pBlock = pCurrent; | ||
161 | for(;;){ | ||
162 | if( pBlock->iFlags & iBlockType ){ | ||
163 | iCount--; /* Decrement nesting level */ | ||
164 | if( iCount < 1 ){ | ||
165 | /* Block meet with the desired criteria */ | ||
166 | return pBlock; | ||
167 | } | ||
168 | } | ||
169 | /* Point to the upper block */ | ||
170 | pBlock = pBlock->pParent; | ||
171 | if( pBlock == 0 || (pBlock->iFlags & (GEN_BLOCK_PROTECTED|GEN_BLOCK_FUNC)) ){ | ||
172 | /* Forbidden */ | ||
173 | break; | ||
174 | } | ||
175 | } | ||
176 | /* No such block */ | ||
177 | return 0; | ||
178 | } | ||
179 | /* | ||
180 | * Initialize a freshly allocated block instance. | ||
181 | */ | ||
182 | static void GenStateInitBlock( | ||
183 | jx9_gen_state *pGen, /* Code generator state */ | ||
184 | GenBlock *pBlock, /* Target block */ | ||
185 | sxi32 iType, /* Block type [i.e: loop, conditional, function body, etc.]*/ | ||
186 | sxu32 nFirstInstr, /* First instruction to compile */ | ||
187 | void *pUserData /* Upper layer private data */ | ||
188 | ) | ||
189 | { | ||
190 | /* Initialize block fields */ | ||
191 | pBlock->nFirstInstr = nFirstInstr; | ||
192 | pBlock->pUserData = pUserData; | ||
193 | pBlock->pGen = pGen; | ||
194 | pBlock->iFlags = iType; | ||
195 | pBlock->pParent = 0; | ||
196 | pBlock->bPostContinue = 0; | ||
197 | SySetInit(&pBlock->aJumpFix, &pGen->pVm->sAllocator, sizeof(JumpFixup)); | ||
198 | SySetInit(&pBlock->aPostContFix, &pGen->pVm->sAllocator, sizeof(JumpFixup)); | ||
199 | } | ||
200 | /* | ||
201 | * Allocate a new block instance. | ||
202 | * Return SXRET_OK and write a pointer to the new instantiated block | ||
203 | * on success.Otherwise generate a compile-time error and abort | ||
204 | * processing on failure. | ||
205 | */ | ||
206 | static sxi32 GenStateEnterBlock( | ||
207 | jx9_gen_state *pGen, /* Code generator state */ | ||
208 | sxi32 iType, /* Block type [i.e: loop, conditional, function body, etc.]*/ | ||
209 | sxu32 nFirstInstr, /* First instruction to compile */ | ||
210 | void *pUserData, /* Upper layer private data */ | ||
211 | GenBlock **ppBlock /* OUT: instantiated block */ | ||
212 | ) | ||
213 | { | ||
214 | GenBlock *pBlock; | ||
215 | /* Allocate a new block instance */ | ||
216 | pBlock = (GenBlock *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator, sizeof(GenBlock)); | ||
217 | if( pBlock == 0 ){ | ||
218 | /* If the supplied memory subsystem is so sick that we are unable to allocate | ||
219 | * a tiny chunk of memory, there is no much we can do here. | ||
220 | */ | ||
221 | return GenStateOutOfMem(pGen); | ||
222 | } | ||
223 | /* Zero the structure */ | ||
224 | SyZero(pBlock, sizeof(GenBlock)); | ||
225 | GenStateInitBlock(&(*pGen), pBlock, iType, nFirstInstr, pUserData); | ||
226 | /* Link to the parent block */ | ||
227 | pBlock->pParent = pGen->pCurrent; | ||
228 | /* Mark as the current block */ | ||
229 | pGen->pCurrent = pBlock; | ||
230 | if( ppBlock ){ | ||
231 | /* Write a pointer to the new instance */ | ||
232 | *ppBlock = pBlock; | ||
233 | } | ||
234 | return SXRET_OK; | ||
235 | } | ||
236 | /* | ||
237 | * Release block fields without freeing the whole instance. | ||
238 | */ | ||
239 | static void GenStateReleaseBlock(GenBlock *pBlock) | ||
240 | { | ||
241 | SySetRelease(&pBlock->aPostContFix); | ||
242 | SySetRelease(&pBlock->aJumpFix); | ||
243 | } | ||
244 | /* | ||
245 | * Release a block. | ||
246 | */ | ||
247 | static void GenStateFreeBlock(GenBlock *pBlock) | ||
248 | { | ||
249 | jx9_gen_state *pGen = pBlock->pGen; | ||
250 | GenStateReleaseBlock(&(*pBlock)); | ||
251 | /* Free the instance */ | ||
252 | SyMemBackendPoolFree(&pGen->pVm->sAllocator, pBlock); | ||
253 | } | ||
254 | /* | ||
255 | * POP and release a block from the stack of compiled blocks. | ||
256 | */ | ||
257 | static sxi32 GenStateLeaveBlock(jx9_gen_state *pGen, GenBlock **ppBlock) | ||
258 | { | ||
259 | GenBlock *pBlock = pGen->pCurrent; | ||
260 | if( pBlock == 0 ){ | ||
261 | /* No more block to pop */ | ||
262 | return SXERR_EMPTY; | ||
263 | } | ||
264 | /* Point to the upper block */ | ||
265 | pGen->pCurrent = pBlock->pParent; | ||
266 | if( ppBlock ){ | ||
267 | /* Write a pointer to the popped block */ | ||
268 | *ppBlock = pBlock; | ||
269 | }else{ | ||
270 | /* Safely release the block */ | ||
271 | GenStateFreeBlock(&(*pBlock)); | ||
272 | } | ||
273 | return SXRET_OK; | ||
274 | } | ||
275 | /* | ||
276 | * Emit a forward jump. | ||
277 | * Notes on forward jumps | ||
278 | * Compilation of some JX9 constructs such as if, for, while and the logical or | ||
279 | * (||) and logical and (&&) operators in expressions requires the | ||
280 | * generation of forward jumps. | ||
281 | * Since the destination PC target of these jumps isn't known when the jumps | ||
282 | * are emitted, we record each forward jump in an instance of the following | ||
283 | * structure. Those jumps are fixed later when the jump destination is resolved. | ||
284 | */ | ||
285 | static sxi32 GenStateNewJumpFixup(GenBlock *pBlock, sxi32 nJumpType, sxu32 nInstrIdx) | ||
286 | { | ||
287 | JumpFixup sJumpFix; | ||
288 | sxi32 rc; | ||
289 | /* Init the JumpFixup structure */ | ||
290 | sJumpFix.nJumpType = nJumpType; | ||
291 | sJumpFix.nInstrIdx = nInstrIdx; | ||
292 | /* Insert in the jump fixup table */ | ||
293 | rc = SySetPut(&pBlock->aJumpFix,(const void *)&sJumpFix); | ||
294 | return rc; | ||
295 | } | ||
296 | /* | ||
297 | * Fix a forward jump now the jump destination is resolved. | ||
298 | * Return the total number of fixed jumps. | ||
299 | * Notes on forward jumps: | ||
300 | * Compilation of some JX9 constructs such as if, for, while and the logical or | ||
301 | * (||) and logical and (&&) operators in expressions requires the | ||
302 | * generation of forward jumps. | ||
303 | * Since the destination PC target of these jumps isn't known when the jumps | ||
304 | * are emitted, we record each forward jump in an instance of the following | ||
305 | * structure.Those jumps are fixed later when the jump destination is resolved. | ||
306 | */ | ||
307 | static sxu32 GenStateFixJumps(GenBlock *pBlock, sxi32 nJumpType, sxu32 nJumpDest) | ||
308 | { | ||
309 | JumpFixup *aFix; | ||
310 | VmInstr *pInstr; | ||
311 | sxu32 nFixed; | ||
312 | sxu32 n; | ||
313 | /* Point to the jump fixup table */ | ||
314 | aFix = (JumpFixup *)SySetBasePtr(&pBlock->aJumpFix); | ||
315 | /* Fix the desired jumps */ | ||
316 | for( nFixed = n = 0 ; n < SySetUsed(&pBlock->aJumpFix) ; ++n ){ | ||
317 | if( aFix[n].nJumpType < 0 ){ | ||
318 | /* Already fixed */ | ||
319 | continue; | ||
320 | } | ||
321 | if( nJumpType > 0 && aFix[n].nJumpType != nJumpType ){ | ||
322 | /* Not of our interest */ | ||
323 | continue; | ||
324 | } | ||
325 | /* Point to the instruction to fix */ | ||
326 | pInstr = jx9VmGetInstr(pBlock->pGen->pVm, aFix[n].nInstrIdx); | ||
327 | if( pInstr ){ | ||
328 | pInstr->iP2 = nJumpDest; | ||
329 | nFixed++; | ||
330 | /* Mark as fixed */ | ||
331 | aFix[n].nJumpType = -1; | ||
332 | } | ||
333 | } | ||
334 | /* Total number of fixed jumps */ | ||
335 | return nFixed; | ||
336 | } | ||
337 | /* | ||
338 | * Reserve a room for a numeric constant [i.e: 64-bit integer or real number] | ||
339 | * in the constant table. | ||
340 | */ | ||
341 | static jx9_value * GenStateInstallNumLiteral(jx9_gen_state *pGen, sxu32 *pIdx) | ||
342 | { | ||
343 | jx9_value *pObj; | ||
344 | sxu32 nIdx = 0; /* cc warning */ | ||
345 | /* Reserve a new constant */ | ||
346 | pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); | ||
347 | if( pObj == 0 ){ | ||
348 | GenStateOutOfMem(pGen); | ||
349 | return 0; | ||
350 | } | ||
351 | *pIdx = nIdx; | ||
352 | /* TODO(chems): Create a numeric table (64bit int keys) same as | ||
353 | * the constant string iterals table [optimization purposes]. | ||
354 | */ | ||
355 | return pObj; | ||
356 | } | ||
357 | /* | ||
358 | * Compile a numeric [i.e: integer or real] literal. | ||
359 | * Notes on the integer type. | ||
360 | * According to the JX9 language reference manual | ||
361 | * Integers can be specified in decimal (base 10), hexadecimal (base 16), octal (base 8) | ||
362 | * or binary (base 2) notation, optionally preceded by a sign (- or +). | ||
363 | * To use octal notation, precede the number with a 0 (zero). To use hexadecimal | ||
364 | * notation precede the number with 0x. To use binary notation precede the number with 0b. | ||
365 | */ | ||
366 | static sxi32 jx9CompileNumLiteral(jx9_gen_state *pGen,sxi32 iCompileFlag) | ||
367 | { | ||
368 | SyToken *pToken = pGen->pIn; /* Raw token */ | ||
369 | sxu32 nIdx = 0; | ||
370 | if( pToken->nType & JX9_TK_INTEGER ){ | ||
371 | jx9_value *pObj; | ||
372 | sxi64 iValue; | ||
373 | iValue = jx9TokenValueToInt64(&pToken->sData); | ||
374 | pObj = GenStateInstallNumLiteral(&(*pGen), &nIdx); | ||
375 | if( pObj == 0 ){ | ||
376 | SXUNUSED(iCompileFlag); /* cc warning */ | ||
377 | return SXERR_ABORT; | ||
378 | } | ||
379 | jx9MemObjInitFromInt(pGen->pVm, pObj, iValue); | ||
380 | }else{ | ||
381 | /* Real number */ | ||
382 | jx9_value *pObj; | ||
383 | /* Reserve a new constant */ | ||
384 | pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); | ||
385 | if( pObj == 0 ){ | ||
386 | return GenStateOutOfMem(pGen); | ||
387 | } | ||
388 | jx9MemObjInitFromString(pGen->pVm, pObj, &pToken->sData); | ||
389 | jx9MemObjToReal(pObj); | ||
390 | } | ||
391 | /* Emit the load constant instruction */ | ||
392 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); | ||
393 | /* Node successfully compiled */ | ||
394 | return SXRET_OK; | ||
395 | } | ||
396 | /* | ||
397 | * Compile a nowdoc string. | ||
398 | * According to the JX9 language reference manual: | ||
399 | * | ||
400 | * Nowdocs are to single-quoted strings what heredocs are to double-quoted strings. | ||
401 | * A nowdoc is specified similarly to a heredoc, but no parsing is done inside a nowdoc. | ||
402 | * The construct is ideal for embedding JX9 code or other large blocks of text without the | ||
403 | * need for escaping. It shares some features in common with the SGML <![CDATA[ ]]> | ||
404 | * construct, in that it declares a block of text which is not for parsing. | ||
405 | * A nowdoc is identified with the same <<< sequence used for heredocs, but the identifier | ||
406 | * which follows is enclosed in single quotes, e.g. <<<'EOT'. All the rules for heredoc | ||
407 | * identifiers also apply to nowdoc identifiers, especially those regarding the appearance | ||
408 | * of the closing identifier. | ||
409 | */ | ||
410 | static sxi32 jx9CompileNowdoc(jx9_gen_state *pGen,sxi32 iCompileFlag) | ||
411 | { | ||
412 | SyString *pStr = &pGen->pIn->sData; /* Constant string literal */ | ||
413 | jx9_value *pObj; | ||
414 | sxu32 nIdx; | ||
415 | nIdx = 0; /* Prevent compiler warning */ | ||
416 | if( pStr->nByte <= 0 ){ | ||
417 | /* Empty string, load NULL */ | ||
418 | jx9VmEmitInstr(pGen->pVm,JX9_OP_LOADC, 0, 0, 0, 0); | ||
419 | return SXRET_OK; | ||
420 | } | ||
421 | /* Reserve a new constant */ | ||
422 | pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); | ||
423 | if( pObj == 0 ){ | ||
424 | jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "JX9 engine is running out of memory"); | ||
425 | SXUNUSED(iCompileFlag); /* cc warning */ | ||
426 | return SXERR_ABORT; | ||
427 | } | ||
428 | /* No processing is done here, simply a memcpy() operation */ | ||
429 | jx9MemObjInitFromString(pGen->pVm, pObj, pStr); | ||
430 | /* Emit the load constant instruction */ | ||
431 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); | ||
432 | /* Node successfully compiled */ | ||
433 | return SXRET_OK; | ||
434 | } | ||
435 | /* | ||
436 | * Compile a single quoted string. | ||
437 | * According to the JX9 language reference manual: | ||
438 | * | ||
439 | * The simplest way to specify a string is to enclose it in single quotes (the character ' ). | ||
440 | * To specify a literal single quote, escape it with a backslash (\). To specify a literal | ||
441 | * backslash, double it (\\). All other instances of backslash will be treated as a literal | ||
442 | * backslash: this means that the other escape sequences you might be used to, such as \r | ||
443 | * or \n, will be output literally as specified rather than having any special meaning. | ||
444 | * | ||
445 | */ | ||
446 | JX9_PRIVATE sxi32 jx9CompileSimpleString(jx9_gen_state *pGen, sxi32 iCompileFlag) | ||
447 | { | ||
448 | SyString *pStr = &pGen->pIn->sData; /* Constant string literal */ | ||
449 | const char *zIn, *zCur, *zEnd; | ||
450 | jx9_value *pObj; | ||
451 | sxu32 nIdx; | ||
452 | nIdx = 0; /* Prevent compiler warning */ | ||
453 | /* Delimit the string */ | ||
454 | zIn = pStr->zString; | ||
455 | zEnd = &zIn[pStr->nByte]; | ||
456 | if( zIn >= zEnd ){ | ||
457 | /* Empty string, load NULL */ | ||
458 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 0, 0, 0); | ||
459 | return SXRET_OK; | ||
460 | } | ||
461 | if( SXRET_OK == GenStateFindLiteral(&(*pGen), pStr, &nIdx) ){ | ||
462 | /* Already processed, emit the load constant instruction | ||
463 | * and return. | ||
464 | */ | ||
465 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); | ||
466 | return SXRET_OK; | ||
467 | } | ||
468 | /* Reserve a new constant */ | ||
469 | pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); | ||
470 | if( pObj == 0 ){ | ||
471 | jx9GenCompileError(&(*pGen), E_ERROR, 1, "JX9 engine is running out of memory"); | ||
472 | SXUNUSED(iCompileFlag); /* cc warning */ | ||
473 | return SXERR_ABORT; | ||
474 | } | ||
475 | jx9MemObjInitFromString(pGen->pVm, pObj, 0); | ||
476 | /* Compile the node */ | ||
477 | for(;;){ | ||
478 | if( zIn >= zEnd ){ | ||
479 | /* End of input */ | ||
480 | break; | ||
481 | } | ||
482 | zCur = zIn; | ||
483 | while( zIn < zEnd && zIn[0] != '\\' ){ | ||
484 | zIn++; | ||
485 | } | ||
486 | if( zIn > zCur ){ | ||
487 | /* Append raw contents*/ | ||
488 | jx9MemObjStringAppend(pObj, zCur, (sxu32)(zIn-zCur)); | ||
489 | } | ||
490 | zIn++; | ||
491 | if( zIn < zEnd ){ | ||
492 | if( zIn[0] == '\\' ){ | ||
493 | /* A literal backslash */ | ||
494 | jx9MemObjStringAppend(pObj, "\\", sizeof(char)); | ||
495 | }else if( zIn[0] == '\'' ){ | ||
496 | /* A single quote */ | ||
497 | jx9MemObjStringAppend(pObj, "'", sizeof(char)); | ||
498 | }else{ | ||
499 | /* verbatim copy */ | ||
500 | zIn--; | ||
501 | jx9MemObjStringAppend(pObj, zIn, sizeof(char)*2); | ||
502 | zIn++; | ||
503 | } | ||
504 | } | ||
505 | /* Advance the stream cursor */ | ||
506 | zIn++; | ||
507 | } | ||
508 | /* Emit the load constant instruction */ | ||
509 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); | ||
510 | if( pStr->nByte < 1024 ){ | ||
511 | /* Install in the literal table */ | ||
512 | GenStateInstallLiteral(pGen, pObj, nIdx); | ||
513 | } | ||
514 | /* Node successfully compiled */ | ||
515 | return SXRET_OK; | ||
516 | } | ||
517 | /* | ||
518 | * Process variable expression [i.e: "$var", "${var}"] embedded in a double quoted/heredoc string. | ||
519 | * According to the JX9 language reference manual | ||
520 | * When a string is specified in double quotes or with heredoc, variables are parsed within it. | ||
521 | * There are two types of syntax: a simple one and a complex one. The simple syntax is the most | ||
522 | * common and convenient. It provides a way to embed a variable, an array value, or an object | ||
523 | * property in a string with a minimum of effort. | ||
524 | * Simple syntax | ||
525 | * If a dollar sign ($) is encountered, the parser will greedily take as many tokens as possible | ||
526 | * to form a valid variable name. Enclose the variable name in curly braces to explicitly specify | ||
527 | * the end of the name. | ||
528 | * Similarly, an array index or an object property can be parsed. With array indices, the closing | ||
529 | * square bracket (]) marks the end of the index. The same rules apply to object properties | ||
530 | * as to simple variables. | ||
531 | * Complex (curly) syntax | ||
532 | * This isn't called complex because the syntax is complex, but because it allows for the use | ||
533 | * of complex expressions. | ||
534 | * Any scalar variable, array element or object property with a string representation can be | ||
535 | * included via this syntax. Simply write the expression the same way as it would appear outside | ||
536 | * the string, and then wrap it in { and }. Since { can not be escaped, this syntax will only | ||
537 | * be recognised when the $ immediately follows the {. Use {\$ to get a literal {$ | ||
538 | */ | ||
539 | static sxi32 GenStateProcessStringExpression( | ||
540 | jx9_gen_state *pGen, /* Code generator state */ | ||
541 | const char *zIn, /* Raw expression */ | ||
542 | const char *zEnd /* End of the expression */ | ||
543 | ) | ||
544 | { | ||
545 | SyToken *pTmpIn, *pTmpEnd; | ||
546 | SySet sToken; | ||
547 | sxi32 rc; | ||
548 | /* Initialize the token set */ | ||
549 | SySetInit(&sToken, &pGen->pVm->sAllocator, sizeof(SyToken)); | ||
550 | /* Preallocate some slots */ | ||
551 | SySetAlloc(&sToken, 0x08); | ||
552 | /* Tokenize the text */ | ||
553 | jx9Tokenize(zIn,(sxu32)(zEnd-zIn),&sToken); | ||
554 | /* Swap delimiter */ | ||
555 | pTmpIn = pGen->pIn; | ||
556 | pTmpEnd = pGen->pEnd; | ||
557 | pGen->pIn = (SyToken *)SySetBasePtr(&sToken); | ||
558 | pGen->pEnd = &pGen->pIn[SySetUsed(&sToken)]; | ||
559 | /* Compile the expression */ | ||
560 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
561 | /* Restore token stream */ | ||
562 | pGen->pIn = pTmpIn; | ||
563 | pGen->pEnd = pTmpEnd; | ||
564 | /* Release the token set */ | ||
565 | SySetRelease(&sToken); | ||
566 | /* Compilation result */ | ||
567 | return rc; | ||
568 | } | ||
569 | /* | ||
570 | * Reserve a new constant for a double quoted/heredoc string. | ||
571 | */ | ||
572 | static jx9_value * GenStateNewStrObj(jx9_gen_state *pGen,sxi32 *pCount) | ||
573 | { | ||
574 | jx9_value *pConstObj; | ||
575 | sxu32 nIdx = 0; | ||
576 | /* Reserve a new constant */ | ||
577 | pConstObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); | ||
578 | if( pConstObj == 0 ){ | ||
579 | GenStateOutOfMem(&(*pGen)); | ||
580 | return 0; | ||
581 | } | ||
582 | (*pCount)++; | ||
583 | jx9MemObjInitFromString(pGen->pVm, pConstObj, 0); | ||
584 | /* Emit the load constant instruction */ | ||
585 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); | ||
586 | return pConstObj; | ||
587 | } | ||
588 | /* | ||
589 | * Compile a double quoted/heredoc string. | ||
590 | * According to the JX9 language reference manual | ||
591 | * Heredoc | ||
592 | * A third way to delimit strings is the heredoc syntax: <<<. After this operator, an identifier | ||
593 | * is provided, then a newline. The string itself follows, and then the same identifier again | ||
594 | * to close the quotation. | ||
595 | * The closing identifier must begin in the first column of the line. Also, the identifier must | ||
596 | * follow the same naming rules as any other label in JX9: it must contain only alphanumeric | ||
597 | * characters and underscores, and must start with a non-digit character or underscore. | ||
598 | * Warning | ||
599 | * It is very important to note that the line with the closing identifier must contain | ||
600 | * no other characters, except possibly a semicolon (;). That means especially that the identifier | ||
601 | * may not be indented, and there may not be any spaces or tabs before or after the semicolon. | ||
602 | * It's also important to realize that the first character before the closing identifier must | ||
603 | * be a newline as defined by the local operating system. This is \n on UNIX systems, including Mac OS X. | ||
604 | * The closing delimiter (possibly followed by a semicolon) must also be followed by a newline. | ||
605 | * If this rule is broken and the closing identifier is not "clean", it will not be considered a closing | ||
606 | * identifier, and JX9 will continue looking for one. If a proper closing identifier is not found before | ||
607 | * the end of the current file, a parse error will result at the last line. | ||
608 | * Heredocs can not be used for initializing object properties. | ||
609 | * Double quoted | ||
610 | * If the string is enclosed in double-quotes ("), JX9 will interpret more escape sequences for special characters: | ||
611 | * Escaped characters Sequence Meaning | ||
612 | * \n linefeed (LF or 0x0A (10) in ASCII) | ||
613 | * \r carriage return (CR or 0x0D (13) in ASCII) | ||
614 | * \t horizontal tab (HT or 0x09 (9) in ASCII) | ||
615 | * \v vertical tab (VT or 0x0B (11) in ASCII) | ||
616 | * \f form feed (FF or 0x0C (12) in ASCII) | ||
617 | * \\ backslash | ||
618 | * \$ dollar sign | ||
619 | * \" double-quote | ||
620 | * \[0-7]{1, 3} the sequence of characters matching the regular expression is a character in octal notation | ||
621 | * \x[0-9A-Fa-f]{1, 2} the sequence of characters matching the regular expression is a character in hexadecimal notation | ||
622 | * As in single quoted strings, escaping any other character will result in the backslash being printed too. | ||
623 | * The most important feature of double-quoted strings is the fact that variable names will be expanded. | ||
624 | * See string parsing for details. | ||
625 | */ | ||
626 | static sxi32 GenStateCompileString(jx9_gen_state *pGen) | ||
627 | { | ||
628 | SyString *pStr = &pGen->pIn->sData; /* Raw token value */ | ||
629 | const char *zIn, *zCur, *zEnd; | ||
630 | jx9_value *pObj = 0; | ||
631 | sxi32 iCons; | ||
632 | sxi32 rc; | ||
633 | /* Delimit the string */ | ||
634 | zIn = pStr->zString; | ||
635 | zEnd = &zIn[pStr->nByte]; | ||
636 | if( zIn >= zEnd ){ | ||
637 | /* Empty string, load NULL */ | ||
638 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 0, 0, 0); | ||
639 | return SXRET_OK; | ||
640 | } | ||
641 | zCur = 0; | ||
642 | /* Compile the node */ | ||
643 | iCons = 0; | ||
644 | for(;;){ | ||
645 | zCur = zIn; | ||
646 | while( zIn < zEnd && zIn[0] != '\\' ){ | ||
647 | if(zIn[0] == '$' && &zIn[1] < zEnd && | ||
648 | (((unsigned char)zIn[1] >= 0xc0 || SyisAlpha(zIn[1]) || zIn[1] == '_')) ){ | ||
649 | break; | ||
650 | } | ||
651 | zIn++; | ||
652 | } | ||
653 | if( zIn > zCur ){ | ||
654 | if( pObj == 0 ){ | ||
655 | pObj = GenStateNewStrObj(&(*pGen), &iCons); | ||
656 | if( pObj == 0 ){ | ||
657 | return SXERR_ABORT; | ||
658 | } | ||
659 | } | ||
660 | jx9MemObjStringAppend(pObj, zCur, (sxu32)(zIn-zCur)); | ||
661 | } | ||
662 | if( zIn >= zEnd ){ | ||
663 | break; | ||
664 | } | ||
665 | if( zIn[0] == '\\' ){ | ||
666 | const char *zPtr = 0; | ||
667 | sxu32 n; | ||
668 | zIn++; | ||
669 | if( zIn >= zEnd ){ | ||
670 | break; | ||
671 | } | ||
672 | if( pObj == 0 ){ | ||
673 | pObj = GenStateNewStrObj(&(*pGen), &iCons); | ||
674 | if( pObj == 0 ){ | ||
675 | return SXERR_ABORT; | ||
676 | } | ||
677 | } | ||
678 | n = sizeof(char); /* size of conversion */ | ||
679 | switch( zIn[0] ){ | ||
680 | case '$': | ||
681 | /* Dollar sign */ | ||
682 | jx9MemObjStringAppend(pObj, "$", sizeof(char)); | ||
683 | break; | ||
684 | case '\\': | ||
685 | /* A literal backslash */ | ||
686 | jx9MemObjStringAppend(pObj, "\\", sizeof(char)); | ||
687 | break; | ||
688 | case 'a': | ||
689 | /* The "alert" character (BEL)[ctrl+g] ASCII code 7 */ | ||
690 | jx9MemObjStringAppend(pObj, "\a", sizeof(char)); | ||
691 | break; | ||
692 | case 'b': | ||
693 | /* Backspace (BS)[ctrl+h] ASCII code 8 */ | ||
694 | jx9MemObjStringAppend(pObj, "\b", sizeof(char)); | ||
695 | break; | ||
696 | case 'f': | ||
697 | /* Form-feed (FF)[ctrl+l] ASCII code 12 */ | ||
698 | jx9MemObjStringAppend(pObj, "\f", sizeof(char)); | ||
699 | break; | ||
700 | case 'n': | ||
701 | /* Line feed(new line) (LF)[ctrl+j] ASCII code 10 */ | ||
702 | jx9MemObjStringAppend(pObj, "\n", sizeof(char)); | ||
703 | break; | ||
704 | case 'r': | ||
705 | /* Carriage return (CR)[ctrl+m] ASCII code 13 */ | ||
706 | jx9MemObjStringAppend(pObj, "\r", sizeof(char)); | ||
707 | break; | ||
708 | case 't': | ||
709 | /* Horizontal tab (HT)[ctrl+i] ASCII code 9 */ | ||
710 | jx9MemObjStringAppend(pObj, "\t", sizeof(char)); | ||
711 | break; | ||
712 | case 'v': | ||
713 | /* Vertical tab(VT)[ctrl+k] ASCII code 11 */ | ||
714 | jx9MemObjStringAppend(pObj, "\v", sizeof(char)); | ||
715 | break; | ||
716 | case '\'': | ||
717 | /* Single quote */ | ||
718 | jx9MemObjStringAppend(pObj, "'", sizeof(char)); | ||
719 | break; | ||
720 | case '"': | ||
721 | /* Double quote */ | ||
722 | jx9MemObjStringAppend(pObj, "\"", sizeof(char)); | ||
723 | break; | ||
724 | case '0': | ||
725 | /* NUL byte */ | ||
726 | jx9MemObjStringAppend(pObj, "\0", sizeof(char)); | ||
727 | break; | ||
728 | case 'x': | ||
729 | if((unsigned char)zIn[1] < 0xc0 && SyisHex(zIn[1]) ){ | ||
730 | int c; | ||
731 | /* Hex digit */ | ||
732 | c = SyHexToint(zIn[1]) << 4; | ||
733 | if( &zIn[2] < zEnd ){ | ||
734 | c += SyHexToint(zIn[2]); | ||
735 | } | ||
736 | /* Output char */ | ||
737 | jx9MemObjStringAppend(pObj, (const char *)&c, sizeof(char)); | ||
738 | n += sizeof(char) * 2; | ||
739 | }else{ | ||
740 | /* Output literal character */ | ||
741 | jx9MemObjStringAppend(pObj, "x", sizeof(char)); | ||
742 | } | ||
743 | break; | ||
744 | case 'o': | ||
745 | if( &zIn[1] < zEnd && (unsigned char)zIn[1] < 0xc0 && SyisDigit(zIn[1]) && (zIn[1] - '0') < 8 ){ | ||
746 | /* Octal digit stream */ | ||
747 | int c; | ||
748 | c = 0; | ||
749 | zIn++; | ||
750 | for( zPtr = zIn ; zPtr < &zIn[3*sizeof(char)] ; zPtr++ ){ | ||
751 | if( zPtr >= zEnd || (unsigned char)zPtr[0] >= 0xc0 || !SyisDigit(zPtr[0]) || (zPtr[0] - '0') > 7 ){ | ||
752 | break; | ||
753 | } | ||
754 | c = c * 8 + (zPtr[0] - '0'); | ||
755 | } | ||
756 | if ( c > 0 ){ | ||
757 | jx9MemObjStringAppend(pObj, (const char *)&c, sizeof(char)); | ||
758 | } | ||
759 | n = (sxu32)(zPtr-zIn); | ||
760 | }else{ | ||
761 | /* Output literal character */ | ||
762 | jx9MemObjStringAppend(pObj, "o", sizeof(char)); | ||
763 | } | ||
764 | break; | ||
765 | default: | ||
766 | /* Output without a slash */ | ||
767 | jx9MemObjStringAppend(pObj, zIn, sizeof(char)); | ||
768 | break; | ||
769 | } | ||
770 | /* Advance the stream cursor */ | ||
771 | zIn += n; | ||
772 | continue; | ||
773 | } | ||
774 | if( zIn[0] == '{' ){ | ||
775 | /* Curly syntax */ | ||
776 | const char *zExpr; | ||
777 | sxi32 iNest = 1; | ||
778 | zIn++; | ||
779 | zExpr = zIn; | ||
780 | /* Synchronize with the next closing curly braces */ | ||
781 | while( zIn < zEnd ){ | ||
782 | if( zIn[0] == '{' ){ | ||
783 | /* Increment nesting level */ | ||
784 | iNest++; | ||
785 | }else if(zIn[0] == '}' ){ | ||
786 | /* Decrement nesting level */ | ||
787 | iNest--; | ||
788 | if( iNest <= 0 ){ | ||
789 | break; | ||
790 | } | ||
791 | } | ||
792 | zIn++; | ||
793 | } | ||
794 | /* Process the expression */ | ||
795 | rc = GenStateProcessStringExpression(&(*pGen),zExpr,zIn); | ||
796 | if( rc == SXERR_ABORT ){ | ||
797 | return SXERR_ABORT; | ||
798 | } | ||
799 | if( rc != SXERR_EMPTY ){ | ||
800 | ++iCons; | ||
801 | } | ||
802 | if( zIn < zEnd ){ | ||
803 | /* Jump the trailing curly */ | ||
804 | zIn++; | ||
805 | } | ||
806 | }else{ | ||
807 | /* Simple syntax */ | ||
808 | const char *zExpr = zIn; | ||
809 | /* Assemble variable name */ | ||
810 | for(;;){ | ||
811 | /* Jump leading dollars */ | ||
812 | while( zIn < zEnd && zIn[0] == '$' ){ | ||
813 | zIn++; | ||
814 | } | ||
815 | for(;;){ | ||
816 | while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && (SyisAlphaNum(zIn[0]) || zIn[0] == '_' ) ){ | ||
817 | zIn++; | ||
818 | } | ||
819 | if((unsigned char)zIn[0] >= 0xc0 ){ | ||
820 | /* UTF-8 stream */ | ||
821 | zIn++; | ||
822 | while( zIn < zEnd && (((unsigned char)zIn[0] & 0xc0) == 0x80) ){ | ||
823 | zIn++; | ||
824 | } | ||
825 | continue; | ||
826 | } | ||
827 | break; | ||
828 | } | ||
829 | if( zIn >= zEnd ){ | ||
830 | break; | ||
831 | } | ||
832 | if( zIn[0] == '[' ){ | ||
833 | sxi32 iSquare = 1; | ||
834 | zIn++; | ||
835 | while( zIn < zEnd ){ | ||
836 | if( zIn[0] == '[' ){ | ||
837 | iSquare++; | ||
838 | }else if (zIn[0] == ']' ){ | ||
839 | iSquare--; | ||
840 | if( iSquare <= 0 ){ | ||
841 | break; | ||
842 | } | ||
843 | } | ||
844 | zIn++; | ||
845 | } | ||
846 | if( zIn < zEnd ){ | ||
847 | zIn++; | ||
848 | } | ||
849 | break; | ||
850 | }else if( zIn[0] == '.' ){ | ||
851 | /* Member access operator '.' */ | ||
852 | zIn++; | ||
853 | }else{ | ||
854 | break; | ||
855 | } | ||
856 | } | ||
857 | /* Process the expression */ | ||
858 | rc = GenStateProcessStringExpression(&(*pGen),zExpr, zIn); | ||
859 | if( rc == SXERR_ABORT ){ | ||
860 | return SXERR_ABORT; | ||
861 | } | ||
862 | if( rc != SXERR_EMPTY ){ | ||
863 | ++iCons; | ||
864 | } | ||
865 | } | ||
866 | /* Invalidate the previously used constant */ | ||
867 | pObj = 0; | ||
868 | }/*for(;;)*/ | ||
869 | if( iCons > 1 ){ | ||
870 | /* Concatenate all compiled constants */ | ||
871 | jx9VmEmitInstr(pGen->pVm, JX9_OP_CAT, iCons, 0, 0, 0); | ||
872 | } | ||
873 | /* Node successfully compiled */ | ||
874 | return SXRET_OK; | ||
875 | } | ||
876 | /* | ||
877 | * Compile a double quoted string. | ||
878 | * See the block-comment above for more information. | ||
879 | */ | ||
880 | JX9_PRIVATE sxi32 jx9CompileString(jx9_gen_state *pGen, sxi32 iCompileFlag) | ||
881 | { | ||
882 | sxi32 rc; | ||
883 | rc = GenStateCompileString(&(*pGen)); | ||
884 | SXUNUSED(iCompileFlag); /* cc warning */ | ||
885 | /* Compilation result */ | ||
886 | return rc; | ||
887 | } | ||
888 | /* | ||
889 | * Compile a literal which is an identifier(name) for simple values. | ||
890 | */ | ||
891 | JX9_PRIVATE sxi32 jx9CompileLiteral(jx9_gen_state *pGen,sxi32 iCompileFlag) | ||
892 | { | ||
893 | SyToken *pToken = pGen->pIn; | ||
894 | jx9_value *pObj; | ||
895 | SyString *pStr; | ||
896 | sxu32 nIdx; | ||
897 | /* Extract token value */ | ||
898 | pStr = &pToken->sData; | ||
899 | /* Deal with the reserved literals [i.e: null, false, true, ...] first */ | ||
900 | if( pStr->nByte == sizeof("NULL") - 1 ){ | ||
901 | if( SyStrnicmp(pStr->zString, "null", sizeof("NULL")-1) == 0 ){ | ||
902 | /* NULL constant are always indexed at 0 */ | ||
903 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 0, 0, 0); | ||
904 | return SXRET_OK; | ||
905 | }else if( SyStrnicmp(pStr->zString, "true", sizeof("TRUE")-1) == 0 ){ | ||
906 | /* TRUE constant are always indexed at 1 */ | ||
907 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 1, 0, 0); | ||
908 | return SXRET_OK; | ||
909 | } | ||
910 | }else if (pStr->nByte == sizeof("FALSE") - 1 && | ||
911 | SyStrnicmp(pStr->zString, "false", sizeof("FALSE")-1) == 0 ){ | ||
912 | /* FALSE constant are always indexed at 2 */ | ||
913 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 2, 0, 0); | ||
914 | return SXRET_OK; | ||
915 | }else if(pStr->nByte == sizeof("__LINE__") - 1 && | ||
916 | SyMemcmp(pStr->zString, "__LINE__", sizeof("__LINE__")-1) == 0 ){ | ||
917 | /* TICKET 1433-004: __LINE__ constant must be resolved at compile time, not run time */ | ||
918 | pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); | ||
919 | if( pObj == 0 ){ | ||
920 | SXUNUSED(iCompileFlag); /* cc warning */ | ||
921 | return GenStateOutOfMem(pGen); | ||
922 | } | ||
923 | jx9MemObjInitFromInt(pGen->pVm, pObj, pToken->nLine); | ||
924 | /* Emit the load constant instruction */ | ||
925 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); | ||
926 | return SXRET_OK; | ||
927 | }else if( pStr->nByte == sizeof("__FUNCTION__") - 1 && | ||
928 | SyMemcmp(pStr->zString, "__FUNCTION__", sizeof("__FUNCTION__")-1) == 0 ){ | ||
929 | GenBlock *pBlock = pGen->pCurrent; | ||
930 | /* TICKET 1433-004: __FUNCTION__/__METHOD__ constants must be resolved at compile time, not run time */ | ||
931 | while( pBlock && (pBlock->iFlags & GEN_BLOCK_FUNC) == 0 ){ | ||
932 | /* Point to the upper block */ | ||
933 | pBlock = pBlock->pParent; | ||
934 | } | ||
935 | if( pBlock == 0 ){ | ||
936 | /* Called in the global scope, load NULL */ | ||
937 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 0, 0, 0); | ||
938 | }else{ | ||
939 | /* Extract the target function/method */ | ||
940 | jx9_vm_func *pFunc = (jx9_vm_func *)pBlock->pUserData; | ||
941 | pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); | ||
942 | if( pObj == 0 ){ | ||
943 | return GenStateOutOfMem(pGen); | ||
944 | } | ||
945 | jx9MemObjInitFromString(pGen->pVm, pObj, &pFunc->sName); | ||
946 | /* Emit the load constant instruction */ | ||
947 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); | ||
948 | } | ||
949 | return SXRET_OK; | ||
950 | } | ||
951 | /* Query literal table */ | ||
952 | if( SXRET_OK != GenStateFindLiteral(&(*pGen), &pToken->sData, &nIdx) ){ | ||
953 | jx9_value *pObj; | ||
954 | /* Unknown literal, install it in the literal table */ | ||
955 | pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); | ||
956 | if( pObj == 0 ){ | ||
957 | return GenStateOutOfMem(pGen); | ||
958 | } | ||
959 | jx9MemObjInitFromString(pGen->pVm, pObj, &pToken->sData); | ||
960 | GenStateInstallLiteral(&(*pGen), pObj, nIdx); | ||
961 | } | ||
962 | /* Emit the load constant instruction */ | ||
963 | jx9VmEmitInstr(pGen->pVm,JX9_OP_LOADC,1,nIdx, 0, 0); | ||
964 | /* Node successfully compiled */ | ||
965 | return SXRET_OK; | ||
966 | } | ||
967 | /* | ||
968 | * Compile an array entry whether it is a key or a value. | ||
969 | */ | ||
970 | static sxi32 GenStateCompileJSONEntry( | ||
971 | jx9_gen_state *pGen, /* Code generator state */ | ||
972 | SyToken *pIn, /* Token stream */ | ||
973 | SyToken *pEnd, /* End of the token stream */ | ||
974 | sxi32 iFlags, /* Compilation flags */ | ||
975 | sxi32 (*xValidator)(jx9_gen_state *,jx9_expr_node *) /* Expression tree validator callback */ | ||
976 | ) | ||
977 | { | ||
978 | SyToken *pTmpIn, *pTmpEnd; | ||
979 | sxi32 rc; | ||
980 | /* Swap token stream */ | ||
981 | SWAP_DELIMITER(pGen, pIn, pEnd); | ||
982 | /* Compile the expression*/ | ||
983 | rc = jx9CompileExpr(&(*pGen), iFlags, xValidator); | ||
984 | /* Restore token stream */ | ||
985 | RE_SWAP_DELIMITER(pGen); | ||
986 | return rc; | ||
987 | } | ||
988 | /* | ||
989 | * Compile a Jx9 JSON Array. | ||
990 | */ | ||
991 | JX9_PRIVATE sxi32 jx9CompileJsonArray(jx9_gen_state *pGen, sxi32 iCompileFlag) | ||
992 | { | ||
993 | sxi32 nPair = 0; | ||
994 | SyToken *pCur; | ||
995 | sxi32 rc; | ||
996 | |||
997 | pGen->pIn++; /* Jump the open square bracket '['*/ | ||
998 | pGen->pEnd--; | ||
999 | SXUNUSED(iCompileFlag); /* cc warning */ | ||
1000 | for(;;){ | ||
1001 | /* Jump leading commas */ | ||
1002 | while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_COMMA) ){ | ||
1003 | pGen->pIn++; | ||
1004 | } | ||
1005 | pCur = pGen->pIn; | ||
1006 | if( SXRET_OK != jx9GetNextExpr(pGen->pIn, pGen->pEnd, &pGen->pIn) ){ | ||
1007 | /* No more entry to process */ | ||
1008 | break; | ||
1009 | } | ||
1010 | /* Compile entry */ | ||
1011 | rc = GenStateCompileJSONEntry(&(*pGen),pCur,pGen->pIn,EXPR_FLAG_RDONLY_LOAD/*Do not create the variable if inexistant*/,0); | ||
1012 | if( rc == SXERR_ABORT ){ | ||
1013 | return SXERR_ABORT; | ||
1014 | } | ||
1015 | nPair++; | ||
1016 | } | ||
1017 | /* Emit the load map instruction */ | ||
1018 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOAD_MAP,nPair,0,0,0); | ||
1019 | /* Node successfully compiled */ | ||
1020 | return SXRET_OK; | ||
1021 | } | ||
1022 | /* | ||
1023 | * Node validator for a given JSON key. | ||
1024 | */ | ||
1025 | static sxi32 GenStateJSONObjectKeyNodeValidator(jx9_gen_state *pGen,jx9_expr_node *pRoot) | ||
1026 | { | ||
1027 | sxi32 rc = SXRET_OK; | ||
1028 | if( pRoot->xCode != jx9CompileVariable && pRoot->xCode != jx9CompileString | ||
1029 | && pRoot->xCode != jx9CompileSimpleString && pRoot->xCode != jx9CompileLiteral ){ | ||
1030 | /* Unexpected expression */ | ||
1031 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pRoot->pStart? pRoot->pStart->nLine : 0, | ||
1032 | "JSON Object: Unexpected expression, key must be of type string, literal or simple variable"); | ||
1033 | if( rc != SXERR_ABORT ){ | ||
1034 | rc = SXERR_INVALID; | ||
1035 | } | ||
1036 | } | ||
1037 | return rc; | ||
1038 | } | ||
1039 | /* | ||
1040 | * Compile a Jx9 JSON Object | ||
1041 | */ | ||
1042 | JX9_PRIVATE sxi32 jx9CompileJsonObject(jx9_gen_state *pGen, sxi32 iCompileFlag) | ||
1043 | { | ||
1044 | SyToken *pKey, *pCur; | ||
1045 | sxi32 nPair = 0; | ||
1046 | sxi32 rc; | ||
1047 | |||
1048 | pGen->pIn++; /* Jump the open querly braces '{'*/ | ||
1049 | pGen->pEnd--; | ||
1050 | SXUNUSED(iCompileFlag); /* cc warning */ | ||
1051 | for(;;){ | ||
1052 | /* Jump leading commas */ | ||
1053 | while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_COMMA) ){ | ||
1054 | pGen->pIn++; | ||
1055 | } | ||
1056 | pCur = pGen->pIn; | ||
1057 | if( SXRET_OK != jx9GetNextExpr(pGen->pIn, pGen->pEnd, &pGen->pIn) ){ | ||
1058 | /* No more entry to process */ | ||
1059 | break; | ||
1060 | } | ||
1061 | /* Compile the key */ | ||
1062 | pKey = pCur; | ||
1063 | while( pCur < pGen->pIn ){ | ||
1064 | if( pCur->nType & JX9_TK_COLON /*':'*/ ){ | ||
1065 | break; | ||
1066 | } | ||
1067 | pCur++; | ||
1068 | } | ||
1069 | rc = SXERR_EMPTY; | ||
1070 | if( pCur < pGen->pIn ){ | ||
1071 | if( &pCur[1] >= pGen->pIn ){ | ||
1072 | /* Missing value */ | ||
1073 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pCur->nLine, "JSON Object: Missing entry value"); | ||
1074 | if( rc == SXERR_ABORT ){ | ||
1075 | return SXERR_ABORT; | ||
1076 | } | ||
1077 | return SXRET_OK; | ||
1078 | } | ||
1079 | /* Compile the expression holding the key */ | ||
1080 | rc = GenStateCompileJSONEntry(&(*pGen), pKey, pCur, | ||
1081 | EXPR_FLAG_RDONLY_LOAD /* Do not create the variable if inexistant */, | ||
1082 | GenStateJSONObjectKeyNodeValidator /* Node validator callback */ | ||
1083 | ); | ||
1084 | if( rc == SXERR_ABORT ){ | ||
1085 | return SXERR_ABORT; | ||
1086 | } | ||
1087 | pCur++; /* Jump the double colon ':' */ | ||
1088 | }else if( pKey == pCur ){ | ||
1089 | /* Key is omitted, emit an error */ | ||
1090 | jx9GenCompileError(&(*pGen),E_ERROR, pCur->nLine, "JSON Object: Missing entry key"); | ||
1091 | pCur++; /* Jump the double colon ':' */ | ||
1092 | }else{ | ||
1093 | /* Reset back the cursor and point to the entry value */ | ||
1094 | pCur = pKey; | ||
1095 | } | ||
1096 | /* Compile indice value */ | ||
1097 | rc = GenStateCompileJSONEntry(&(*pGen), pCur, pGen->pIn, EXPR_FLAG_RDONLY_LOAD/*Do not create the variable if inexistant*/,0); | ||
1098 | if( rc == SXERR_ABORT ){ | ||
1099 | return SXERR_ABORT; | ||
1100 | } | ||
1101 | nPair++; | ||
1102 | } | ||
1103 | /* Emit the load map instruction */ | ||
1104 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOAD_MAP, nPair * 2, 1, 0, 0); | ||
1105 | /* Node successfully compiled */ | ||
1106 | return SXRET_OK; | ||
1107 | } | ||
1108 | /* | ||
1109 | * Compile a function [i.e: print, exit(), include(), ...] which is a langauge | ||
1110 | * construct. | ||
1111 | */ | ||
1112 | JX9_PRIVATE sxi32 jx9CompileLangConstruct(jx9_gen_state *pGen,sxi32 iCompileFlag) | ||
1113 | { | ||
1114 | SyString *pName; | ||
1115 | sxu32 nKeyID; | ||
1116 | sxi32 rc; | ||
1117 | /* Name of the language construct [i.e: print, die...]*/ | ||
1118 | pName = &pGen->pIn->sData; | ||
1119 | nKeyID = (sxu32)SX_PTR_TO_INT(pGen->pIn->pUserData); | ||
1120 | pGen->pIn++; /* Jump the language construct keyword */ | ||
1121 | if( nKeyID == JX9_TKWRD_PRINT ){ | ||
1122 | SyToken *pTmp, *pNext = 0; | ||
1123 | /* Compile arguments one after one */ | ||
1124 | pTmp = pGen->pEnd; | ||
1125 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 1 /* Boolean true index */, 0, 0); | ||
1126 | while( SXRET_OK == jx9GetNextExpr(pGen->pIn, pTmp, &pNext) ){ | ||
1127 | if( pGen->pIn < pNext ){ | ||
1128 | pGen->pEnd = pNext; | ||
1129 | rc = jx9CompileExpr(&(*pGen), EXPR_FLAG_RDONLY_LOAD/* Do not create variable if inexistant */, 0); | ||
1130 | if( rc == SXERR_ABORT ){ | ||
1131 | return SXERR_ABORT; | ||
1132 | } | ||
1133 | if( rc != SXERR_EMPTY ){ | ||
1134 | /* Ticket 1433-008: Optimization #1: Consume input directly | ||
1135 | * without the overhead of a function call. | ||
1136 | * This is a very powerful optimization that improve | ||
1137 | * performance greatly. | ||
1138 | */ | ||
1139 | jx9VmEmitInstr(pGen->pVm,JX9_OP_CONSUME,1,0,0,0); | ||
1140 | } | ||
1141 | } | ||
1142 | /* Jump trailing commas */ | ||
1143 | while( pNext < pTmp && (pNext->nType & JX9_TK_COMMA) ){ | ||
1144 | pNext++; | ||
1145 | } | ||
1146 | pGen->pIn = pNext; | ||
1147 | } | ||
1148 | /* Restore token stream */ | ||
1149 | pGen->pEnd = pTmp; | ||
1150 | }else{ | ||
1151 | sxi32 nArg = 0; | ||
1152 | sxu32 nIdx = 0; | ||
1153 | rc = jx9CompileExpr(&(*pGen), EXPR_FLAG_RDONLY_LOAD/* Do not create variable if inexistant */, 0); | ||
1154 | if( rc == SXERR_ABORT ){ | ||
1155 | return SXERR_ABORT; | ||
1156 | }else if(rc != SXERR_EMPTY ){ | ||
1157 | nArg = 1; | ||
1158 | } | ||
1159 | if( SXRET_OK != GenStateFindLiteral(&(*pGen), pName, &nIdx) ){ | ||
1160 | jx9_value *pObj; | ||
1161 | /* Emit the call instruction */ | ||
1162 | pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); | ||
1163 | if( pObj == 0 ){ | ||
1164 | SXUNUSED(iCompileFlag); /* cc warning */ | ||
1165 | return GenStateOutOfMem(pGen); | ||
1166 | } | ||
1167 | jx9MemObjInitFromString(pGen->pVm, pObj, pName); | ||
1168 | /* Install in the literal table */ | ||
1169 | GenStateInstallLiteral(&(*pGen), pObj, nIdx); | ||
1170 | } | ||
1171 | /* Emit the call instruction */ | ||
1172 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); | ||
1173 | jx9VmEmitInstr(pGen->pVm, JX9_OP_CALL, nArg, 0, 0, 0); | ||
1174 | } | ||
1175 | /* Node successfully compiled */ | ||
1176 | return SXRET_OK; | ||
1177 | } | ||
1178 | /* | ||
1179 | * Compile a node holding a variable declaration. | ||
1180 | * According to the J9X language reference | ||
1181 | * Variables in JX9 are represented by a dollar sign followed by the name of the variable. | ||
1182 | * The variable name is case-sensitive. | ||
1183 | * Variable names follow the same rules as other labels in JX9. A valid variable name | ||
1184 | * starts with a letter, underscore or any UTF-8 stream, followed by any number of letters | ||
1185 | * numbers, or underscores. | ||
1186 | * By default, variables are always assigned by value unless the target value is a JSON | ||
1187 | * array or a JSON object which is passed by reference. | ||
1188 | */ | ||
1189 | JX9_PRIVATE sxi32 jx9CompileVariable(jx9_gen_state *pGen,sxi32 iCompileFlag) | ||
1190 | { | ||
1191 | sxu32 nLine = pGen->pIn->nLine; | ||
1192 | SyHashEntry *pEntry; | ||
1193 | SyString *pName; | ||
1194 | char *zName = 0; | ||
1195 | sxi32 iP1; | ||
1196 | void *p3; | ||
1197 | sxi32 rc; | ||
1198 | |||
1199 | pGen->pIn++; /* Jump the dollar sign '$' */ | ||
1200 | if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){ | ||
1201 | /* Invalid variable name */ | ||
1202 | rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Invalid variable name"); | ||
1203 | if( rc == SXERR_ABORT ){ | ||
1204 | /* Error count limit reached, abort immediately */ | ||
1205 | return SXERR_ABORT; | ||
1206 | } | ||
1207 | return SXRET_OK; | ||
1208 | } | ||
1209 | /* Extract variable name */ | ||
1210 | pName = &pGen->pIn->sData; | ||
1211 | /* Advance the stream cursor */ | ||
1212 | pGen->pIn++; | ||
1213 | pEntry = SyHashGet(&pGen->hVar, (const void *)pName->zString, pName->nByte); | ||
1214 | if( pEntry == 0 ){ | ||
1215 | /* Duplicate name */ | ||
1216 | zName = SyMemBackendStrDup(&pGen->pVm->sAllocator, pName->zString, pName->nByte); | ||
1217 | if( zName == 0 ){ | ||
1218 | return GenStateOutOfMem(pGen); | ||
1219 | } | ||
1220 | /* Install in the hashtable */ | ||
1221 | SyHashInsert(&pGen->hVar, zName, pName->nByte, zName); | ||
1222 | }else{ | ||
1223 | /* Name already available */ | ||
1224 | zName = (char *)pEntry->pUserData; | ||
1225 | } | ||
1226 | p3 = (void *)zName; | ||
1227 | iP1 = 0; | ||
1228 | if( iCompileFlag & EXPR_FLAG_RDONLY_LOAD ){ | ||
1229 | if( (iCompileFlag & EXPR_FLAG_LOAD_IDX_STORE) == 0 ){ | ||
1230 | /* Read-only load.In other words do not create the variable if inexistant */ | ||
1231 | iP1 = 1; | ||
1232 | } | ||
1233 | } | ||
1234 | /* Emit the load instruction */ | ||
1235 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOAD, iP1, 0, p3, 0); | ||
1236 | /* Node successfully compiled */ | ||
1237 | return SXRET_OK; | ||
1238 | } | ||
1239 | /* Forward declaration */ | ||
1240 | static sxi32 GenStateCompileFunc(jx9_gen_state *pGen,SyString *pName,sxi32 iFlags,jx9_vm_func **ppFunc); | ||
1241 | /* | ||
1242 | * Compile an annoynmous function or a closure. | ||
1243 | * According to the JX9 language reference | ||
1244 | * Anonymous functions, also known as closures, allow the creation of functions | ||
1245 | * which have no specified name. They are most useful as the value of callback | ||
1246 | * parameters, but they have many other uses. Closures can also be used as | ||
1247 | * the values of variables; Assigning a closure to a variable uses the same | ||
1248 | * syntax as any other assignment, including the trailing semicolon: | ||
1249 | * Example Anonymous function variable assignment example | ||
1250 | * $greet = function($name) | ||
1251 | * { | ||
1252 | * printf("Hello %s\r\n", $name); | ||
1253 | * }; | ||
1254 | * $greet('World'); | ||
1255 | * $greet('JX9'); | ||
1256 | * Note that the implementation of annoynmous function and closure under | ||
1257 | * JX9 is completely different from the one used by the engine. | ||
1258 | */ | ||
1259 | JX9_PRIVATE sxi32 jx9CompileAnnonFunc(jx9_gen_state *pGen,sxi32 iCompileFlag) | ||
1260 | { | ||
1261 | jx9_vm_func *pAnnonFunc; /* Annonymous function body */ | ||
1262 | char zName[512]; /* Unique lambda name */ | ||
1263 | static int iCnt = 1; /* There is no worry about thread-safety here, because only | ||
1264 | * one thread is allowed to compile the script. | ||
1265 | */ | ||
1266 | jx9_value *pObj; | ||
1267 | SyString sName; | ||
1268 | sxu32 nIdx; | ||
1269 | sxu32 nLen; | ||
1270 | sxi32 rc; | ||
1271 | |||
1272 | pGen->pIn++; /* Jump the 'function' keyword */ | ||
1273 | if( pGen->pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD) ){ | ||
1274 | pGen->pIn++; | ||
1275 | } | ||
1276 | /* Reserve a constant for the lambda */ | ||
1277 | pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); | ||
1278 | if( pObj == 0 ){ | ||
1279 | GenStateOutOfMem(pGen); | ||
1280 | SXUNUSED(iCompileFlag); /* cc warning */ | ||
1281 | return SXERR_ABORT; | ||
1282 | } | ||
1283 | /* Generate a unique name */ | ||
1284 | nLen = SyBufferFormat(zName, sizeof(zName), "[lambda_%d]", iCnt++); | ||
1285 | /* Make sure the generated name is unique */ | ||
1286 | while( SyHashGet(&pGen->pVm->hFunction, zName, nLen) != 0 && nLen < sizeof(zName) - 2 ){ | ||
1287 | nLen = SyBufferFormat(zName, sizeof(zName), "[lambda_%d]", iCnt++); | ||
1288 | } | ||
1289 | SyStringInitFromBuf(&sName, zName, nLen); | ||
1290 | jx9MemObjInitFromString(pGen->pVm, pObj, &sName); | ||
1291 | /* Compile the lambda body */ | ||
1292 | rc = GenStateCompileFunc(&(*pGen),&sName,0,&pAnnonFunc); | ||
1293 | if( rc == SXERR_ABORT ){ | ||
1294 | return SXERR_ABORT; | ||
1295 | } | ||
1296 | /* Emit the load constant instruction */ | ||
1297 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); | ||
1298 | /* Node successfully compiled */ | ||
1299 | return SXRET_OK; | ||
1300 | } | ||
1301 | /* | ||
1302 | * Compile the 'continue' statement. | ||
1303 | * According to the JX9 language reference | ||
1304 | * continue is used within looping structures to skip the rest of the current loop iteration | ||
1305 | * and continue execution at the condition evaluation and then the beginning of the next | ||
1306 | * iteration. | ||
1307 | * Note: Note that in JX9 the switch statement is considered a looping structure for | ||
1308 | * the purposes of continue. | ||
1309 | * continue accepts an optional numeric argument which tells it how many levels | ||
1310 | * of enclosing loops it should skip to the end of. | ||
1311 | * Note: | ||
1312 | * continue 0; and continue 1; is the same as running continue;. | ||
1313 | */ | ||
1314 | static sxi32 jx9CompileContinue(jx9_gen_state *pGen) | ||
1315 | { | ||
1316 | GenBlock *pLoop; /* Target loop */ | ||
1317 | sxi32 iLevel; /* How many nesting loop to skip */ | ||
1318 | sxu32 nLine; | ||
1319 | sxi32 rc; | ||
1320 | nLine = pGen->pIn->nLine; | ||
1321 | iLevel = 0; | ||
1322 | /* Jump the 'continue' keyword */ | ||
1323 | pGen->pIn++; | ||
1324 | if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_NUM) ){ | ||
1325 | /* optional numeric argument which tells us how many levels | ||
1326 | * of enclosing loops we should skip to the end of. | ||
1327 | */ | ||
1328 | iLevel = (sxi32)jx9TokenValueToInt64(&pGen->pIn->sData); | ||
1329 | if( iLevel < 2 ){ | ||
1330 | iLevel = 0; | ||
1331 | } | ||
1332 | pGen->pIn++; /* Jump the optional numeric argument */ | ||
1333 | } | ||
1334 | /* Point to the target loop */ | ||
1335 | pLoop = GenStateFetchBlock(pGen->pCurrent, GEN_BLOCK_LOOP, iLevel); | ||
1336 | if( pLoop == 0 ){ | ||
1337 | /* Illegal continue */ | ||
1338 | rc = jx9GenCompileError(pGen, E_ERROR, nLine, "A 'continue' statement may only be used within a loop or switch"); | ||
1339 | if( rc == SXERR_ABORT ){ | ||
1340 | /* Error count limit reached, abort immediately */ | ||
1341 | return SXERR_ABORT; | ||
1342 | } | ||
1343 | }else{ | ||
1344 | sxu32 nInstrIdx = 0; | ||
1345 | /* Emit the unconditional jump to the beginning of the target loop */ | ||
1346 | jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pLoop->nFirstInstr, 0, &nInstrIdx); | ||
1347 | if( pLoop->bPostContinue == TRUE ){ | ||
1348 | JumpFixup sJumpFix; | ||
1349 | /* Post-continue */ | ||
1350 | sJumpFix.nJumpType = JX9_OP_JMP; | ||
1351 | sJumpFix.nInstrIdx = nInstrIdx; | ||
1352 | SySetPut(&pLoop->aPostContFix, (const void *)&sJumpFix); | ||
1353 | } | ||
1354 | } | ||
1355 | if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ | ||
1356 | /* Not so fatal, emit a warning only */ | ||
1357 | jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn->nLine, "Expected semi-colon ';' after 'continue' statement"); | ||
1358 | } | ||
1359 | /* Statement successfully compiled */ | ||
1360 | return SXRET_OK; | ||
1361 | } | ||
1362 | /* | ||
1363 | * Compile the 'break' statement. | ||
1364 | * According to the JX9 language reference | ||
1365 | * break ends execution of the current for, foreach, while, do-while or switch | ||
1366 | * structure. | ||
1367 | * break accepts an optional numeric argument which tells it how many nested | ||
1368 | * enclosing structures are to be broken out of. | ||
1369 | */ | ||
1370 | static sxi32 jx9CompileBreak(jx9_gen_state *pGen) | ||
1371 | { | ||
1372 | GenBlock *pLoop; /* Target loop */ | ||
1373 | sxi32 iLevel; /* How many nesting loop to skip */ | ||
1374 | sxu32 nLine; | ||
1375 | sxi32 rc; | ||
1376 | nLine = pGen->pIn->nLine; | ||
1377 | iLevel = 0; | ||
1378 | /* Jump the 'break' keyword */ | ||
1379 | pGen->pIn++; | ||
1380 | if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_NUM) ){ | ||
1381 | /* optional numeric argument which tells us how many levels | ||
1382 | * of enclosing loops we should skip to the end of. | ||
1383 | */ | ||
1384 | iLevel = (sxi32)jx9TokenValueToInt64(&pGen->pIn->sData); | ||
1385 | if( iLevel < 2 ){ | ||
1386 | iLevel = 0; | ||
1387 | } | ||
1388 | pGen->pIn++; /* Jump the optional numeric argument */ | ||
1389 | } | ||
1390 | /* Extract the target loop */ | ||
1391 | pLoop = GenStateFetchBlock(pGen->pCurrent, GEN_BLOCK_LOOP, iLevel); | ||
1392 | if( pLoop == 0 ){ | ||
1393 | /* Illegal break */ | ||
1394 | rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "A 'break' statement may only be used within a loop or switch"); | ||
1395 | if( rc == SXERR_ABORT ){ | ||
1396 | /* Error count limit reached, abort immediately */ | ||
1397 | return SXERR_ABORT; | ||
1398 | } | ||
1399 | }else{ | ||
1400 | sxu32 nInstrIdx; | ||
1401 | rc = jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, 0, 0, &nInstrIdx); | ||
1402 | if( rc == SXRET_OK ){ | ||
1403 | /* Fix the jump later when the jump destination is resolved */ | ||
1404 | GenStateNewJumpFixup(pLoop, JX9_OP_JMP, nInstrIdx); | ||
1405 | } | ||
1406 | } | ||
1407 | if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ | ||
1408 | /* Not so fatal, emit a warning only */ | ||
1409 | jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn->nLine, "Expected semi-colon ';' after 'break' statement"); | ||
1410 | } | ||
1411 | /* Statement successfully compiled */ | ||
1412 | return SXRET_OK; | ||
1413 | } | ||
1414 | /* Forward declaration */ | ||
1415 | static sxi32 GenStateCompileChunk(jx9_gen_state *pGen,sxi32 iFlags); | ||
1416 | /* | ||
1417 | * Compile a JX9 block. | ||
1418 | * A block is simply one or more JX9 statements and expressions to compile | ||
1419 | * optionally delimited by braces {}. | ||
1420 | * Return SXRET_OK on success. Any other return value indicates failure | ||
1421 | * and this function takes care of generating the appropriate error | ||
1422 | * message. | ||
1423 | */ | ||
1424 | static sxi32 jx9CompileBlock( | ||
1425 | jx9_gen_state *pGen /* Code generator state */ | ||
1426 | ) | ||
1427 | { | ||
1428 | sxi32 rc; | ||
1429 | if( pGen->pIn->nType & JX9_TK_OCB /* '{' */ ){ | ||
1430 | sxu32 nLine = pGen->pIn->nLine; | ||
1431 | rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_STD, jx9VmInstrLength(pGen->pVm), 0, 0); | ||
1432 | if( rc != SXRET_OK ){ | ||
1433 | return SXERR_ABORT; | ||
1434 | } | ||
1435 | pGen->pIn++; | ||
1436 | /* Compile until we hit the closing braces '}' */ | ||
1437 | for(;;){ | ||
1438 | if( pGen->pIn >= pGen->pEnd ){ | ||
1439 | /* No more token to process. Missing closing braces */ | ||
1440 | jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Missing closing braces '}'"); | ||
1441 | break; | ||
1442 | } | ||
1443 | if( pGen->pIn->nType & JX9_TK_CCB/*'}'*/ ){ | ||
1444 | /* Closing braces found, break immediately*/ | ||
1445 | pGen->pIn++; | ||
1446 | break; | ||
1447 | } | ||
1448 | /* Compile a single statement */ | ||
1449 | rc = GenStateCompileChunk(&(*pGen),JX9_COMPILE_SINGLE_STMT); | ||
1450 | if( rc == SXERR_ABORT ){ | ||
1451 | return SXERR_ABORT; | ||
1452 | } | ||
1453 | } | ||
1454 | GenStateLeaveBlock(&(*pGen), 0); | ||
1455 | }else{ | ||
1456 | /* Compile a single statement */ | ||
1457 | rc = GenStateCompileChunk(&(*pGen),JX9_COMPILE_SINGLE_STMT); | ||
1458 | if( rc == SXERR_ABORT ){ | ||
1459 | return SXERR_ABORT; | ||
1460 | } | ||
1461 | } | ||
1462 | /* Jump trailing semi-colons ';' */ | ||
1463 | while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) ){ | ||
1464 | pGen->pIn++; | ||
1465 | } | ||
1466 | return SXRET_OK; | ||
1467 | } | ||
1468 | /* | ||
1469 | * Compile the gentle 'while' statement. | ||
1470 | * According to the JX9 language reference | ||
1471 | * while loops are the simplest type of loop in JX9.They behave just like their C counterparts. | ||
1472 | * The basic form of a while statement is: | ||
1473 | * while (expr) | ||
1474 | * statement | ||
1475 | * The meaning of a while statement is simple. It tells JX9 to execute the nested statement(s) | ||
1476 | * repeatedly, as long as the while expression evaluates to TRUE. The value of the expression | ||
1477 | * is checked each time at the beginning of the loop, so even if this value changes during | ||
1478 | * the execution of the nested statement(s), execution will not stop until the end of the iteration | ||
1479 | * (each time JX9 runs the statements in the loop is one iteration). Sometimes, if the while | ||
1480 | * expression evaluates to FALSE from the very beginning, the nested statement(s) won't even be run once. | ||
1481 | * Like with the if statement, you can group multiple statements within the same while loop by surrounding | ||
1482 | * a group of statements with curly braces, or by using the alternate syntax: | ||
1483 | * while (expr): | ||
1484 | * statement | ||
1485 | * endwhile; | ||
1486 | */ | ||
1487 | static sxi32 jx9CompileWhile(jx9_gen_state *pGen) | ||
1488 | { | ||
1489 | GenBlock *pWhileBlock = 0; | ||
1490 | SyToken *pTmp, *pEnd = 0; | ||
1491 | sxu32 nFalseJump; | ||
1492 | sxu32 nLine; | ||
1493 | sxi32 rc; | ||
1494 | nLine = pGen->pIn->nLine; | ||
1495 | /* Jump the 'while' keyword */ | ||
1496 | pGen->pIn++; | ||
1497 | if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){ | ||
1498 | /* Syntax error */ | ||
1499 | rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after 'while' keyword"); | ||
1500 | if( rc == SXERR_ABORT ){ | ||
1501 | /* Error count limit reached, abort immediately */ | ||
1502 | return SXERR_ABORT; | ||
1503 | } | ||
1504 | goto Synchronize; | ||
1505 | } | ||
1506 | /* Jump the left parenthesis '(' */ | ||
1507 | pGen->pIn++; | ||
1508 | /* Create the loop block */ | ||
1509 | rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP, jx9VmInstrLength(pGen->pVm), 0, &pWhileBlock); | ||
1510 | if( rc != SXRET_OK ){ | ||
1511 | return SXERR_ABORT; | ||
1512 | } | ||
1513 | /* Delimit the condition */ | ||
1514 | jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd); | ||
1515 | if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){ | ||
1516 | /* Empty expression */ | ||
1517 | rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected expression after 'while' keyword"); | ||
1518 | if( rc == SXERR_ABORT ){ | ||
1519 | /* Error count limit reached, abort immediately */ | ||
1520 | return SXERR_ABORT; | ||
1521 | } | ||
1522 | } | ||
1523 | /* Swap token streams */ | ||
1524 | pTmp = pGen->pEnd; | ||
1525 | pGen->pEnd = pEnd; | ||
1526 | /* Compile the expression */ | ||
1527 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
1528 | if( rc == SXERR_ABORT ){ | ||
1529 | /* Expression handler request an operation abort [i.e: Out-of-memory] */ | ||
1530 | return SXERR_ABORT; | ||
1531 | } | ||
1532 | /* Update token stream */ | ||
1533 | while(pGen->pIn < pEnd ){ | ||
1534 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'", &pGen->pIn->sData); | ||
1535 | if( rc == SXERR_ABORT ){ | ||
1536 | return SXERR_ABORT; | ||
1537 | } | ||
1538 | pGen->pIn++; | ||
1539 | } | ||
1540 | /* Synchronize pointers */ | ||
1541 | pGen->pIn = &pEnd[1]; | ||
1542 | pGen->pEnd = pTmp; | ||
1543 | /* Emit the false jump */ | ||
1544 | jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 0, 0, 0, &nFalseJump); | ||
1545 | /* Save the instruction index so we can fix it later when the jump destination is resolved */ | ||
1546 | GenStateNewJumpFixup(pWhileBlock, JX9_OP_JZ, nFalseJump); | ||
1547 | /* Compile the loop body */ | ||
1548 | rc = jx9CompileBlock(&(*pGen)); | ||
1549 | if( rc == SXERR_ABORT ){ | ||
1550 | return SXERR_ABORT; | ||
1551 | } | ||
1552 | /* Emit the unconditional jump to the start of the loop */ | ||
1553 | jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pWhileBlock->nFirstInstr, 0, 0); | ||
1554 | /* Fix all jumps now the destination is resolved */ | ||
1555 | GenStateFixJumps(pWhileBlock, -1, jx9VmInstrLength(pGen->pVm)); | ||
1556 | /* Release the loop block */ | ||
1557 | GenStateLeaveBlock(pGen, 0); | ||
1558 | /* Statement successfully compiled */ | ||
1559 | return SXRET_OK; | ||
1560 | Synchronize: | ||
1561 | /* Synchronize with the first semi-colon ';' so we can avoid | ||
1562 | * compiling this erroneous block. | ||
1563 | */ | ||
1564 | while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){ | ||
1565 | pGen->pIn++; | ||
1566 | } | ||
1567 | return SXRET_OK; | ||
1568 | } | ||
1569 | /* | ||
1570 | * Compile the complex and powerful 'for' statement. | ||
1571 | * According to the JX9 language reference | ||
1572 | * for loops are the most complex loops in JX9. They behave like their C counterparts. | ||
1573 | * The syntax of a for loop is: | ||
1574 | * for (expr1; expr2; expr3) | ||
1575 | * statement | ||
1576 | * The first expression (expr1) is evaluated (executed) once unconditionally at | ||
1577 | * the beginning of the loop. | ||
1578 | * In the beginning of each iteration, expr2 is evaluated. If it evaluates to | ||
1579 | * TRUE, the loop continues and the nested statement(s) are executed. If it evaluates | ||
1580 | * to FALSE, the execution of the loop ends. | ||
1581 | * At the end of each iteration, expr3 is evaluated (executed). | ||
1582 | * Each of the expressions can be empty or contain multiple expressions separated by commas. | ||
1583 | * In expr2, all expressions separated by a comma are evaluated but the result is taken | ||
1584 | * from the last part. expr2 being empty means the loop should be run indefinitely | ||
1585 | * (JX9 implicitly considers it as TRUE, like C). This may not be as useless as you might | ||
1586 | * think, since often you'd want to end the loop using a conditional break statement instead | ||
1587 | * of using the for truth expression. | ||
1588 | */ | ||
1589 | static sxi32 jx9CompileFor(jx9_gen_state *pGen) | ||
1590 | { | ||
1591 | SyToken *pTmp, *pPostStart, *pEnd = 0; | ||
1592 | GenBlock *pForBlock = 0; | ||
1593 | sxu32 nFalseJump; | ||
1594 | sxu32 nLine; | ||
1595 | sxi32 rc; | ||
1596 | nLine = pGen->pIn->nLine; | ||
1597 | /* Jump the 'for' keyword */ | ||
1598 | pGen->pIn++; | ||
1599 | if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){ | ||
1600 | /* Syntax error */ | ||
1601 | rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after 'for' keyword"); | ||
1602 | if( rc == SXERR_ABORT ){ | ||
1603 | /* Error count limit reached, abort immediately */ | ||
1604 | return SXERR_ABORT; | ||
1605 | } | ||
1606 | return SXRET_OK; | ||
1607 | } | ||
1608 | /* Jump the left parenthesis '(' */ | ||
1609 | pGen->pIn++; | ||
1610 | /* Delimit the init-expr;condition;post-expr */ | ||
1611 | jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd); | ||
1612 | if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){ | ||
1613 | /* Empty expression */ | ||
1614 | rc = jx9GenCompileError(pGen, E_ERROR, nLine, "for: Invalid expression"); | ||
1615 | if( rc == SXERR_ABORT ){ | ||
1616 | /* Error count limit reached, abort immediately */ | ||
1617 | return SXERR_ABORT; | ||
1618 | } | ||
1619 | /* Synchronize */ | ||
1620 | pGen->pIn = pEnd; | ||
1621 | if( pGen->pIn < pGen->pEnd ){ | ||
1622 | pGen->pIn++; | ||
1623 | } | ||
1624 | return SXRET_OK; | ||
1625 | } | ||
1626 | /* Swap token streams */ | ||
1627 | pTmp = pGen->pEnd; | ||
1628 | pGen->pEnd = pEnd; | ||
1629 | /* Compile initialization expressions if available */ | ||
1630 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
1631 | /* Pop operand lvalues */ | ||
1632 | if( rc == SXERR_ABORT ){ | ||
1633 | /* Expression handler request an operation abort [i.e: Out-of-memory] */ | ||
1634 | return SXERR_ABORT; | ||
1635 | }else if( rc != SXERR_EMPTY ){ | ||
1636 | jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0); | ||
1637 | } | ||
1638 | if( (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ | ||
1639 | /* Syntax error */ | ||
1640 | rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, | ||
1641 | "for: Expected ';' after initialization expressions"); | ||
1642 | if( rc == SXERR_ABORT ){ | ||
1643 | /* Error count limit reached, abort immediately */ | ||
1644 | return SXERR_ABORT; | ||
1645 | } | ||
1646 | return SXRET_OK; | ||
1647 | } | ||
1648 | /* Jump the trailing ';' */ | ||
1649 | pGen->pIn++; | ||
1650 | /* Create the loop block */ | ||
1651 | rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP, jx9VmInstrLength(pGen->pVm), 0, &pForBlock); | ||
1652 | if( rc != SXRET_OK ){ | ||
1653 | return SXERR_ABORT; | ||
1654 | } | ||
1655 | /* Deffer continue jumps */ | ||
1656 | pForBlock->bPostContinue = TRUE; | ||
1657 | /* Compile the condition */ | ||
1658 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
1659 | if( rc == SXERR_ABORT ){ | ||
1660 | /* Expression handler request an operation abort [i.e: Out-of-memory] */ | ||
1661 | return SXERR_ABORT; | ||
1662 | }else if( rc != SXERR_EMPTY ){ | ||
1663 | /* Emit the false jump */ | ||
1664 | jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 0, 0, 0, &nFalseJump); | ||
1665 | /* Save the instruction index so we can fix it later when the jump destination is resolved */ | ||
1666 | GenStateNewJumpFixup(pForBlock, JX9_OP_JZ, nFalseJump); | ||
1667 | } | ||
1668 | if( (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ | ||
1669 | /* Syntax error */ | ||
1670 | rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, | ||
1671 | "for: Expected ';' after conditionals expressions"); | ||
1672 | if( rc == SXERR_ABORT ){ | ||
1673 | /* Error count limit reached, abort immediately */ | ||
1674 | return SXERR_ABORT; | ||
1675 | } | ||
1676 | return SXRET_OK; | ||
1677 | } | ||
1678 | /* Jump the trailing ';' */ | ||
1679 | pGen->pIn++; | ||
1680 | /* Save the post condition stream */ | ||
1681 | pPostStart = pGen->pIn; | ||
1682 | /* Compile the loop body */ | ||
1683 | pGen->pIn = &pEnd[1]; /* Jump the trailing parenthesis ')' */ | ||
1684 | pGen->pEnd = pTmp; | ||
1685 | rc = jx9CompileBlock(&(*pGen)); | ||
1686 | if( rc == SXERR_ABORT ){ | ||
1687 | return SXERR_ABORT; | ||
1688 | } | ||
1689 | /* Fix post-continue jumps */ | ||
1690 | if( SySetUsed(&pForBlock->aPostContFix) > 0 ){ | ||
1691 | JumpFixup *aPost; | ||
1692 | VmInstr *pInstr; | ||
1693 | sxu32 nJumpDest; | ||
1694 | sxu32 n; | ||
1695 | aPost = (JumpFixup *)SySetBasePtr(&pForBlock->aPostContFix); | ||
1696 | nJumpDest = jx9VmInstrLength(pGen->pVm); | ||
1697 | for( n = 0 ; n < SySetUsed(&pForBlock->aPostContFix) ; ++n ){ | ||
1698 | pInstr = jx9VmGetInstr(pGen->pVm, aPost[n].nInstrIdx); | ||
1699 | if( pInstr ){ | ||
1700 | /* Fix jump */ | ||
1701 | pInstr->iP2 = nJumpDest; | ||
1702 | } | ||
1703 | } | ||
1704 | } | ||
1705 | /* compile the post-expressions if available */ | ||
1706 | while( pPostStart < pEnd && (pPostStart->nType & JX9_TK_SEMI) ){ | ||
1707 | pPostStart++; | ||
1708 | } | ||
1709 | if( pPostStart < pEnd ){ | ||
1710 | SyToken *pTmpIn, *pTmpEnd; | ||
1711 | SWAP_DELIMITER(pGen, pPostStart, pEnd); | ||
1712 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
1713 | if( pGen->pIn < pGen->pEnd ){ | ||
1714 | /* Syntax error */ | ||
1715 | rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "for: Expected ')' after post-expressions"); | ||
1716 | if( rc == SXERR_ABORT ){ | ||
1717 | /* Error count limit reached, abort immediately */ | ||
1718 | return SXERR_ABORT; | ||
1719 | } | ||
1720 | return SXRET_OK; | ||
1721 | } | ||
1722 | RE_SWAP_DELIMITER(pGen); | ||
1723 | if( rc == SXERR_ABORT ){ | ||
1724 | /* Expression handler request an operation abort [i.e: Out-of-memory] */ | ||
1725 | return SXERR_ABORT; | ||
1726 | }else if( rc != SXERR_EMPTY){ | ||
1727 | /* Pop operand lvalue */ | ||
1728 | jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0); | ||
1729 | } | ||
1730 | } | ||
1731 | /* Emit the unconditional jump to the start of the loop */ | ||
1732 | jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pForBlock->nFirstInstr, 0, 0); | ||
1733 | /* Fix all jumps now the destination is resolved */ | ||
1734 | GenStateFixJumps(pForBlock, -1, jx9VmInstrLength(pGen->pVm)); | ||
1735 | /* Release the loop block */ | ||
1736 | GenStateLeaveBlock(pGen, 0); | ||
1737 | /* Statement successfully compiled */ | ||
1738 | return SXRET_OK; | ||
1739 | } | ||
1740 | /* Expression tree validator callback used by the 'foreach' statement. | ||
1741 | * Note that only variable expression [i.e: $x; ${'My'.'Var'}; ${$a['key]};...] | ||
1742 | * are allowed. | ||
1743 | */ | ||
1744 | static sxi32 GenStateForEachNodeValidator(jx9_gen_state *pGen,jx9_expr_node *pRoot) | ||
1745 | { | ||
1746 | sxi32 rc = SXRET_OK; /* Assume a valid expression tree */ | ||
1747 | if( pRoot->xCode != jx9CompileVariable ){ | ||
1748 | /* Unexpected expression */ | ||
1749 | rc = jx9GenCompileError(&(*pGen), | ||
1750 | E_ERROR, | ||
1751 | pRoot->pStart? pRoot->pStart->nLine : 0, | ||
1752 | "foreach: Expecting a variable name" | ||
1753 | ); | ||
1754 | if( rc != SXERR_ABORT ){ | ||
1755 | rc = SXERR_INVALID; | ||
1756 | } | ||
1757 | } | ||
1758 | return rc; | ||
1759 | } | ||
1760 | /* | ||
1761 | * Compile the 'foreach' statement. | ||
1762 | * According to the JX9 language reference | ||
1763 | * The foreach construct simply gives an easy way to iterate over arrays. foreach works | ||
1764 | * only on arrays (and objects), and will issue an error when you try to use it on a variable | ||
1765 | * with a different data type or an uninitialized variable. There are two syntaxes; the second | ||
1766 | * is a minor but useful extension of the first: | ||
1767 | * foreach (json_array_json_object as $value) | ||
1768 | * statement | ||
1769 | * foreach (json_array_json_objec as $key,$value) | ||
1770 | * statement | ||
1771 | * The first form loops over the array given by array_expression. On each loop, the value | ||
1772 | * of the current element is assigned to $value and the internal array pointer is advanced | ||
1773 | * by one (so on the next loop, you'll be looking at the next element). | ||
1774 | * The second form does the same thing, except that the current element's key will be assigned | ||
1775 | * to the variable $key on each loop. | ||
1776 | * Note: | ||
1777 | * When foreach first starts executing, the internal array pointer is automatically reset to the | ||
1778 | * first element of the array. This means that you do not need to call reset() before a foreach loop. | ||
1779 | */ | ||
1780 | static sxi32 jx9CompileForeach(jx9_gen_state *pGen) | ||
1781 | { | ||
1782 | SyToken *pCur, *pTmp, *pEnd = 0; | ||
1783 | GenBlock *pForeachBlock = 0; | ||
1784 | jx9_foreach_info *pInfo; | ||
1785 | sxu32 nFalseJump; | ||
1786 | VmInstr *pInstr; | ||
1787 | sxu32 nLine; | ||
1788 | sxi32 rc; | ||
1789 | nLine = pGen->pIn->nLine; | ||
1790 | /* Jump the 'foreach' keyword */ | ||
1791 | pGen->pIn++; | ||
1792 | if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){ | ||
1793 | /* Syntax error */ | ||
1794 | rc = jx9GenCompileError(pGen, E_ERROR, nLine, "foreach: Expected '('"); | ||
1795 | if( rc == SXERR_ABORT ){ | ||
1796 | /* Error count limit reached, abort immediately */ | ||
1797 | return SXERR_ABORT; | ||
1798 | } | ||
1799 | goto Synchronize; | ||
1800 | } | ||
1801 | /* Jump the left parenthesis '(' */ | ||
1802 | pGen->pIn++; | ||
1803 | /* Create the loop block */ | ||
1804 | rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP, jx9VmInstrLength(pGen->pVm), 0, &pForeachBlock); | ||
1805 | if( rc != SXRET_OK ){ | ||
1806 | return SXERR_ABORT; | ||
1807 | } | ||
1808 | /* Delimit the expression */ | ||
1809 | jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd); | ||
1810 | if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){ | ||
1811 | /* Empty expression */ | ||
1812 | rc = jx9GenCompileError(pGen, E_ERROR, nLine, "foreach: Missing expression"); | ||
1813 | if( rc == SXERR_ABORT ){ | ||
1814 | /* Error count limit reached, abort immediately */ | ||
1815 | return SXERR_ABORT; | ||
1816 | } | ||
1817 | /* Synchronize */ | ||
1818 | pGen->pIn = pEnd; | ||
1819 | if( pGen->pIn < pGen->pEnd ){ | ||
1820 | pGen->pIn++; | ||
1821 | } | ||
1822 | return SXRET_OK; | ||
1823 | } | ||
1824 | /* Compile the array expression */ | ||
1825 | pCur = pGen->pIn; | ||
1826 | while( pCur < pEnd ){ | ||
1827 | if( pCur->nType & JX9_TK_KEYWORD ){ | ||
1828 | sxi32 nKeywrd = SX_PTR_TO_INT(pCur->pUserData); | ||
1829 | if( nKeywrd == JX9_TKWRD_AS ){ | ||
1830 | /* Break with the first 'as' found */ | ||
1831 | break; | ||
1832 | } | ||
1833 | } | ||
1834 | /* Advance the stream cursor */ | ||
1835 | pCur++; | ||
1836 | } | ||
1837 | if( pCur <= pGen->pIn ){ | ||
1838 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, | ||
1839 | "foreach: Missing array/object expression"); | ||
1840 | if( rc == SXERR_ABORT ){ | ||
1841 | /* Don't worry about freeing memory, everything will be released shortly */ | ||
1842 | return SXERR_ABORT; | ||
1843 | } | ||
1844 | goto Synchronize; | ||
1845 | } | ||
1846 | /* Swap token streams */ | ||
1847 | pTmp = pGen->pEnd; | ||
1848 | pGen->pEnd = pCur; | ||
1849 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
1850 | if( rc == SXERR_ABORT ){ | ||
1851 | /* Expression handler request an operation abort [i.e: Out-of-memory] */ | ||
1852 | return SXERR_ABORT; | ||
1853 | } | ||
1854 | /* Update token stream */ | ||
1855 | while(pGen->pIn < pCur ){ | ||
1856 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Unexpected token '%z'", &pGen->pIn->sData); | ||
1857 | if( rc == SXERR_ABORT ){ | ||
1858 | /* Don't worry about freeing memory, everything will be released shortly */ | ||
1859 | return SXERR_ABORT; | ||
1860 | } | ||
1861 | pGen->pIn++; | ||
1862 | } | ||
1863 | pCur++; /* Jump the 'as' keyword */ | ||
1864 | pGen->pIn = pCur; | ||
1865 | if( pGen->pIn >= pEnd ){ | ||
1866 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing $key => $value pair"); | ||
1867 | if( rc == SXERR_ABORT ){ | ||
1868 | return SXERR_ABORT; | ||
1869 | } | ||
1870 | } | ||
1871 | /* Create the foreach context */ | ||
1872 | pInfo = (jx9_foreach_info *)SyMemBackendAlloc(&pGen->pVm->sAllocator, sizeof(jx9_foreach_info)); | ||
1873 | if( pInfo == 0 ){ | ||
1874 | jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Fatal, JX9 engine is running out-of-memory"); | ||
1875 | return SXERR_ABORT; | ||
1876 | } | ||
1877 | /* Zero the structure */ | ||
1878 | SyZero(pInfo, sizeof(jx9_foreach_info)); | ||
1879 | /* Initialize structure fields */ | ||
1880 | SySetInit(&pInfo->aStep, &pGen->pVm->sAllocator, sizeof(jx9_foreach_step *)); | ||
1881 | /* Check if we have a key field */ | ||
1882 | while( pCur < pEnd && (pCur->nType & JX9_TK_COMMA) == 0 ){ | ||
1883 | pCur++; | ||
1884 | } | ||
1885 | if( pCur < pEnd ){ | ||
1886 | /* Compile the expression holding the key name */ | ||
1887 | if( pGen->pIn >= pCur ){ | ||
1888 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing $key"); | ||
1889 | if( rc == SXERR_ABORT ){ | ||
1890 | /* Don't worry about freeing memory, everything will be released shortly */ | ||
1891 | return SXERR_ABORT; | ||
1892 | } | ||
1893 | }else{ | ||
1894 | pGen->pEnd = pCur; | ||
1895 | rc = jx9CompileExpr(&(*pGen), 0, GenStateForEachNodeValidator); | ||
1896 | if( rc == SXERR_ABORT ){ | ||
1897 | /* Don't worry about freeing memory, everything will be released shortly */ | ||
1898 | return SXERR_ABORT; | ||
1899 | } | ||
1900 | pInstr = jx9VmPopInstr(pGen->pVm); | ||
1901 | if( pInstr->p3 ){ | ||
1902 | /* Record key name */ | ||
1903 | SyStringInitFromBuf(&pInfo->sKey, pInstr->p3, SyStrlen((const char *)pInstr->p3)); | ||
1904 | } | ||
1905 | pInfo->iFlags |= JX9_4EACH_STEP_KEY; | ||
1906 | } | ||
1907 | pGen->pIn = &pCur[1]; /* Jump the arrow */ | ||
1908 | } | ||
1909 | pGen->pEnd = pEnd; | ||
1910 | if( pGen->pIn >= pEnd ){ | ||
1911 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing $value"); | ||
1912 | if( rc == SXERR_ABORT ){ | ||
1913 | /* Don't worry about freeing memory, everything will be released shortly */ | ||
1914 | return SXERR_ABORT; | ||
1915 | } | ||
1916 | goto Synchronize; | ||
1917 | } | ||
1918 | /* Compile the expression holding the value name */ | ||
1919 | rc = jx9CompileExpr(&(*pGen), 0, GenStateForEachNodeValidator); | ||
1920 | if( rc == SXERR_ABORT ){ | ||
1921 | /* Don't worry about freeing memory, everything will be released shortly */ | ||
1922 | return SXERR_ABORT; | ||
1923 | } | ||
1924 | pInstr = jx9VmPopInstr(pGen->pVm); | ||
1925 | if( pInstr->p3 ){ | ||
1926 | /* Record value name */ | ||
1927 | SyStringInitFromBuf(&pInfo->sValue, pInstr->p3, SyStrlen((const char *)pInstr->p3)); | ||
1928 | } | ||
1929 | /* Emit the 'FOREACH_INIT' instruction */ | ||
1930 | jx9VmEmitInstr(pGen->pVm, JX9_OP_FOREACH_INIT, 0, 0, pInfo, &nFalseJump); | ||
1931 | /* Save the instruction index so we can fix it later when the jump destination is resolved */ | ||
1932 | GenStateNewJumpFixup(pForeachBlock, JX9_OP_FOREACH_INIT, nFalseJump); | ||
1933 | /* Record the first instruction to execute */ | ||
1934 | pForeachBlock->nFirstInstr = jx9VmInstrLength(pGen->pVm); | ||
1935 | /* Emit the FOREACH_STEP instruction */ | ||
1936 | jx9VmEmitInstr(pGen->pVm, JX9_OP_FOREACH_STEP, 0, 0, pInfo, &nFalseJump); | ||
1937 | /* Save the instruction index so we can fix it later when the jump destination is resolved */ | ||
1938 | GenStateNewJumpFixup(pForeachBlock, JX9_OP_FOREACH_STEP, nFalseJump); | ||
1939 | /* Compile the loop body */ | ||
1940 | pGen->pIn = &pEnd[1]; | ||
1941 | pGen->pEnd = pTmp; | ||
1942 | rc = jx9CompileBlock(&(*pGen)); | ||
1943 | if( rc == SXERR_ABORT ){ | ||
1944 | /* Don't worry about freeing memory, everything will be released shortly */ | ||
1945 | return SXERR_ABORT; | ||
1946 | } | ||
1947 | /* Emit the unconditional jump to the start of the loop */ | ||
1948 | jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pForeachBlock->nFirstInstr, 0, 0); | ||
1949 | /* Fix all jumps now the destination is resolved */ | ||
1950 | GenStateFixJumps(pForeachBlock, -1,jx9VmInstrLength(pGen->pVm)); | ||
1951 | /* Release the loop block */ | ||
1952 | GenStateLeaveBlock(pGen, 0); | ||
1953 | /* Statement successfully compiled */ | ||
1954 | return SXRET_OK; | ||
1955 | Synchronize: | ||
1956 | /* Synchronize with the first semi-colon ';' so we can avoid | ||
1957 | * compiling this erroneous block. | ||
1958 | */ | ||
1959 | while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){ | ||
1960 | pGen->pIn++; | ||
1961 | } | ||
1962 | return SXRET_OK; | ||
1963 | } | ||
1964 | /* | ||
1965 | * Compile the infamous if/elseif/else if/else statements. | ||
1966 | * According to the JX9 language reference | ||
1967 | * The if construct is one of the most important features of many languages JX9 included. | ||
1968 | * It allows for conditional execution of code fragments. JX9 features an if structure | ||
1969 | * that is similar to that of C: | ||
1970 | * if (expr) | ||
1971 | * statement | ||
1972 | * else construct: | ||
1973 | * Often you'd want to execute a statement if a certain condition is met, and a different | ||
1974 | * statement if the condition is not met. This is what else is for. else extends an if statement | ||
1975 | * to execute a statement in case the expression in the if statement evaluates to FALSE. | ||
1976 | * For example, the following code would display a is greater than b if $a is greater than | ||
1977 | * $b, and a is NOT greater than b otherwise. | ||
1978 | * The else statement is only executed if the if expression evaluated to FALSE, and if there | ||
1979 | * were any elseif expressions - only if they evaluated to FALSE as well | ||
1980 | * elseif | ||
1981 | * elseif, as its name suggests, is a combination of if and else. Like else, it extends | ||
1982 | * an if statement to execute a different statement in case the original if expression evaluates | ||
1983 | * to FALSE. However, unlike else, it will execute that alternative expression only if the elseif | ||
1984 | * conditional expression evaluates to TRUE. For example, the following code would display a is bigger | ||
1985 | * than b, a equal to b or a is smaller than b: | ||
1986 | * if ($a > $b) { | ||
1987 | * print "a is bigger than b"; | ||
1988 | * } elseif ($a == $b) { | ||
1989 | * print "a is equal to b"; | ||
1990 | * } else { | ||
1991 | * print "a is smaller than b"; | ||
1992 | * } | ||
1993 | */ | ||
1994 | static sxi32 jx9CompileIf(jx9_gen_state *pGen) | ||
1995 | { | ||
1996 | SyToken *pToken, *pTmp, *pEnd = 0; | ||
1997 | GenBlock *pCondBlock = 0; | ||
1998 | sxu32 nJumpIdx; | ||
1999 | sxu32 nKeyID; | ||
2000 | sxi32 rc; | ||
2001 | /* Jump the 'if' keyword */ | ||
2002 | pGen->pIn++; | ||
2003 | pToken = pGen->pIn; | ||
2004 | /* Create the conditional block */ | ||
2005 | rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_COND, jx9VmInstrLength(pGen->pVm), 0, &pCondBlock); | ||
2006 | if( rc != SXRET_OK ){ | ||
2007 | return SXERR_ABORT; | ||
2008 | } | ||
2009 | /* Process as many [if/else if/elseif/else] blocks as we can */ | ||
2010 | for(;;){ | ||
2011 | if( pToken >= pGen->pEnd || (pToken->nType & JX9_TK_LPAREN) == 0 ){ | ||
2012 | /* Syntax error */ | ||
2013 | if( pToken >= pGen->pEnd ){ | ||
2014 | pToken--; | ||
2015 | } | ||
2016 | rc = jx9GenCompileError(pGen, E_ERROR, pToken->nLine, "if/else/elseif: Missing '('"); | ||
2017 | if( rc == SXERR_ABORT ){ | ||
2018 | /* Error count limit reached, abort immediately */ | ||
2019 | return SXERR_ABORT; | ||
2020 | } | ||
2021 | goto Synchronize; | ||
2022 | } | ||
2023 | /* Jump the left parenthesis '(' */ | ||
2024 | pToken++; | ||
2025 | /* Delimit the condition */ | ||
2026 | jx9DelimitNestedTokens(pToken, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd); | ||
2027 | if( pToken >= pEnd || (pEnd->nType & JX9_TK_RPAREN) == 0 ){ | ||
2028 | /* Syntax error */ | ||
2029 | if( pToken >= pGen->pEnd ){ | ||
2030 | pToken--; | ||
2031 | } | ||
2032 | rc = jx9GenCompileError(pGen, E_ERROR, pToken->nLine, "if/else/elseif: Missing ')'"); | ||
2033 | if( rc == SXERR_ABORT ){ | ||
2034 | /* Error count limit reached, abort immediately */ | ||
2035 | return SXERR_ABORT; | ||
2036 | } | ||
2037 | goto Synchronize; | ||
2038 | } | ||
2039 | /* Swap token streams */ | ||
2040 | SWAP_TOKEN_STREAM(pGen, pToken, pEnd); | ||
2041 | /* Compile the condition */ | ||
2042 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
2043 | /* Update token stream */ | ||
2044 | while(pGen->pIn < pEnd ){ | ||
2045 | jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'", &pGen->pIn->sData); | ||
2046 | pGen->pIn++; | ||
2047 | } | ||
2048 | pGen->pIn = &pEnd[1]; | ||
2049 | pGen->pEnd = pTmp; | ||
2050 | if( rc == SXERR_ABORT ){ | ||
2051 | /* Expression handler request an operation abort [i.e: Out-of-memory] */ | ||
2052 | return SXERR_ABORT; | ||
2053 | } | ||
2054 | /* Emit the false jump */ | ||
2055 | jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 0, 0, 0, &nJumpIdx); | ||
2056 | /* Save the instruction index so we can fix it later when the jump destination is resolved */ | ||
2057 | GenStateNewJumpFixup(pCondBlock, JX9_OP_JZ, nJumpIdx); | ||
2058 | /* Compile the body */ | ||
2059 | rc = jx9CompileBlock(&(*pGen)); | ||
2060 | if( rc == SXERR_ABORT ){ | ||
2061 | return SXERR_ABORT; | ||
2062 | } | ||
2063 | if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_KEYWORD) == 0 ){ | ||
2064 | break; | ||
2065 | } | ||
2066 | /* Ensure that the keyword ID is 'else if' or 'else' */ | ||
2067 | nKeyID = (sxu32)SX_PTR_TO_INT(pGen->pIn->pUserData); | ||
2068 | if( (nKeyID & (JX9_TKWRD_ELSE|JX9_TKWRD_ELIF)) == 0 ){ | ||
2069 | break; | ||
2070 | } | ||
2071 | /* Emit the unconditional jump */ | ||
2072 | jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, 0, 0, &nJumpIdx); | ||
2073 | /* Save the instruction index so we can fix it later when the jump destination is resolved */ | ||
2074 | GenStateNewJumpFixup(pCondBlock, JX9_OP_JMP, nJumpIdx); | ||
2075 | if( nKeyID & JX9_TKWRD_ELSE ){ | ||
2076 | pToken = &pGen->pIn[1]; | ||
2077 | if( pToken >= pGen->pEnd || (pToken->nType & JX9_TK_KEYWORD) == 0 || | ||
2078 | SX_PTR_TO_INT(pToken->pUserData) != JX9_TKWRD_IF ){ | ||
2079 | break; | ||
2080 | } | ||
2081 | pGen->pIn++; /* Jump the 'else' keyword */ | ||
2082 | } | ||
2083 | pGen->pIn++; /* Jump the 'elseif/if' keyword */ | ||
2084 | /* Synchronize cursors */ | ||
2085 | pToken = pGen->pIn; | ||
2086 | /* Fix the false jump */ | ||
2087 | GenStateFixJumps(pCondBlock, JX9_OP_JZ, jx9VmInstrLength(pGen->pVm)); | ||
2088 | } /* For(;;) */ | ||
2089 | /* Fix the false jump */ | ||
2090 | GenStateFixJumps(pCondBlock, JX9_OP_JZ, jx9VmInstrLength(pGen->pVm)); | ||
2091 | if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_KEYWORD) && | ||
2092 | (SX_PTR_TO_INT(pGen->pIn->pUserData) & JX9_TKWRD_ELSE) ){ | ||
2093 | /* Compile the else block */ | ||
2094 | pGen->pIn++; | ||
2095 | rc = jx9CompileBlock(&(*pGen)); | ||
2096 | if( rc == SXERR_ABORT ){ | ||
2097 | |||
2098 | return SXERR_ABORT; | ||
2099 | } | ||
2100 | } | ||
2101 | nJumpIdx = jx9VmInstrLength(pGen->pVm); | ||
2102 | /* Fix all unconditional jumps now the destination is resolved */ | ||
2103 | GenStateFixJumps(pCondBlock, JX9_OP_JMP, nJumpIdx); | ||
2104 | /* Release the conditional block */ | ||
2105 | GenStateLeaveBlock(pGen, 0); | ||
2106 | /* Statement successfully compiled */ | ||
2107 | return SXRET_OK; | ||
2108 | Synchronize: | ||
2109 | /* Synchronize with the first semi-colon ';' so we can avoid compiling this erroneous block. | ||
2110 | */ | ||
2111 | while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){ | ||
2112 | pGen->pIn++; | ||
2113 | } | ||
2114 | return SXRET_OK; | ||
2115 | } | ||
2116 | /* | ||
2117 | * Compile the return statement. | ||
2118 | * According to the JX9 language reference | ||
2119 | * If called from within a function, the return() statement immediately ends execution | ||
2120 | * of the current function, and returns its argument as the value of the function call. | ||
2121 | * return() will also end the execution of an eval() statement or script file. | ||
2122 | * If called from the global scope, then execution of the current script file is ended. | ||
2123 | * If the current script file was include()ed or require()ed, then control is passed back | ||
2124 | * to the calling file. Furthermore, if the current script file was include()ed, then the value | ||
2125 | * given to return() will be returned as the value of the include() call. If return() is called | ||
2126 | * from within the main script file, then script execution end. | ||
2127 | * Note that since return() is a language construct and not a function, the parentheses | ||
2128 | * surrounding its arguments are not required. It is common to leave them out, and you actually | ||
2129 | * should do so as JX9 has less work to do in this case. | ||
2130 | * Note: If no parameter is supplied, then the parentheses must be omitted and JX9 is returning NULL instead.. | ||
2131 | */ | ||
2132 | static sxi32 jx9CompileReturn(jx9_gen_state *pGen) | ||
2133 | { | ||
2134 | sxi32 nRet = 0; /* TRUE if there is a return value */ | ||
2135 | sxi32 rc; | ||
2136 | /* Jump the 'return' keyword */ | ||
2137 | pGen->pIn++; | ||
2138 | if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ | ||
2139 | /* Compile the expression */ | ||
2140 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
2141 | if( rc == SXERR_ABORT ){ | ||
2142 | return SXERR_ABORT; | ||
2143 | }else if(rc != SXERR_EMPTY ){ | ||
2144 | nRet = 1; | ||
2145 | } | ||
2146 | } | ||
2147 | /* Emit the done instruction */ | ||
2148 | jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, nRet, 0, 0, 0); | ||
2149 | return SXRET_OK; | ||
2150 | } | ||
2151 | /* | ||
2152 | * Compile the die/exit language construct. | ||
2153 | * The role of these constructs is to terminate execution of the script. | ||
2154 | * Shutdown functions will always be executed even if exit() is called. | ||
2155 | */ | ||
2156 | static sxi32 jx9CompileHalt(jx9_gen_state *pGen) | ||
2157 | { | ||
2158 | sxi32 nExpr = 0; | ||
2159 | sxi32 rc; | ||
2160 | /* Jump the die/exit keyword */ | ||
2161 | pGen->pIn++; | ||
2162 | if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ | ||
2163 | /* Compile the expression */ | ||
2164 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
2165 | if( rc == SXERR_ABORT ){ | ||
2166 | return SXERR_ABORT; | ||
2167 | }else if(rc != SXERR_EMPTY ){ | ||
2168 | nExpr = 1; | ||
2169 | } | ||
2170 | } | ||
2171 | /* Emit the HALT instruction */ | ||
2172 | jx9VmEmitInstr(pGen->pVm, JX9_OP_HALT, nExpr, 0, 0, 0); | ||
2173 | return SXRET_OK; | ||
2174 | } | ||
2175 | /* | ||
2176 | * Compile the static statement. | ||
2177 | * According to the JX9 language reference | ||
2178 | * Another important feature of variable scoping is the static variable. | ||
2179 | * A static variable exists only in a local function scope, but it does not lose its value | ||
2180 | * when program execution leaves this scope. | ||
2181 | * Static variables also provide one way to deal with recursive functions. | ||
2182 | */ | ||
2183 | static sxi32 jx9CompileStatic(jx9_gen_state *pGen) | ||
2184 | { | ||
2185 | jx9_vm_func_static_var sStatic; /* Structure describing the static variable */ | ||
2186 | jx9_vm_func *pFunc; /* Enclosing function */ | ||
2187 | GenBlock *pBlock; | ||
2188 | SyString *pName; | ||
2189 | char *zDup; | ||
2190 | sxu32 nLine; | ||
2191 | sxi32 rc; | ||
2192 | /* Jump the static keyword */ | ||
2193 | nLine = pGen->pIn->nLine; | ||
2194 | pGen->pIn++; | ||
2195 | /* Extract the enclosing function if any */ | ||
2196 | pBlock = pGen->pCurrent; | ||
2197 | while( pBlock ){ | ||
2198 | if( pBlock->iFlags & GEN_BLOCK_FUNC){ | ||
2199 | break; | ||
2200 | } | ||
2201 | /* Point to the upper block */ | ||
2202 | pBlock = pBlock->pParent; | ||
2203 | } | ||
2204 | if( pBlock == 0 ){ | ||
2205 | /* Static statement, called outside of a function body, treat it as a simple variable. */ | ||
2206 | if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_DOLLAR) == 0 ){ | ||
2207 | rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Expected variable after 'static' keyword"); | ||
2208 | if( rc == SXERR_ABORT ){ | ||
2209 | return SXERR_ABORT; | ||
2210 | } | ||
2211 | goto Synchronize; | ||
2212 | } | ||
2213 | /* Compile the expression holding the variable */ | ||
2214 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
2215 | if( rc == SXERR_ABORT ){ | ||
2216 | return SXERR_ABORT; | ||
2217 | }else if( rc != SXERR_EMPTY ){ | ||
2218 | /* Emit the POP instruction */ | ||
2219 | jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0); | ||
2220 | } | ||
2221 | return SXRET_OK; | ||
2222 | } | ||
2223 | pFunc = (jx9_vm_func *)pBlock->pUserData; | ||
2224 | /* Make sure we are dealing with a valid statement */ | ||
2225 | if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_DOLLAR) == 0 || &pGen->pIn[1] >= pGen->pEnd || | ||
2226 | (pGen->pIn[1].nType & (JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){ | ||
2227 | rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Expected variable after 'static' keyword"); | ||
2228 | if( rc == SXERR_ABORT ){ | ||
2229 | return SXERR_ABORT; | ||
2230 | } | ||
2231 | goto Synchronize; | ||
2232 | } | ||
2233 | pGen->pIn++; | ||
2234 | /* Extract variable name */ | ||
2235 | pName = &pGen->pIn->sData; | ||
2236 | pGen->pIn++; /* Jump the var name */ | ||
2237 | if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI/*';'*/|JX9_TK_EQUAL/*'='*/)) == 0 ){ | ||
2238 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "static: Unexpected token '%z'", &pGen->pIn->sData); | ||
2239 | goto Synchronize; | ||
2240 | } | ||
2241 | /* Initialize the structure describing the static variable */ | ||
2242 | SySetInit(&sStatic.aByteCode, &pGen->pVm->sAllocator, sizeof(VmInstr)); | ||
2243 | sStatic.nIdx = SXU32_HIGH; /* Not yet created */ | ||
2244 | /* Duplicate variable name */ | ||
2245 | zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator, pName->zString, pName->nByte); | ||
2246 | if( zDup == 0 ){ | ||
2247 | jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Fatal, JX9 engine is running out of memory"); | ||
2248 | return SXERR_ABORT; | ||
2249 | } | ||
2250 | SyStringInitFromBuf(&sStatic.sName, zDup, pName->nByte); | ||
2251 | /* Check if we have an expression to compile */ | ||
2252 | if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_EQUAL) ){ | ||
2253 | SySet *pInstrContainer; | ||
2254 | pGen->pIn++; /* Jump the equal '=' sign */ | ||
2255 | /* Swap bytecode container */ | ||
2256 | pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm); | ||
2257 | jx9VmSetByteCodeContainer(pGen->pVm, &sStatic.aByteCode); | ||
2258 | /* Compile the expression */ | ||
2259 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
2260 | /* Emit the done instruction */ | ||
2261 | jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0); | ||
2262 | /* Restore default bytecode container */ | ||
2263 | jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer); | ||
2264 | } | ||
2265 | /* Finally save the compiled static variable in the appropriate container */ | ||
2266 | SySetPut(&pFunc->aStatic, (const void *)&sStatic); | ||
2267 | return SXRET_OK; | ||
2268 | Synchronize: | ||
2269 | /* Synchronize with the first semi-colon ';', so we can avoid compiling this erroneous | ||
2270 | * statement. | ||
2271 | */ | ||
2272 | while(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ | ||
2273 | pGen->pIn++; | ||
2274 | } | ||
2275 | return SXRET_OK; | ||
2276 | } | ||
2277 | /* | ||
2278 | * Compile the 'const' statement. | ||
2279 | * According to the JX9 language reference | ||
2280 | * A constant is an identifier (name) for a simple value. As the name suggests, that value | ||
2281 | * cannot change during the execution of the script (except for magic constants, which aren't actually constants). | ||
2282 | * A constant is case-sensitive by default. By convention, constant identifiers are always uppercase. | ||
2283 | * The name of a constant follows the same rules as any label in JX9. A valid constant name starts | ||
2284 | * with a letter or underscore, followed by any number of letters, numbers, or underscores. | ||
2285 | * As a regular expression it would be expressed thusly: [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]* | ||
2286 | * Syntax | ||
2287 | * You can define a constant by using the define()-function or by using the const keyword outside | ||
2288 | * a object definition. Once a constant is defined, it can never be changed or undefined. | ||
2289 | * You can get the value of a constant by simply specifying its name. Unlike with variables | ||
2290 | * you should not prepend a constant with a $. You can also use the function constant() to read | ||
2291 | * a constant's value if you wish to obtain the constant's name dynamically. Use get_defined_constants() | ||
2292 | * to get a list of all defined constants. | ||
2293 | */ | ||
2294 | static sxi32 jx9CompileConstant(jx9_gen_state *pGen) | ||
2295 | { | ||
2296 | SySet *pConsCode, *pInstrContainer; | ||
2297 | sxu32 nLine = pGen->pIn->nLine; | ||
2298 | SyString *pName; | ||
2299 | sxi32 rc; | ||
2300 | pGen->pIn++; /* Jump the 'const' keyword */ | ||
2301 | if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (JX9_TK_SSTR|JX9_TK_DSTR|JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){ | ||
2302 | /* Invalid constant name */ | ||
2303 | rc = jx9GenCompileError(pGen, E_ERROR, nLine, "const: Invalid constant name"); | ||
2304 | if( rc == SXERR_ABORT ){ | ||
2305 | /* Error count limit reached, abort immediately */ | ||
2306 | return SXERR_ABORT; | ||
2307 | } | ||
2308 | goto Synchronize; | ||
2309 | } | ||
2310 | /* Peek constant name */ | ||
2311 | pName = &pGen->pIn->sData; | ||
2312 | /* Make sure the constant name isn't reserved */ | ||
2313 | if( GenStateIsReservedID(pName) ){ | ||
2314 | /* Reserved constant */ | ||
2315 | rc = jx9GenCompileError(pGen, E_ERROR, nLine, "const: Cannot redeclare a reserved constant '%z'", pName); | ||
2316 | if( rc == SXERR_ABORT ){ | ||
2317 | /* Error count limit reached, abort immediately */ | ||
2318 | return SXERR_ABORT; | ||
2319 | } | ||
2320 | goto Synchronize; | ||
2321 | } | ||
2322 | pGen->pIn++; | ||
2323 | if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_EQUAL /* '=' */) == 0 ){ | ||
2324 | /* Invalid statement*/ | ||
2325 | rc = jx9GenCompileError(pGen, E_ERROR, nLine, "const: Expected '=' after constant name"); | ||
2326 | if( rc == SXERR_ABORT ){ | ||
2327 | /* Error count limit reached, abort immediately */ | ||
2328 | return SXERR_ABORT; | ||
2329 | } | ||
2330 | goto Synchronize; | ||
2331 | } | ||
2332 | pGen->pIn++; /*Jump the equal sign */ | ||
2333 | /* Allocate a new constant value container */ | ||
2334 | pConsCode = (SySet *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator, sizeof(SySet)); | ||
2335 | if( pConsCode == 0 ){ | ||
2336 | return GenStateOutOfMem(pGen); | ||
2337 | } | ||
2338 | SySetInit(pConsCode, &pGen->pVm->sAllocator, sizeof(VmInstr)); | ||
2339 | /* Swap bytecode container */ | ||
2340 | pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm); | ||
2341 | jx9VmSetByteCodeContainer(pGen->pVm, pConsCode); | ||
2342 | /* Compile constant value */ | ||
2343 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
2344 | /* Emit the done instruction */ | ||
2345 | jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0); | ||
2346 | jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer); | ||
2347 | if( rc == SXERR_ABORT ){ | ||
2348 | /* Don't worry about freeing memory, everything will be released shortly */ | ||
2349 | return SXERR_ABORT; | ||
2350 | } | ||
2351 | SySetSetUserData(pConsCode, pGen->pVm); | ||
2352 | /* Register the constant */ | ||
2353 | rc = jx9VmRegisterConstant(pGen->pVm, pName, jx9VmExpandConstantValue, pConsCode); | ||
2354 | if( rc != SXRET_OK ){ | ||
2355 | SySetRelease(pConsCode); | ||
2356 | SyMemBackendPoolFree(&pGen->pVm->sAllocator, pConsCode); | ||
2357 | } | ||
2358 | return SXRET_OK; | ||
2359 | Synchronize: | ||
2360 | /* Synchronize with the next-semi-colon and avoid compiling this erroneous statement */ | ||
2361 | while(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ | ||
2362 | pGen->pIn++; | ||
2363 | } | ||
2364 | return SXRET_OK; | ||
2365 | } | ||
2366 | /* | ||
2367 | * Compile the uplink construct. | ||
2368 | * According to the JX9 language reference | ||
2369 | * In JX9 global variables must be declared uplink inside a function if they are going | ||
2370 | * to be used in that function. | ||
2371 | * Example #1 Using global | ||
2372 | * $a = 1; | ||
2373 | * $b = 2; | ||
2374 | * function Sum() | ||
2375 | * { | ||
2376 | * uplink $a, $b; | ||
2377 | * $b = $a + $b; | ||
2378 | * } | ||
2379 | * Sum(); | ||
2380 | * print $b; | ||
2381 | * ?> | ||
2382 | * The above script will output 3. By declaring $a and $b global within the function | ||
2383 | * all references to either variable will refer to the global version. There is no limit | ||
2384 | * to the number of global variables that can be manipulated by a function. | ||
2385 | */ | ||
2386 | static sxi32 jx9CompileUplink(jx9_gen_state *pGen) | ||
2387 | { | ||
2388 | SyToken *pTmp, *pNext = 0; | ||
2389 | sxi32 nExpr; | ||
2390 | sxi32 rc; | ||
2391 | /* Jump the 'uplink' keyword */ | ||
2392 | pGen->pIn++; | ||
2393 | if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_SEMI) ){ | ||
2394 | /* Nothing to process */ | ||
2395 | return SXRET_OK; | ||
2396 | } | ||
2397 | pTmp = pGen->pEnd; | ||
2398 | nExpr = 0; | ||
2399 | while( SXRET_OK == jx9GetNextExpr(pGen->pIn, pTmp, &pNext) ){ | ||
2400 | if( pGen->pIn < pNext ){ | ||
2401 | pGen->pEnd = pNext; | ||
2402 | if( (pGen->pIn->nType & JX9_TK_DOLLAR) == 0 ){ | ||
2403 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "uplink: Expected variable name"); | ||
2404 | if( rc == SXERR_ABORT ){ | ||
2405 | return SXERR_ABORT; | ||
2406 | } | ||
2407 | }else{ | ||
2408 | pGen->pIn++; | ||
2409 | if( pGen->pIn >= pGen->pEnd ){ | ||
2410 | /* Emit a warning */ | ||
2411 | jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn[-1].nLine, "uplink: Empty variable name"); | ||
2412 | }else{ | ||
2413 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
2414 | if( rc == SXERR_ABORT ){ | ||
2415 | return SXERR_ABORT; | ||
2416 | }else if(rc != SXERR_EMPTY ){ | ||
2417 | nExpr++; | ||
2418 | } | ||
2419 | } | ||
2420 | } | ||
2421 | } | ||
2422 | /* Next expression in the stream */ | ||
2423 | pGen->pIn = pNext; | ||
2424 | /* Jump trailing commas */ | ||
2425 | while( pGen->pIn < pTmp && (pGen->pIn->nType & JX9_TK_COMMA) ){ | ||
2426 | pGen->pIn++; | ||
2427 | } | ||
2428 | } | ||
2429 | /* Restore token stream */ | ||
2430 | pGen->pEnd = pTmp; | ||
2431 | if( nExpr > 0 ){ | ||
2432 | /* Emit the uplink instruction */ | ||
2433 | jx9VmEmitInstr(pGen->pVm, JX9_OP_UPLINK, nExpr, 0, 0, 0); | ||
2434 | } | ||
2435 | return SXRET_OK; | ||
2436 | } | ||
2437 | /* | ||
2438 | * Compile a switch block. | ||
2439 | * (See block-comment below for more information) | ||
2440 | */ | ||
2441 | static sxi32 GenStateCompileSwitchBlock(jx9_gen_state *pGen,sxu32 *pBlockStart) | ||
2442 | { | ||
2443 | sxi32 rc = SXRET_OK; | ||
2444 | while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI/*';'*/|JX9_TK_COLON/*':'*/)) == 0 ){ | ||
2445 | /* Unexpected token */ | ||
2446 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'", &pGen->pIn->sData); | ||
2447 | if( rc == SXERR_ABORT ){ | ||
2448 | return SXERR_ABORT; | ||
2449 | } | ||
2450 | pGen->pIn++; | ||
2451 | } | ||
2452 | pGen->pIn++; | ||
2453 | /* First instruction to execute in this block. */ | ||
2454 | *pBlockStart = jx9VmInstrLength(pGen->pVm); | ||
2455 | /* Compile the block until we hit a case/default/endswitch keyword | ||
2456 | * or the '}' token */ | ||
2457 | for(;;){ | ||
2458 | if( pGen->pIn >= pGen->pEnd ){ | ||
2459 | /* No more input to process */ | ||
2460 | break; | ||
2461 | } | ||
2462 | rc = SXRET_OK; | ||
2463 | if( (pGen->pIn->nType & JX9_TK_KEYWORD) == 0 ){ | ||
2464 | if( pGen->pIn->nType & JX9_TK_CCB /*'}' */ ){ | ||
2465 | rc = SXERR_EOF; | ||
2466 | break; | ||
2467 | } | ||
2468 | }else{ | ||
2469 | sxi32 nKwrd; | ||
2470 | /* Extract the keyword */ | ||
2471 | nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData); | ||
2472 | if( nKwrd == JX9_TKWRD_CASE || nKwrd == JX9_TKWRD_DEFAULT ){ | ||
2473 | break; | ||
2474 | } | ||
2475 | } | ||
2476 | /* Compile block */ | ||
2477 | rc = jx9CompileBlock(&(*pGen)); | ||
2478 | if( rc == SXERR_ABORT ){ | ||
2479 | return SXERR_ABORT; | ||
2480 | } | ||
2481 | } | ||
2482 | return rc; | ||
2483 | } | ||
2484 | /* | ||
2485 | * Compile a case eXpression. | ||
2486 | * (See block-comment below for more information) | ||
2487 | */ | ||
2488 | static sxi32 GenStateCompileCaseExpr(jx9_gen_state *pGen, jx9_case_expr *pExpr) | ||
2489 | { | ||
2490 | SySet *pInstrContainer; | ||
2491 | SyToken *pEnd, *pTmp; | ||
2492 | sxi32 iNest = 0; | ||
2493 | sxi32 rc; | ||
2494 | /* Delimit the expression */ | ||
2495 | pEnd = pGen->pIn; | ||
2496 | while( pEnd < pGen->pEnd ){ | ||
2497 | if( pEnd->nType & JX9_TK_LPAREN /*(*/ ){ | ||
2498 | /* Increment nesting level */ | ||
2499 | iNest++; | ||
2500 | }else if( pEnd->nType & JX9_TK_RPAREN /*)*/ ){ | ||
2501 | /* Decrement nesting level */ | ||
2502 | iNest--; | ||
2503 | }else if( pEnd->nType & (JX9_TK_SEMI/*';'*/|JX9_TK_COLON/*;'*/) && iNest < 1 ){ | ||
2504 | break; | ||
2505 | } | ||
2506 | pEnd++; | ||
2507 | } | ||
2508 | if( pGen->pIn >= pEnd ){ | ||
2509 | rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "Empty case expression"); | ||
2510 | if( rc == SXERR_ABORT ){ | ||
2511 | /* Error count limit reached, abort immediately */ | ||
2512 | return SXERR_ABORT; | ||
2513 | } | ||
2514 | } | ||
2515 | /* Swap token stream */ | ||
2516 | pTmp = pGen->pEnd; | ||
2517 | pGen->pEnd = pEnd; | ||
2518 | pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm); | ||
2519 | jx9VmSetByteCodeContainer(pGen->pVm, &pExpr->aByteCode); | ||
2520 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
2521 | /* Emit the done instruction */ | ||
2522 | jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0); | ||
2523 | jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer); | ||
2524 | /* Update token stream */ | ||
2525 | pGen->pIn = pEnd; | ||
2526 | pGen->pEnd = pTmp; | ||
2527 | if( rc == SXERR_ABORT ){ | ||
2528 | return SXERR_ABORT; | ||
2529 | } | ||
2530 | return SXRET_OK; | ||
2531 | } | ||
2532 | /* | ||
2533 | * Compile the smart switch statement. | ||
2534 | * According to the JX9 language reference manual | ||
2535 | * The switch statement is similar to a series of IF statements on the same expression. | ||
2536 | * In many occasions, you may want to compare the same variable (or expression) with many | ||
2537 | * different values, and execute a different piece of code depending on which value it equals to. | ||
2538 | * This is exactly what the switch statement is for. | ||
2539 | * Note: Note that unlike some other languages, the continue statement applies to switch and acts | ||
2540 | * similar to break. If you have a switch inside a loop and wish to continue to the next iteration | ||
2541 | * of the outer loop, use continue 2. | ||
2542 | * Note that switch/case does loose comparision. | ||
2543 | * It is important to understand how the switch statement is executed in order to avoid mistakes. | ||
2544 | * The switch statement executes line by line (actually, statement by statement). | ||
2545 | * In the beginning, no code is executed. Only when a case statement is found with a value that | ||
2546 | * matches the value of the switch expression does JX9 begin to execute the statements. | ||
2547 | * JX9 continues to execute the statements until the end of the switch block, or the first time | ||
2548 | * it sees a break statement. If you don't write a break statement at the end of a case's statement list. | ||
2549 | * In a switch statement, the condition is evaluated only once and the result is compared to each | ||
2550 | * case statement. In an elseif statement, the condition is evaluated again. If your condition | ||
2551 | * is more complicated than a simple compare and/or is in a tight loop, a switch may be faster. | ||
2552 | * The statement list for a case can also be empty, which simply passes control into the statement | ||
2553 | * list for the next case. | ||
2554 | * The case expression may be any expression that evaluates to a simple type, that is, integer | ||
2555 | * or floating-point numbers and strings. | ||
2556 | */ | ||
2557 | static sxi32 jx9CompileSwitch(jx9_gen_state *pGen) | ||
2558 | { | ||
2559 | GenBlock *pSwitchBlock; | ||
2560 | SyToken *pTmp, *pEnd; | ||
2561 | jx9_switch *pSwitch; | ||
2562 | sxu32 nLine; | ||
2563 | sxi32 rc; | ||
2564 | nLine = pGen->pIn->nLine; | ||
2565 | /* Jump the 'switch' keyword */ | ||
2566 | pGen->pIn++; | ||
2567 | if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){ | ||
2568 | /* Syntax error */ | ||
2569 | rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after 'switch' keyword"); | ||
2570 | if( rc == SXERR_ABORT ){ | ||
2571 | /* Error count limit reached, abort immediately */ | ||
2572 | return SXERR_ABORT; | ||
2573 | } | ||
2574 | goto Synchronize; | ||
2575 | } | ||
2576 | /* Jump the left parenthesis '(' */ | ||
2577 | pGen->pIn++; | ||
2578 | pEnd = 0; /* cc warning */ | ||
2579 | /* Create the loop block */ | ||
2580 | rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP|GEN_BLOCK_SWITCH, | ||
2581 | jx9VmInstrLength(pGen->pVm), 0, &pSwitchBlock); | ||
2582 | if( rc != SXRET_OK ){ | ||
2583 | return SXERR_ABORT; | ||
2584 | } | ||
2585 | /* Delimit the condition */ | ||
2586 | jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd); | ||
2587 | if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){ | ||
2588 | /* Empty expression */ | ||
2589 | rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected expression after 'switch' keyword"); | ||
2590 | if( rc == SXERR_ABORT ){ | ||
2591 | /* Error count limit reached, abort immediately */ | ||
2592 | return SXERR_ABORT; | ||
2593 | } | ||
2594 | } | ||
2595 | /* Swap token streams */ | ||
2596 | pTmp = pGen->pEnd; | ||
2597 | pGen->pEnd = pEnd; | ||
2598 | /* Compile the expression */ | ||
2599 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
2600 | if( rc == SXERR_ABORT ){ | ||
2601 | /* Expression handler request an operation abort [i.e: Out-of-memory] */ | ||
2602 | return SXERR_ABORT; | ||
2603 | } | ||
2604 | /* Update token stream */ | ||
2605 | while(pGen->pIn < pEnd ){ | ||
2606 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, | ||
2607 | "Switch: Unexpected token '%z'", &pGen->pIn->sData); | ||
2608 | if( rc == SXERR_ABORT ){ | ||
2609 | return SXERR_ABORT; | ||
2610 | } | ||
2611 | pGen->pIn++; | ||
2612 | } | ||
2613 | pGen->pIn = &pEnd[1]; | ||
2614 | pGen->pEnd = pTmp; | ||
2615 | if( pGen->pIn >= pGen->pEnd || &pGen->pIn[1] >= pGen->pEnd || | ||
2616 | (pGen->pIn->nType & (JX9_TK_OCB/*'{'*/|JX9_TK_COLON/*:*/)) == 0 ){ | ||
2617 | pTmp = pGen->pIn; | ||
2618 | if( pTmp >= pGen->pEnd ){ | ||
2619 | pTmp--; | ||
2620 | } | ||
2621 | /* Unexpected token */ | ||
2622 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pTmp->nLine, "Switch: Unexpected token '%z'", &pTmp->sData); | ||
2623 | if( rc == SXERR_ABORT ){ | ||
2624 | return SXERR_ABORT; | ||
2625 | } | ||
2626 | goto Synchronize; | ||
2627 | } | ||
2628 | pGen->pIn++; /* Jump the leading curly braces/colons */ | ||
2629 | /* Create the switch blocks container */ | ||
2630 | pSwitch = (jx9_switch *)SyMemBackendAlloc(&pGen->pVm->sAllocator, sizeof(jx9_switch)); | ||
2631 | if( pSwitch == 0 ){ | ||
2632 | /* Abort compilation */ | ||
2633 | return GenStateOutOfMem(pGen); | ||
2634 | } | ||
2635 | /* Zero the structure */ | ||
2636 | SyZero(pSwitch, sizeof(jx9_switch)); | ||
2637 | /* Initialize fields */ | ||
2638 | SySetInit(&pSwitch->aCaseExpr, &pGen->pVm->sAllocator, sizeof(jx9_case_expr)); | ||
2639 | /* Emit the switch instruction */ | ||
2640 | jx9VmEmitInstr(pGen->pVm, JX9_OP_SWITCH, 0, 0, pSwitch, 0); | ||
2641 | /* Compile case blocks */ | ||
2642 | for(;;){ | ||
2643 | sxu32 nKwrd; | ||
2644 | if( pGen->pIn >= pGen->pEnd ){ | ||
2645 | /* No more input to process */ | ||
2646 | break; | ||
2647 | } | ||
2648 | if( (pGen->pIn->nType & JX9_TK_KEYWORD) == 0 ){ | ||
2649 | if( (pGen->pIn->nType & JX9_TK_CCB /*}*/) == 0 ){ | ||
2650 | /* Unexpected token */ | ||
2651 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Switch: Unexpected token '%z'", | ||
2652 | &pGen->pIn->sData); | ||
2653 | if( rc == SXERR_ABORT ){ | ||
2654 | return SXERR_ABORT; | ||
2655 | } | ||
2656 | /* FALL THROUGH */ | ||
2657 | } | ||
2658 | /* Block compiled */ | ||
2659 | break; | ||
2660 | } | ||
2661 | /* Extract the keyword */ | ||
2662 | nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData); | ||
2663 | if( nKwrd == JX9_TKWRD_DEFAULT ){ | ||
2664 | /* | ||
2665 | * Accroding to the JX9 language reference manual | ||
2666 | * A special case is the default case. This case matches anything | ||
2667 | * that wasn't matched by the other cases. | ||
2668 | */ | ||
2669 | if( pSwitch->nDefault > 0 ){ | ||
2670 | /* Default case already compiled */ | ||
2671 | rc = jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn->nLine, "Switch: 'default' case already compiled"); | ||
2672 | if( rc == SXERR_ABORT ){ | ||
2673 | return SXERR_ABORT; | ||
2674 | } | ||
2675 | } | ||
2676 | pGen->pIn++; /* Jump the 'default' keyword */ | ||
2677 | /* Compile the default block */ | ||
2678 | rc = GenStateCompileSwitchBlock(pGen,&pSwitch->nDefault); | ||
2679 | if( rc == SXERR_ABORT){ | ||
2680 | return SXERR_ABORT; | ||
2681 | }else if( rc == SXERR_EOF ){ | ||
2682 | break; | ||
2683 | } | ||
2684 | }else if( nKwrd == JX9_TKWRD_CASE ){ | ||
2685 | jx9_case_expr sCase; | ||
2686 | /* Standard case block */ | ||
2687 | pGen->pIn++; /* Jump the 'case' keyword */ | ||
2688 | /* initialize the structure */ | ||
2689 | SySetInit(&sCase.aByteCode, &pGen->pVm->sAllocator, sizeof(VmInstr)); | ||
2690 | /* Compile the case expression */ | ||
2691 | rc = GenStateCompileCaseExpr(pGen, &sCase); | ||
2692 | if( rc == SXERR_ABORT ){ | ||
2693 | return SXERR_ABORT; | ||
2694 | } | ||
2695 | /* Compile the case block */ | ||
2696 | rc = GenStateCompileSwitchBlock(pGen,&sCase.nStart); | ||
2697 | /* Insert in the switch container */ | ||
2698 | SySetPut(&pSwitch->aCaseExpr, (const void *)&sCase); | ||
2699 | if( rc == SXERR_ABORT){ | ||
2700 | return SXERR_ABORT; | ||
2701 | }else if( rc == SXERR_EOF ){ | ||
2702 | break; | ||
2703 | } | ||
2704 | }else{ | ||
2705 | /* Unexpected token */ | ||
2706 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Switch: Unexpected token '%z'", | ||
2707 | &pGen->pIn->sData); | ||
2708 | if( rc == SXERR_ABORT ){ | ||
2709 | return SXERR_ABORT; | ||
2710 | } | ||
2711 | break; | ||
2712 | } | ||
2713 | } | ||
2714 | /* Fix all jumps now the destination is resolved */ | ||
2715 | pSwitch->nOut = jx9VmInstrLength(pGen->pVm); | ||
2716 | GenStateFixJumps(pSwitchBlock, -1, jx9VmInstrLength(pGen->pVm)); | ||
2717 | /* Release the loop block */ | ||
2718 | GenStateLeaveBlock(pGen, 0); | ||
2719 | if( pGen->pIn < pGen->pEnd ){ | ||
2720 | /* Jump the trailing curly braces */ | ||
2721 | pGen->pIn++; | ||
2722 | } | ||
2723 | /* Statement successfully compiled */ | ||
2724 | return SXRET_OK; | ||
2725 | Synchronize: | ||
2726 | /* Synchronize with the first semi-colon */ | ||
2727 | while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ | ||
2728 | pGen->pIn++; | ||
2729 | } | ||
2730 | return SXRET_OK; | ||
2731 | } | ||
2732 | /* | ||
2733 | * Process default argument values. That is, a function may define C++-style default value | ||
2734 | * as follows: | ||
2735 | * function makecoffee($type = "cappuccino") | ||
2736 | * { | ||
2737 | * return "Making a cup of $type.\n"; | ||
2738 | * } | ||
2739 | * Some features: | ||
2740 | * 1 -) Default arguments value can be any complex expression [i.e: function call, annynoymous | ||
2741 | * functions, array member, ..] | ||
2742 | * 2 -) Full type hinting: (Arguments are automatically casted to the desired type) | ||
2743 | * Example: | ||
2744 | * function a(string $a){} function b(int $a, string $c, float $d){} | ||
2745 | * 3 -) Function overloading!! | ||
2746 | * Example: | ||
2747 | * function foo($a) { | ||
2748 | * return $a.JX9_EOL; | ||
2749 | * } | ||
2750 | * function foo($a, $b) { | ||
2751 | * return $a + $b; | ||
2752 | * } | ||
2753 | * print foo(5); // Prints "5" | ||
2754 | * print foo(5, 2); // Prints "7" | ||
2755 | * // Same arg | ||
2756 | * function foo(string $a) | ||
2757 | * { | ||
2758 | * print "a is a string\n"; | ||
2759 | * dump($a); | ||
2760 | * } | ||
2761 | * function foo(int $a) | ||
2762 | * { | ||
2763 | * print "a is integer\n"; | ||
2764 | * dump($a); | ||
2765 | * } | ||
2766 | * function foo(array $a) | ||
2767 | * { | ||
2768 | * print "a is an array\n"; | ||
2769 | * dump($a); | ||
2770 | * } | ||
2771 | * foo('This is a great feature'); // a is a string [first foo] | ||
2772 | * foo(52); // a is integer [second foo] | ||
2773 | * foo(array(14, __TIME__, __DATE__)); // a is an array [third foo] | ||
2774 | * Please refer to the official documentation for more information on the powerful extension | ||
2775 | * introduced by the JX9 engine. | ||
2776 | */ | ||
2777 | static sxi32 GenStateProcessArgValue(jx9_gen_state *pGen, jx9_vm_func_arg *pArg, SyToken *pIn, SyToken *pEnd) | ||
2778 | { | ||
2779 | SyToken *pTmpIn, *pTmpEnd; | ||
2780 | SySet *pInstrContainer; | ||
2781 | sxi32 rc; | ||
2782 | /* Swap token stream */ | ||
2783 | SWAP_DELIMITER(pGen, pIn, pEnd); | ||
2784 | pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm); | ||
2785 | jx9VmSetByteCodeContainer(pGen->pVm, &pArg->aByteCode); | ||
2786 | /* Compile the expression holding the argument value */ | ||
2787 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
2788 | /* Emit the done instruction */ | ||
2789 | jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0); | ||
2790 | jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer); | ||
2791 | RE_SWAP_DELIMITER(pGen); | ||
2792 | if( rc == SXERR_ABORT ){ | ||
2793 | return SXERR_ABORT; | ||
2794 | } | ||
2795 | return SXRET_OK; | ||
2796 | } | ||
2797 | /* | ||
2798 | * Collect function arguments one after one. | ||
2799 | * According to the JX9 language reference manual. | ||
2800 | * Information may be passed to functions via the argument list, which is a comma-delimited | ||
2801 | * list of expressions. | ||
2802 | * JX9 supports passing arguments by value (the default), passing by reference | ||
2803 | * and default argument values. Variable-length argument lists are also supported, | ||
2804 | * see also the function references for func_num_args(), func_get_arg(), and func_get_args() | ||
2805 | * for more information. | ||
2806 | * Example #1 Passing arrays to functions | ||
2807 | * <?jx9 | ||
2808 | * function takes_array($input) | ||
2809 | * { | ||
2810 | * print "$input[0] + $input[1] = ", $input[0]+$input[1]; | ||
2811 | * } | ||
2812 | * ?> | ||
2813 | * Making arguments be passed by reference | ||
2814 | * By default, function arguments are passed by value (so that if the value of the argument | ||
2815 | * within the function is changed, it does not get changed outside of the function). | ||
2816 | * To allow a function to modify its arguments, they must be passed by reference. | ||
2817 | * To have an argument to a function always passed by reference, prepend an ampersand (&) | ||
2818 | * to the argument name in the function definition: | ||
2819 | * Example #2 Passing function parameters by reference | ||
2820 | * <?jx9 | ||
2821 | * function add_some_extra(&$string) | ||
2822 | * { | ||
2823 | * $string .= 'and something extra.'; | ||
2824 | * } | ||
2825 | * $str = 'This is a string, '; | ||
2826 | * add_some_extra($str); | ||
2827 | * print $str; // outputs 'This is a string, and something extra.' | ||
2828 | * ?> | ||
2829 | * | ||
2830 | * JX9 have introduced powerful extension including full type hinting, function overloading | ||
2831 | * complex agrument values.Please refer to the official documentation for more information | ||
2832 | * on these extension. | ||
2833 | */ | ||
2834 | static sxi32 GenStateCollectFuncArgs(jx9_vm_func *pFunc, jx9_gen_state *pGen, SyToken *pEnd) | ||
2835 | { | ||
2836 | jx9_vm_func_arg sArg; /* Current processed argument */ | ||
2837 | SyToken *pCur, *pIn; /* Token stream */ | ||
2838 | SyBlob sSig; /* Function signature */ | ||
2839 | char *zDup; /* Copy of argument name */ | ||
2840 | sxi32 rc; | ||
2841 | |||
2842 | pIn = pGen->pIn; | ||
2843 | pCur = 0; | ||
2844 | SyBlobInit(&sSig, &pGen->pVm->sAllocator); | ||
2845 | /* Process arguments one after one */ | ||
2846 | for(;;){ | ||
2847 | if( pIn >= pEnd ){ | ||
2848 | /* No more arguments to process */ | ||
2849 | break; | ||
2850 | } | ||
2851 | SyZero(&sArg, sizeof(jx9_vm_func_arg)); | ||
2852 | SySetInit(&sArg.aByteCode, &pGen->pVm->sAllocator, sizeof(VmInstr)); | ||
2853 | if( pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD) ){ | ||
2854 | if( pIn->nType & JX9_TK_KEYWORD ){ | ||
2855 | sxu32 nKey = (sxu32)(SX_PTR_TO_INT(pIn->pUserData)); | ||
2856 | if( nKey & JX9_TKWRD_BOOL ){ | ||
2857 | sArg.nType = MEMOBJ_BOOL; | ||
2858 | }else if( nKey & JX9_TKWRD_INT ){ | ||
2859 | sArg.nType = MEMOBJ_INT; | ||
2860 | }else if( nKey & JX9_TKWRD_STRING ){ | ||
2861 | sArg.nType = MEMOBJ_STRING; | ||
2862 | }else if( nKey & JX9_TKWRD_FLOAT ){ | ||
2863 | sArg.nType = MEMOBJ_REAL; | ||
2864 | }else{ | ||
2865 | jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn->nLine, | ||
2866 | "Invalid argument type '%z', Automatic cast will not be performed", | ||
2867 | &pIn->sData); | ||
2868 | } | ||
2869 | } | ||
2870 | pIn++; | ||
2871 | } | ||
2872 | if( pIn >= pEnd ){ | ||
2873 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Missing argument name"); | ||
2874 | return rc; | ||
2875 | } | ||
2876 | if( pIn >= pEnd || (pIn->nType & JX9_TK_DOLLAR) == 0 || &pIn[1] >= pEnd || (pIn[1].nType & (JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){ | ||
2877 | /* Invalid argument */ | ||
2878 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Invalid argument name"); | ||
2879 | return rc; | ||
2880 | } | ||
2881 | pIn++; /* Jump the dollar sign */ | ||
2882 | /* Copy argument name */ | ||
2883 | zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator, SyStringData(&pIn->sData), SyStringLength(&pIn->sData)); | ||
2884 | if( zDup == 0 ){ | ||
2885 | return GenStateOutOfMem(pGen); | ||
2886 | } | ||
2887 | SyStringInitFromBuf(&sArg.sName, zDup, SyStringLength(&pIn->sData)); | ||
2888 | pIn++; | ||
2889 | if( pIn < pEnd ){ | ||
2890 | if( pIn->nType & JX9_TK_EQUAL ){ | ||
2891 | SyToken *pDefend; | ||
2892 | sxi32 iNest = 0; | ||
2893 | pIn++; /* Jump the equal sign */ | ||
2894 | pDefend = pIn; | ||
2895 | /* Process the default value associated with this argument */ | ||
2896 | while( pDefend < pEnd ){ | ||
2897 | if( (pDefend->nType & JX9_TK_COMMA) && iNest <= 0 ){ | ||
2898 | break; | ||
2899 | } | ||
2900 | if( pDefend->nType & (JX9_TK_LPAREN/*'('*/|JX9_TK_OCB/*'{'*/|JX9_TK_OSB/*[*/) ){ | ||
2901 | /* Increment nesting level */ | ||
2902 | iNest++; | ||
2903 | }else if( pDefend->nType & (JX9_TK_RPAREN/*')'*/|JX9_TK_CCB/*'}'*/|JX9_TK_CSB/*]*/) ){ | ||
2904 | /* Decrement nesting level */ | ||
2905 | iNest--; | ||
2906 | } | ||
2907 | pDefend++; | ||
2908 | } | ||
2909 | if( pIn >= pDefend ){ | ||
2910 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pIn->nLine, "Missing argument default value"); | ||
2911 | return rc; | ||
2912 | } | ||
2913 | /* Process default value */ | ||
2914 | rc = GenStateProcessArgValue(&(*pGen), &sArg, pIn, pDefend); | ||
2915 | if( rc != SXRET_OK ){ | ||
2916 | return rc; | ||
2917 | } | ||
2918 | /* Point beyond the default value */ | ||
2919 | pIn = pDefend; | ||
2920 | } | ||
2921 | if( pIn < pEnd && (pIn->nType & JX9_TK_COMMA) == 0 ){ | ||
2922 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pIn->nLine, "Unexpected token '%z'", &pIn->sData); | ||
2923 | return rc; | ||
2924 | } | ||
2925 | pIn++; /* Jump the trailing comma */ | ||
2926 | } | ||
2927 | /* Append argument signature */ | ||
2928 | if( sArg.nType > 0 ){ | ||
2929 | int c; | ||
2930 | c = 'n'; /* cc warning */ | ||
2931 | /* Type leading character */ | ||
2932 | switch(sArg.nType){ | ||
2933 | case MEMOBJ_HASHMAP: | ||
2934 | /* Hashmap aka 'array' */ | ||
2935 | c = 'h'; | ||
2936 | break; | ||
2937 | case MEMOBJ_INT: | ||
2938 | /* Integer */ | ||
2939 | c = 'i'; | ||
2940 | break; | ||
2941 | case MEMOBJ_BOOL: | ||
2942 | /* Bool */ | ||
2943 | c = 'b'; | ||
2944 | break; | ||
2945 | case MEMOBJ_REAL: | ||
2946 | /* Float */ | ||
2947 | c = 'f'; | ||
2948 | break; | ||
2949 | case MEMOBJ_STRING: | ||
2950 | /* String */ | ||
2951 | c = 's'; | ||
2952 | break; | ||
2953 | default: | ||
2954 | break; | ||
2955 | } | ||
2956 | SyBlobAppend(&sSig, (const void *)&c, sizeof(char)); | ||
2957 | } | ||
2958 | /* Save in the argument set */ | ||
2959 | SySetPut(&pFunc->aArgs, (const void *)&sArg); | ||
2960 | } | ||
2961 | if( SyBlobLength(&sSig) > 0 ){ | ||
2962 | /* Save function signature */ | ||
2963 | SyStringInitFromBuf(&pFunc->sSignature, SyBlobData(&sSig), SyBlobLength(&sSig)); | ||
2964 | } | ||
2965 | return SXRET_OK; | ||
2966 | } | ||
2967 | /* | ||
2968 | * Compile function [i.e: standard function, annonymous function or closure ] body. | ||
2969 | * Return SXRET_OK on success. Any other return value indicates failure | ||
2970 | * and this routine takes care of generating the appropriate error message. | ||
2971 | */ | ||
2972 | static sxi32 GenStateCompileFuncBody( | ||
2973 | jx9_gen_state *pGen, /* Code generator state */ | ||
2974 | jx9_vm_func *pFunc /* Function state */ | ||
2975 | ) | ||
2976 | { | ||
2977 | SySet *pInstrContainer; /* Instruction container */ | ||
2978 | GenBlock *pBlock; | ||
2979 | sxi32 rc; | ||
2980 | /* Attach the new function */ | ||
2981 | rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_PROTECTED|GEN_BLOCK_FUNC,jx9VmInstrLength(pGen->pVm), pFunc, &pBlock); | ||
2982 | if( rc != SXRET_OK ){ | ||
2983 | return GenStateOutOfMem(pGen); | ||
2984 | } | ||
2985 | /* Swap bytecode containers */ | ||
2986 | pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm); | ||
2987 | jx9VmSetByteCodeContainer(pGen->pVm, &pFunc->aByteCode); | ||
2988 | /* Compile the body */ | ||
2989 | jx9CompileBlock(&(*pGen)); | ||
2990 | /* Emit the final return if not yet done */ | ||
2991 | jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, 0, 0, 0, 0); | ||
2992 | /* Restore the default container */ | ||
2993 | jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer); | ||
2994 | /* Leave function block */ | ||
2995 | GenStateLeaveBlock(&(*pGen), 0); | ||
2996 | if( rc == SXERR_ABORT ){ | ||
2997 | /* Don't worry about freeing memory, everything will be released shortly */ | ||
2998 | return SXERR_ABORT; | ||
2999 | } | ||
3000 | /* All done, function body compiled */ | ||
3001 | return SXRET_OK; | ||
3002 | } | ||
3003 | /* | ||
3004 | * Compile a JX9 function whether is a Standard or Annonymous function. | ||
3005 | * According to the JX9 language reference manual. | ||
3006 | * Function names follow the same rules as other labels in JX9. A valid function name | ||
3007 | * starts with a letter or underscore, followed by any number of letters, numbers, or | ||
3008 | * underscores. As a regular expression, it would be expressed thus: | ||
3009 | * [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*. | ||
3010 | * Functions need not be defined before they are referenced. | ||
3011 | * All functions and objectes in JX9 have the global scope - they can be called outside | ||
3012 | * a function even if they were defined inside and vice versa. | ||
3013 | * It is possible to call recursive functions in JX9. However avoid recursive function/method | ||
3014 | * calls with over 32-64 recursion levels. | ||
3015 | * | ||
3016 | * JX9 have introduced powerful extension including full type hinting, function overloading, | ||
3017 | * complex agrument values and more. Please refer to the official documentation for more information | ||
3018 | * on these extension. | ||
3019 | */ | ||
3020 | static sxi32 GenStateCompileFunc( | ||
3021 | jx9_gen_state *pGen, /* Code generator state */ | ||
3022 | SyString *pName, /* Function name. NULL otherwise */ | ||
3023 | sxi32 iFlags, /* Control flags */ | ||
3024 | jx9_vm_func **ppFunc /* OUT: function state */ | ||
3025 | ) | ||
3026 | { | ||
3027 | jx9_vm_func *pFunc; | ||
3028 | SyToken *pEnd; | ||
3029 | sxu32 nLine; | ||
3030 | char *zName; | ||
3031 | sxi32 rc; | ||
3032 | /* Extract line number */ | ||
3033 | nLine = pGen->pIn->nLine; | ||
3034 | /* Jump the left parenthesis '(' */ | ||
3035 | pGen->pIn++; | ||
3036 | /* Delimit the function signature */ | ||
3037 | jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd); | ||
3038 | if( pEnd >= pGen->pEnd ){ | ||
3039 | /* Syntax error */ | ||
3040 | rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Missing ')' after function '%z' signature", pName); | ||
3041 | if( rc == SXERR_ABORT ){ | ||
3042 | /* Error count limit reached, abort immediately */ | ||
3043 | return SXERR_ABORT; | ||
3044 | } | ||
3045 | pGen->pIn = pGen->pEnd; | ||
3046 | return SXRET_OK; | ||
3047 | } | ||
3048 | /* Create the function state */ | ||
3049 | pFunc = (jx9_vm_func *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator, sizeof(jx9_vm_func)); | ||
3050 | if( pFunc == 0 ){ | ||
3051 | goto OutOfMem; | ||
3052 | } | ||
3053 | /* function ID */ | ||
3054 | zName = SyMemBackendStrDup(&pGen->pVm->sAllocator, pName->zString, pName->nByte); | ||
3055 | if( zName == 0 ){ | ||
3056 | /* Don't worry about freeing memory, everything will be released shortly */ | ||
3057 | goto OutOfMem; | ||
3058 | } | ||
3059 | /* Initialize the function state */ | ||
3060 | jx9VmInitFuncState(pGen->pVm, pFunc, zName, pName->nByte, iFlags, 0); | ||
3061 | if( pGen->pIn < pEnd ){ | ||
3062 | /* Collect function arguments */ | ||
3063 | rc = GenStateCollectFuncArgs(pFunc, &(*pGen), pEnd); | ||
3064 | if( rc == SXERR_ABORT ){ | ||
3065 | /* Don't worry about freeing memory, everything will be released shortly */ | ||
3066 | return SXERR_ABORT; | ||
3067 | } | ||
3068 | } | ||
3069 | /* Compile function body */ | ||
3070 | pGen->pIn = &pEnd[1]; | ||
3071 | /* Compile the body */ | ||
3072 | rc = GenStateCompileFuncBody(&(*pGen), pFunc); | ||
3073 | if( rc == SXERR_ABORT ){ | ||
3074 | return SXERR_ABORT; | ||
3075 | } | ||
3076 | if( ppFunc ){ | ||
3077 | *ppFunc = pFunc; | ||
3078 | } | ||
3079 | /* Finally register the function */ | ||
3080 | rc = jx9VmInstallUserFunction(pGen->pVm, pFunc, 0); | ||
3081 | return rc; | ||
3082 | /* Fall through if something goes wrong */ | ||
3083 | OutOfMem: | ||
3084 | /* If the supplied memory subsystem is so sick that we are unable to allocate | ||
3085 | * a tiny chunk of memory, there is no much we can do here. | ||
3086 | */ | ||
3087 | return GenStateOutOfMem(pGen); | ||
3088 | } | ||
3089 | /* | ||
3090 | * Compile a standard JX9 function. | ||
3091 | * Refer to the block-comment above for more information. | ||
3092 | */ | ||
3093 | static sxi32 jx9CompileFunction(jx9_gen_state *pGen) | ||
3094 | { | ||
3095 | SyString *pName; | ||
3096 | sxi32 iFlags; | ||
3097 | sxu32 nLine; | ||
3098 | sxi32 rc; | ||
3099 | |||
3100 | nLine = pGen->pIn->nLine; | ||
3101 | pGen->pIn++; /* Jump the 'function' keyword */ | ||
3102 | iFlags = 0; | ||
3103 | if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){ | ||
3104 | /* Invalid function name */ | ||
3105 | rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Invalid function name"); | ||
3106 | if( rc == SXERR_ABORT ){ | ||
3107 | return SXERR_ABORT; | ||
3108 | } | ||
3109 | /* Sychronize with the next semi-colon or braces*/ | ||
3110 | while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){ | ||
3111 | pGen->pIn++; | ||
3112 | } | ||
3113 | return SXRET_OK; | ||
3114 | } | ||
3115 | pName = &pGen->pIn->sData; | ||
3116 | nLine = pGen->pIn->nLine; | ||
3117 | /* Jump the function name */ | ||
3118 | pGen->pIn++; | ||
3119 | if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){ | ||
3120 | /* Syntax error */ | ||
3121 | rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after function name '%z'", pName); | ||
3122 | if( rc == SXERR_ABORT ){ | ||
3123 | /* Error count limit reached, abort immediately */ | ||
3124 | return SXERR_ABORT; | ||
3125 | } | ||
3126 | /* Sychronize with the next semi-colon or '{' */ | ||
3127 | while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){ | ||
3128 | pGen->pIn++; | ||
3129 | } | ||
3130 | return SXRET_OK; | ||
3131 | } | ||
3132 | /* Compile function body */ | ||
3133 | rc = GenStateCompileFunc(&(*pGen),pName,iFlags,0); | ||
3134 | return rc; | ||
3135 | } | ||
3136 | /* | ||
3137 | * Generate bytecode for a given expression tree. | ||
3138 | * If something goes wrong while generating bytecode | ||
3139 | * for the expression tree (A very unlikely scenario) | ||
3140 | * this function takes care of generating the appropriate | ||
3141 | * error message. | ||
3142 | */ | ||
3143 | static sxi32 GenStateEmitExprCode( | ||
3144 | jx9_gen_state *pGen, /* Code generator state */ | ||
3145 | jx9_expr_node *pNode, /* Root of the expression tree */ | ||
3146 | sxi32 iFlags /* Control flags */ | ||
3147 | ) | ||
3148 | { | ||
3149 | VmInstr *pInstr; | ||
3150 | sxu32 nJmpIdx; | ||
3151 | sxi32 iP1 = 0; | ||
3152 | sxu32 iP2 = 0; | ||
3153 | void *p3 = 0; | ||
3154 | sxi32 iVmOp; | ||
3155 | sxi32 rc; | ||
3156 | if( pNode->xCode ){ | ||
3157 | SyToken *pTmpIn, *pTmpEnd; | ||
3158 | /* Compile node */ | ||
3159 | SWAP_DELIMITER(pGen, pNode->pStart, pNode->pEnd); | ||
3160 | rc = pNode->xCode(&(*pGen), iFlags); | ||
3161 | RE_SWAP_DELIMITER(pGen); | ||
3162 | return rc; | ||
3163 | } | ||
3164 | if( pNode->pOp == 0 ){ | ||
3165 | jx9GenCompileError(&(*pGen), E_ERROR, pNode->pStart->nLine, | ||
3166 | "Invalid expression node, JX9 is aborting compilation"); | ||
3167 | return SXERR_ABORT; | ||
3168 | } | ||
3169 | iVmOp = pNode->pOp->iVmOp; | ||
3170 | if( pNode->pOp->iOp == EXPR_OP_QUESTY ){ | ||
3171 | sxu32 nJz, nJmp; | ||
3172 | /* Ternary operator require special handling */ | ||
3173 | /* Phase#1: Compile the condition */ | ||
3174 | rc = GenStateEmitExprCode(&(*pGen), pNode->pCond, iFlags); | ||
3175 | if( rc != SXRET_OK ){ | ||
3176 | return rc; | ||
3177 | } | ||
3178 | nJz = nJmp = 0; /* cc -O6 warning */ | ||
3179 | /* Phase#2: Emit the false jump */ | ||
3180 | jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 0, 0, 0, &nJz); | ||
3181 | if( pNode->pLeft ){ | ||
3182 | /* Phase#3: Compile the 'then' expression */ | ||
3183 | rc = GenStateEmitExprCode(&(*pGen), pNode->pLeft, iFlags); | ||
3184 | if( rc != SXRET_OK ){ | ||
3185 | return rc; | ||
3186 | } | ||
3187 | } | ||
3188 | /* Phase#4: Emit the unconditional jump */ | ||
3189 | jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, 0, 0, &nJmp); | ||
3190 | /* Phase#5: Fix the false jump now the jump destination is resolved. */ | ||
3191 | pInstr = jx9VmGetInstr(pGen->pVm, nJz); | ||
3192 | if( pInstr ){ | ||
3193 | pInstr->iP2 = jx9VmInstrLength(pGen->pVm); | ||
3194 | } | ||
3195 | /* Phase#6: Compile the 'else' expression */ | ||
3196 | if( pNode->pRight ){ | ||
3197 | rc = GenStateEmitExprCode(&(*pGen), pNode->pRight, iFlags); | ||
3198 | if( rc != SXRET_OK ){ | ||
3199 | return rc; | ||
3200 | } | ||
3201 | } | ||
3202 | if( nJmp > 0 ){ | ||
3203 | /* Phase#7: Fix the unconditional jump */ | ||
3204 | pInstr = jx9VmGetInstr(pGen->pVm, nJmp); | ||
3205 | if( pInstr ){ | ||
3206 | pInstr->iP2 = jx9VmInstrLength(pGen->pVm); | ||
3207 | } | ||
3208 | } | ||
3209 | /* All done */ | ||
3210 | return SXRET_OK; | ||
3211 | } | ||
3212 | /* Generate code for the left tree */ | ||
3213 | if( pNode->pLeft ){ | ||
3214 | if( iVmOp == JX9_OP_CALL ){ | ||
3215 | jx9_expr_node **apNode; | ||
3216 | sxi32 n; | ||
3217 | /* Recurse and generate bytecodes for function arguments */ | ||
3218 | apNode = (jx9_expr_node **)SySetBasePtr(&pNode->aNodeArgs); | ||
3219 | /* Read-only load */ | ||
3220 | iFlags |= EXPR_FLAG_RDONLY_LOAD; | ||
3221 | for( n = 0 ; n < (sxi32)SySetUsed(&pNode->aNodeArgs) ; ++n ){ | ||
3222 | rc = GenStateEmitExprCode(&(*pGen), apNode[n], iFlags&~EXPR_FLAG_LOAD_IDX_STORE); | ||
3223 | if( rc != SXRET_OK ){ | ||
3224 | return rc; | ||
3225 | } | ||
3226 | } | ||
3227 | /* Total number of given arguments */ | ||
3228 | iP1 = (sxi32)SySetUsed(&pNode->aNodeArgs); | ||
3229 | /* Remove stale flags now */ | ||
3230 | iFlags &= ~EXPR_FLAG_RDONLY_LOAD; | ||
3231 | } | ||
3232 | rc = GenStateEmitExprCode(&(*pGen), pNode->pLeft, iFlags); | ||
3233 | if( rc != SXRET_OK ){ | ||
3234 | return rc; | ||
3235 | } | ||
3236 | if( iVmOp == JX9_OP_CALL ){ | ||
3237 | pInstr = jx9VmPeekInstr(pGen->pVm); | ||
3238 | if( pInstr ){ | ||
3239 | if ( pInstr->iOp == JX9_OP_LOADC ){ | ||
3240 | /* Prevent constant expansion */ | ||
3241 | pInstr->iP1 = 0; | ||
3242 | }else if( pInstr->iOp == JX9_OP_MEMBER /* $a.b(1, 2, 3) */ ){ | ||
3243 | /* Annonymous function call, flag that */ | ||
3244 | pInstr->iP2 = 1; | ||
3245 | } | ||
3246 | } | ||
3247 | }else if( iVmOp == JX9_OP_LOAD_IDX ){ | ||
3248 | jx9_expr_node **apNode; | ||
3249 | sxi32 n; | ||
3250 | /* Recurse and generate bytecodes for array index */ | ||
3251 | apNode = (jx9_expr_node **)SySetBasePtr(&pNode->aNodeArgs); | ||
3252 | for( n = 0 ; n < (sxi32)SySetUsed(&pNode->aNodeArgs) ; ++n ){ | ||
3253 | rc = GenStateEmitExprCode(&(*pGen), apNode[n], iFlags&~EXPR_FLAG_LOAD_IDX_STORE); | ||
3254 | if( rc != SXRET_OK ){ | ||
3255 | return rc; | ||
3256 | } | ||
3257 | } | ||
3258 | if( SySetUsed(&pNode->aNodeArgs) > 0 ){ | ||
3259 | iP1 = 1; /* Node have an index associated with it */ | ||
3260 | } | ||
3261 | if( iFlags & EXPR_FLAG_LOAD_IDX_STORE ){ | ||
3262 | /* Create an empty entry when the desired index is not found */ | ||
3263 | iP2 = 1; | ||
3264 | } | ||
3265 | }else if( pNode->pOp->iOp == EXPR_OP_COMMA ){ | ||
3266 | /* POP the left node */ | ||
3267 | jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0); | ||
3268 | } | ||
3269 | } | ||
3270 | rc = SXRET_OK; | ||
3271 | nJmpIdx = 0; | ||
3272 | /* Generate code for the right tree */ | ||
3273 | if( pNode->pRight ){ | ||
3274 | if( iVmOp == JX9_OP_LAND ){ | ||
3275 | /* Emit the false jump so we can short-circuit the logical and */ | ||
3276 | jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 1/* Keep the value on the stack */, 0, 0, &nJmpIdx); | ||
3277 | }else if (iVmOp == JX9_OP_LOR ){ | ||
3278 | /* Emit the true jump so we can short-circuit the logical or*/ | ||
3279 | jx9VmEmitInstr(pGen->pVm, JX9_OP_JNZ, 1/* Keep the value on the stack */, 0, 0, &nJmpIdx); | ||
3280 | }else if( pNode->pOp->iPrec == 18 /* Combined binary operators [i.e: =, '.=', '+=', *=' ...] precedence */ ){ | ||
3281 | iFlags |= EXPR_FLAG_LOAD_IDX_STORE; | ||
3282 | } | ||
3283 | rc = GenStateEmitExprCode(&(*pGen), pNode->pRight, iFlags); | ||
3284 | if( iVmOp == JX9_OP_STORE ){ | ||
3285 | pInstr = jx9VmPeekInstr(pGen->pVm); | ||
3286 | if( pInstr ){ | ||
3287 | if(pInstr->iOp == JX9_OP_MEMBER ){ | ||
3288 | /* Perform a member store operation [i.e: $this.x = 50] */ | ||
3289 | iP2 = 1; | ||
3290 | }else{ | ||
3291 | if( pInstr->iOp == JX9_OP_LOAD_IDX ){ | ||
3292 | /* Transform the STORE instruction to STORE_IDX instruction */ | ||
3293 | iVmOp = JX9_OP_STORE_IDX; | ||
3294 | iP1 = pInstr->iP1; | ||
3295 | }else{ | ||
3296 | p3 = pInstr->p3; | ||
3297 | } | ||
3298 | /* POP the last dynamic load instruction */ | ||
3299 | (void)jx9VmPopInstr(pGen->pVm); | ||
3300 | } | ||
3301 | } | ||
3302 | } | ||
3303 | } | ||
3304 | if( iVmOp > 0 ){ | ||
3305 | if( iVmOp == JX9_OP_INCR || iVmOp == JX9_OP_DECR ){ | ||
3306 | if( pNode->iFlags & EXPR_NODE_PRE_INCR ){ | ||
3307 | /* Pre-increment/decrement operator [i.e: ++$i, --$j ] */ | ||
3308 | iP1 = 1; | ||
3309 | } | ||
3310 | } | ||
3311 | /* Finally, emit the VM instruction associated with this operator */ | ||
3312 | jx9VmEmitInstr(pGen->pVm, iVmOp, iP1, iP2, p3, 0); | ||
3313 | if( nJmpIdx > 0 ){ | ||
3314 | /* Fix short-circuited jumps now the destination is resolved */ | ||
3315 | pInstr = jx9VmGetInstr(pGen->pVm, nJmpIdx); | ||
3316 | if( pInstr ){ | ||
3317 | pInstr->iP2 = jx9VmInstrLength(pGen->pVm); | ||
3318 | } | ||
3319 | } | ||
3320 | } | ||
3321 | return rc; | ||
3322 | } | ||
3323 | /* | ||
3324 | * Compile a JX9 expression. | ||
3325 | * According to the JX9 language reference manual: | ||
3326 | * Expressions are the most important building stones of JX9. | ||
3327 | * In JX9, almost anything you write is an expression. | ||
3328 | * The simplest yet most accurate way to define an expression | ||
3329 | * is "anything that has a value". | ||
3330 | * If something goes wrong while compiling the expression, this | ||
3331 | * function takes care of generating the appropriate error | ||
3332 | * message. | ||
3333 | */ | ||
3334 | static sxi32 jx9CompileExpr( | ||
3335 | jx9_gen_state *pGen, /* Code generator state */ | ||
3336 | sxi32 iFlags, /* Control flags */ | ||
3337 | sxi32 (*xTreeValidator)(jx9_gen_state *, jx9_expr_node *) /* Node validator callback.NULL otherwise */ | ||
3338 | ) | ||
3339 | { | ||
3340 | jx9_expr_node *pRoot; | ||
3341 | SySet sExprNode; | ||
3342 | SyToken *pEnd; | ||
3343 | sxi32 nExpr; | ||
3344 | sxi32 iNest; | ||
3345 | sxi32 rc; | ||
3346 | /* Initialize worker variables */ | ||
3347 | nExpr = 0; | ||
3348 | pRoot = 0; | ||
3349 | SySetInit(&sExprNode, &pGen->pVm->sAllocator, sizeof(jx9_expr_node *)); | ||
3350 | SySetAlloc(&sExprNode, 0x10); | ||
3351 | rc = SXRET_OK; | ||
3352 | /* Delimit the expression */ | ||
3353 | pEnd = pGen->pIn; | ||
3354 | iNest = 0; | ||
3355 | while( pEnd < pGen->pEnd ){ | ||
3356 | if( pEnd->nType & JX9_TK_OCB /* '{' */ ){ | ||
3357 | /* Ticket 1433-30: Annonymous/Closure functions body */ | ||
3358 | iNest++; | ||
3359 | }else if(pEnd->nType & JX9_TK_CCB /* '}' */ ){ | ||
3360 | iNest--; | ||
3361 | }else if( pEnd->nType & JX9_TK_SEMI /* ';' */ ){ | ||
3362 | if( iNest <= 0 ){ | ||
3363 | break; | ||
3364 | } | ||
3365 | } | ||
3366 | pEnd++; | ||
3367 | } | ||
3368 | if( iFlags & EXPR_FLAG_COMMA_STATEMENT ){ | ||
3369 | SyToken *pEnd2 = pGen->pIn; | ||
3370 | iNest = 0; | ||
3371 | /* Stop at the first comma */ | ||
3372 | while( pEnd2 < pEnd ){ | ||
3373 | if( pEnd2->nType & (JX9_TK_OCB/*'{'*/|JX9_TK_OSB/*'['*/|JX9_TK_LPAREN/*'('*/) ){ | ||
3374 | iNest++; | ||
3375 | }else if(pEnd2->nType & (JX9_TK_CCB/*'}'*/|JX9_TK_CSB/*']'*/|JX9_TK_RPAREN/*')'*/)){ | ||
3376 | iNest--; | ||
3377 | }else if( pEnd2->nType & JX9_TK_COMMA /*','*/ ){ | ||
3378 | if( iNest <= 0 ){ | ||
3379 | break; | ||
3380 | } | ||
3381 | } | ||
3382 | pEnd2++; | ||
3383 | } | ||
3384 | if( pEnd2 <pEnd ){ | ||
3385 | pEnd = pEnd2; | ||
3386 | } | ||
3387 | } | ||
3388 | if( pEnd > pGen->pIn ){ | ||
3389 | SyToken *pTmp = pGen->pEnd; | ||
3390 | /* Swap delimiter */ | ||
3391 | pGen->pEnd = pEnd; | ||
3392 | /* Try to get an expression tree */ | ||
3393 | rc = jx9ExprMakeTree(&(*pGen), &sExprNode, &pRoot); | ||
3394 | if( rc == SXRET_OK && pRoot ){ | ||
3395 | rc = SXRET_OK; | ||
3396 | if( xTreeValidator ){ | ||
3397 | /* Call the upper layer validator callback */ | ||
3398 | rc = xTreeValidator(&(*pGen), pRoot); | ||
3399 | } | ||
3400 | if( rc != SXERR_ABORT ){ | ||
3401 | /* Generate code for the given tree */ | ||
3402 | rc = GenStateEmitExprCode(&(*pGen), pRoot, iFlags); | ||
3403 | } | ||
3404 | nExpr = 1; | ||
3405 | } | ||
3406 | /* Release the whole tree */ | ||
3407 | jx9ExprFreeTree(&(*pGen), &sExprNode); | ||
3408 | /* Synchronize token stream */ | ||
3409 | pGen->pEnd = pTmp; | ||
3410 | pGen->pIn = pEnd; | ||
3411 | if( rc == SXERR_ABORT ){ | ||
3412 | SySetRelease(&sExprNode); | ||
3413 | return SXERR_ABORT; | ||
3414 | } | ||
3415 | } | ||
3416 | SySetRelease(&sExprNode); | ||
3417 | return nExpr > 0 ? SXRET_OK : SXERR_EMPTY; | ||
3418 | } | ||
3419 | /* | ||
3420 | * Return a pointer to the node construct handler associated | ||
3421 | * with a given node type [i.e: string, integer, float, ...]. | ||
3422 | */ | ||
3423 | JX9_PRIVATE ProcNodeConstruct jx9GetNodeHandler(sxu32 nNodeType) | ||
3424 | { | ||
3425 | if( nNodeType & JX9_TK_NUM ){ | ||
3426 | /* Numeric literal: Either real or integer */ | ||
3427 | return jx9CompileNumLiteral; | ||
3428 | }else if( nNodeType & JX9_TK_DSTR ){ | ||
3429 | /* Double quoted string */ | ||
3430 | return jx9CompileString; | ||
3431 | }else if( nNodeType & JX9_TK_SSTR ){ | ||
3432 | /* Single quoted string */ | ||
3433 | return jx9CompileSimpleString; | ||
3434 | }else if( nNodeType & JX9_TK_NOWDOC ){ | ||
3435 | /* Nowdoc */ | ||
3436 | return jx9CompileNowdoc; | ||
3437 | } | ||
3438 | return 0; | ||
3439 | } | ||
3440 | /* | ||
3441 | * Jx9 Language construct table. | ||
3442 | */ | ||
3443 | static const LangConstruct aLangConstruct[] = { | ||
3444 | { JX9_TKWRD_IF, jx9CompileIf }, | ||
3445 | { JX9_TKWRD_FUNCTION, jx9CompileFunction }, | ||
3446 | { JX9_TKWRD_FOREACH, jx9CompileForeach }, | ||
3447 | { JX9_TKWRD_WHILE, jx9CompileWhile }, | ||
3448 | { JX9_TKWRD_FOR, jx9CompileFor }, | ||
3449 | { JX9_TKWRD_SWITCH, jx9CompileSwitch }, | ||
3450 | { JX9_TKWRD_DIE, jx9CompileHalt }, | ||
3451 | { JX9_TKWRD_EXIT, jx9CompileHalt }, | ||
3452 | { JX9_TKWRD_RETURN, jx9CompileReturn }, | ||
3453 | { JX9_TKWRD_BREAK, jx9CompileBreak }, | ||
3454 | { JX9_TKWRD_CONTINUE, jx9CompileContinue }, | ||
3455 | { JX9_TKWRD_STATIC, jx9CompileStatic }, | ||
3456 | { JX9_TKWRD_UPLINK, jx9CompileUplink }, | ||
3457 | { JX9_TKWRD_CONST, jx9CompileConstant }, | ||
3458 | }; | ||
3459 | /* | ||
3460 | * Return a pointer to the statement handler routine associated | ||
3461 | * with a given JX9 keyword [i.e: if, for, while, ...]. | ||
3462 | */ | ||
3463 | static ProcLangConstruct GenStateGetStatementHandler( | ||
3464 | sxu32 nKeywordID /* Keyword ID*/ | ||
3465 | ) | ||
3466 | { | ||
3467 | sxu32 n = 0; | ||
3468 | for(;;){ | ||
3469 | if( n >= SX_ARRAYSIZE(aLangConstruct) ){ | ||
3470 | break; | ||
3471 | } | ||
3472 | if( aLangConstruct[n].nID == nKeywordID ){ | ||
3473 | /* Return a pointer to the handler. | ||
3474 | */ | ||
3475 | return aLangConstruct[n].xConstruct; | ||
3476 | } | ||
3477 | n++; | ||
3478 | } | ||
3479 | /* Not a language construct */ | ||
3480 | return 0; | ||
3481 | } | ||
3482 | /* | ||
3483 | * Compile a jx9 program. | ||
3484 | * If something goes wrong while compiling the Jx9 chunk, this function | ||
3485 | * takes care of generating the appropriate error message. | ||
3486 | */ | ||
3487 | static sxi32 GenStateCompileChunk( | ||
3488 | jx9_gen_state *pGen, /* Code generator state */ | ||
3489 | sxi32 iFlags /* Compile flags */ | ||
3490 | ) | ||
3491 | { | ||
3492 | ProcLangConstruct xCons; | ||
3493 | sxi32 rc; | ||
3494 | rc = SXRET_OK; /* Prevent compiler warning */ | ||
3495 | for(;;){ | ||
3496 | if( pGen->pIn >= pGen->pEnd ){ | ||
3497 | /* No more input to process */ | ||
3498 | break; | ||
3499 | } | ||
3500 | xCons = 0; | ||
3501 | if( pGen->pIn->nType & JX9_TK_KEYWORD ){ | ||
3502 | sxu32 nKeyword = (sxu32)SX_PTR_TO_INT(pGen->pIn->pUserData); | ||
3503 | /* Try to extract a language construct handler */ | ||
3504 | xCons = GenStateGetStatementHandler(nKeyword); | ||
3505 | if( xCons == 0 && !jx9IsLangConstruct(nKeyword) ){ | ||
3506 | rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, | ||
3507 | "Syntax error: Unexpected keyword '%z'", | ||
3508 | &pGen->pIn->sData); | ||
3509 | if( rc == SXERR_ABORT ){ | ||
3510 | break; | ||
3511 | } | ||
3512 | /* Synchronize with the first semi-colon and avoid compiling | ||
3513 | * this erroneous statement. | ||
3514 | */ | ||
3515 | xCons = jx9ErrorRecover; | ||
3516 | } | ||
3517 | } | ||
3518 | if( xCons == 0 ){ | ||
3519 | /* Assume an expression an try to compile it */ | ||
3520 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
3521 | if( rc != SXERR_EMPTY ){ | ||
3522 | /* Pop l-value */ | ||
3523 | jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0); | ||
3524 | } | ||
3525 | }else{ | ||
3526 | /* Go compile the sucker */ | ||
3527 | rc = xCons(&(*pGen)); | ||
3528 | } | ||
3529 | if( rc == SXERR_ABORT ){ | ||
3530 | /* Request to abort compilation */ | ||
3531 | break; | ||
3532 | } | ||
3533 | /* Ignore trailing semi-colons ';' */ | ||
3534 | while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) ){ | ||
3535 | pGen->pIn++; | ||
3536 | } | ||
3537 | if( iFlags & JX9_COMPILE_SINGLE_STMT ){ | ||
3538 | /* Compile a single statement and return */ | ||
3539 | break; | ||
3540 | } | ||
3541 | /* LOOP ONE */ | ||
3542 | /* LOOP TWO */ | ||
3543 | /* LOOP THREE */ | ||
3544 | /* LOOP FOUR */ | ||
3545 | } | ||
3546 | /* Return compilation status */ | ||
3547 | return rc; | ||
3548 | } | ||
3549 | /* | ||
3550 | * Compile a raw chunk. The raw chunk can contain JX9 code embedded | ||
3551 | * in HTML, XML and so on. This function handle all the stuff. | ||
3552 | * This is the only compile interface exported from this file. | ||
3553 | */ | ||
3554 | JX9_PRIVATE sxi32 jx9CompileScript( | ||
3555 | jx9_vm *pVm, /* Generate JX9 bytecodes for this Virtual Machine */ | ||
3556 | SyString *pScript, /* Script to compile */ | ||
3557 | sxi32 iFlags /* Compile flags */ | ||
3558 | ) | ||
3559 | { | ||
3560 | jx9_gen_state *pGen; | ||
3561 | SySet aToken; | ||
3562 | sxi32 rc; | ||
3563 | if( pScript->nByte < 1 ){ | ||
3564 | /* Nothing to compile */ | ||
3565 | return JX9_OK; | ||
3566 | } | ||
3567 | /* Initialize the tokens containers */ | ||
3568 | SySetInit(&aToken, &pVm->sAllocator, sizeof(SyToken)); | ||
3569 | SySetAlloc(&aToken, 0xc0); | ||
3570 | pGen = &pVm->sCodeGen; | ||
3571 | rc = JX9_OK; | ||
3572 | /* Tokenize the JX9 chunk first */ | ||
3573 | jx9Tokenize(pScript->zString,pScript->nByte,&aToken); | ||
3574 | if( SySetUsed(&aToken) < 1 ){ | ||
3575 | return SXERR_EMPTY; | ||
3576 | } | ||
3577 | /* Point to the head and tail of the token stream. */ | ||
3578 | pGen->pIn = (SyToken *)SySetBasePtr(&aToken); | ||
3579 | pGen->pEnd = &pGen->pIn[SySetUsed(&aToken)]; | ||
3580 | /* Compile the chunk */ | ||
3581 | rc = GenStateCompileChunk(pGen,iFlags); | ||
3582 | /* Cleanup */ | ||
3583 | SySetRelease(&aToken); | ||
3584 | return rc; | ||
3585 | } | ||
3586 | /* | ||
3587 | * Utility routines.Initialize the code generator. | ||
3588 | */ | ||
3589 | JX9_PRIVATE sxi32 jx9InitCodeGenerator( | ||
3590 | jx9_vm *pVm, /* Target VM */ | ||
3591 | ProcConsumer xErr, /* Error log consumer callabck */ | ||
3592 | void *pErrData /* Last argument to xErr() */ | ||
3593 | ) | ||
3594 | { | ||
3595 | jx9_gen_state *pGen = &pVm->sCodeGen; | ||
3596 | /* Zero the structure */ | ||
3597 | SyZero(pGen, sizeof(jx9_gen_state)); | ||
3598 | /* Initial state */ | ||
3599 | pGen->pVm = &(*pVm); | ||
3600 | pGen->xErr = xErr; | ||
3601 | pGen->pErrData = pErrData; | ||
3602 | SyHashInit(&pGen->hLiteral, &pVm->sAllocator, 0, 0); | ||
3603 | SyHashInit(&pGen->hVar, &pVm->sAllocator, 0, 0); | ||
3604 | /* Create the global scope */ | ||
3605 | GenStateInitBlock(pGen, &pGen->sGlobal,GEN_BLOCK_GLOBAL,jx9VmInstrLength(&(*pVm)), 0); | ||
3606 | /* Point to the global scope */ | ||
3607 | pGen->pCurrent = &pGen->sGlobal; | ||
3608 | return SXRET_OK; | ||
3609 | } | ||
3610 | /* | ||
3611 | * Utility routines. Reset the code generator to it's initial state. | ||
3612 | */ | ||
3613 | JX9_PRIVATE sxi32 jx9ResetCodeGenerator( | ||
3614 | jx9_vm *pVm, /* Target VM */ | ||
3615 | ProcConsumer xErr, /* Error log consumer callabck */ | ||
3616 | void *pErrData /* Last argument to xErr() */ | ||
3617 | ) | ||
3618 | { | ||
3619 | jx9_gen_state *pGen = &pVm->sCodeGen; | ||
3620 | GenBlock *pBlock, *pParent; | ||
3621 | /* Point to the global scope */ | ||
3622 | pBlock = pGen->pCurrent; | ||
3623 | while( pBlock->pParent != 0 ){ | ||
3624 | pParent = pBlock->pParent; | ||
3625 | GenStateFreeBlock(pBlock); | ||
3626 | pBlock = pParent; | ||
3627 | } | ||
3628 | pGen->xErr = xErr; | ||
3629 | pGen->pErrData = pErrData; | ||
3630 | pGen->pCurrent = &pGen->sGlobal; | ||
3631 | pGen->pIn = pGen->pEnd = 0; | ||
3632 | pGen->nErr = 0; | ||
3633 | return SXRET_OK; | ||
3634 | } | ||
3635 | /* | ||
3636 | * Generate a compile-time error message. | ||
3637 | * If the error count limit is reached (usually 15 error message) | ||
3638 | * this function return SXERR_ABORT.In that case upper-layers must | ||
3639 | * abort compilation immediately. | ||
3640 | */ | ||
3641 | JX9_PRIVATE sxi32 jx9GenCompileError(jx9_gen_state *pGen,sxi32 nErrType,sxu32 nLine,const char *zFormat,...) | ||
3642 | { | ||
3643 | SyBlob *pWorker = &pGen->pVm->pEngine->xConf.sErrConsumer; | ||
3644 | const char *zErr = "Error"; | ||
3645 | va_list ap; | ||
3646 | if( nErrType == E_ERROR ){ | ||
3647 | /* Increment the error counter */ | ||
3648 | pGen->nErr++; | ||
3649 | if( pGen->nErr > 15 ){ | ||
3650 | /* Error count limit reached */ | ||
3651 | SyBlobFormat(pWorker, "%u Error count limit reached, JX9 is aborting compilation\n", nLine); | ||
3652 | /* Abort immediately */ | ||
3653 | return SXERR_ABORT; | ||
3654 | } | ||
3655 | } | ||
3656 | switch(nErrType){ | ||
3657 | case E_WARNING: zErr = "Warning"; break; | ||
3658 | case E_PARSE: zErr = "Parse error"; break; | ||
3659 | case E_NOTICE: zErr = "Notice"; break; | ||
3660 | default: | ||
3661 | break; | ||
3662 | } | ||
3663 | /* Format the error message */ | ||
3664 | SyBlobFormat(pWorker, "%u %s: ", nLine, zErr); | ||
3665 | va_start(ap, zFormat); | ||
3666 | SyBlobFormatAp(pWorker, zFormat, ap); | ||
3667 | va_end(ap); | ||
3668 | /* Append a new line */ | ||
3669 | SyBlobAppend(pWorker, (const void *)"\n", sizeof(char)); | ||
3670 | return JX9_OK; | ||
3671 | } | ||