diff options
| -rw-r--r-- | core.e | 5 | ||||
| -rw-r--r-- | elf.e | 25 | ||||
| -rw-r--r-- | evoke.e | 6 | ||||
| -rw-r--r-- | execution.e | 2 | ||||
| -rw-r--r-- | hello.e | 3 | ||||
| -rw-r--r-- | interpret.e | 2 | ||||
| -rw-r--r-- | labels.e | 2 | ||||
| -rw-r--r-- | recompile.e | 12 | ||||
| -rw-r--r-- | transform.e | 346 | ||||
| -rw-r--r-- | vim/syntax/evocation.vim | 12 |
10 files changed, 288 insertions, 127 deletions
diff --git a/core.e b/core.e index cb3921a..479fd26 100644 --- a/core.e +++ b/core.e @@ -7,6 +7,11 @@ ~ our goal right now is just to bootstrap the heap, we keep this short and ~ sweet. ~ +~ This code has been written to satisfy the assumptions needed by the label +~ transform, described in-depth in transform.e. This means that it is, in +~ effect, written in a restricted dialect of Evocation rather than in the full +~ language. +~ ~ There is definitely plenty of optimization that could be done. : swap diff --git a/elf.e b/elf.e index 708de13..68eb6f2 100644 --- a/elf.e +++ b/elf.e @@ -9,22 +9,11 @@ ~ This relies on the label facility defined in labels.e. Make sure to load ~ that first. -~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -~ ~~ Runtime memory origin ~~ -~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -~ -~ First, we pick an origin to load at. This is arbitrary, but it can't be -~ zero. We define a constant word for it so the body of the program can use -~ it in label calculations in whatever ways it needs to. - -: origin 0x08000000 ; - - ~ ~~~~~~~~~~~~~~~~~~~~~ ~ ~~ ELF file header ~~ ~ ~~~~~~~~~~~~~~~~~~~~~ ~ -~ Second, we output ELF's top-level file header. This header describes the +~ First, we output ELF's top-level file header. This header describes the ~ entire file. An ELF always has exactly one of this header, which is always ~ at the start of the file. ~ @@ -48,7 +37,7 @@ 0x3e pack16 ~ *Intel x86-64 1 pack32 ~ ELF format version - L@' cold-start origin + pack64 ~ *entry point + L@' cold-start L@' origin + pack64 ~ *entry point ~ This includes the origin, intentionally. L@' elf-program-header pack64 ~ *program header offset @@ -74,7 +63,7 @@ ~ ~~ ELF program header ~~ ~ ~~~~~~~~~~~~~~~~~~~~~~~~ ~ -~ Third, we output ELF's program header, which lists the memory regions +~ Second, we output ELF's program header, which lists the memory regions ~ ("segments") we want to have and where we want them to come from. There may ~ be any number of these entries, one per segment, , and they may be anywhere ~ in the file as long as they're consecutive. @@ -104,7 +93,7 @@ 1 pack32 ~ *"loadable" segment type 0x05 pack32 ~ *read+execute permission 0 pack64 ~ *offset in file - origin pack64 ~ *virtual address + L@' origin pack64 ~ *virtual address ~ required, but can be anything, subject to alignment 0 pack64 ~ physical address (ignored) @@ -123,6 +112,8 @@ ~ ~~~~~~~~~~~~~~~~ ~ ~ ELF is a simple format, really. Now you can output your own machine code -~ that you generate however you want; make sure to define the label -~ cold-start, which will be the first thing that runs. +~ that you generate however you want; make sure to define the labels "origin" +~ and "cold-start". Origin will control the address the code loads at; +~ cold-start will be the first thing that runs. The origin is arbitrary, but +~ can't be zero. diff --git a/evoke.e b/evoke.e index c34ab2a..68645cb 100644 --- a/evoke.e +++ b/evoke.e @@ -3,6 +3,8 @@ 1024 read-to-buffer : foo ;asm + +: bar ;asm pyrzqxgl s" source-to-precompile" variable @@ -15,6 +17,7 @@ s" source-to-precompile" variable ~ Everything directly called by all-contents has this same interface. ~ : all-contents + 0x08000000 L!' origin elf-file-header elf-program-header cold-start @@ -23,6 +26,9 @@ s" source-to-precompile" variable source-to-precompile transform 0 L!' final-word-name current-offset L!' total-size + 0 L!' : + 0 L!' ; + 0 L!' ;asm ; ' all-contents entry-to-execution-token label-loop diff --git a/execution.e b/execution.e index 1100efd..48761de 100644 --- a/execution.e +++ b/execution.e @@ -463,7 +463,7 @@ ~ We are about to set up rsi, we did rbp already, and rsp came to us ~ already set up. That's all that "next" needs, so take it away! - L@' warm-start origin + :rsi mov-reg64-imm64 + L@' warm-start L@' origin + :rsi mov-reg64-imm64 pack-next ; ~ Routine warm-start diff --git a/hello.e b/hello.e index fcafa25..6c20295 100644 --- a/hello.e +++ b/hello.e @@ -4,7 +4,7 @@ current-offset L!' cold-start 1 :rax mov-reg64-imm32 1 :rdi mov-reg64-imm64 - origin L@' greeting + :rsi mov-reg64-imm64 + L@' origin L@' greeting + :rsi mov-reg64-imm64 L@' greeting-size :rdx mov-reg64-imm64 syscall 60 :rax mov-reg64-imm32 @@ -23,6 +23,7 @@ ~ Everything directly called by all-contents has this same interface. ~ : all-contents + 0x08000000 L!' origin elf-file-header elf-program-header output-start-routine diff --git a/interpret.e b/interpret.e index eabbb09..f1efd4d 100644 --- a/interpret.e +++ b/interpret.e @@ -485,7 +485,7 @@ latest @ dup hide-entry entry-to-execution-token execute exit } if - ~ If it's not in the dictionary, check whether it's a decimal number. + ~ If it's not in the dictionary, check whether it's an integer literal. drop ~ As before, we get the stack address and use it as a string pointer. ~ (string) diff --git a/labels.e b/labels.e index bfc85fb..c437003 100644 --- a/labels.e +++ b/labels.e @@ -1,7 +1,7 @@ ~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~ ~~ Machine label facility ~~ ~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - +~ ~ Compilers and assemblers always have a need to resolve symbolic names for ~ parts of their code to numeric values. Usually, these symbolic names are ~ called labels. diff --git a/recompile.e b/recompile.e new file mode 100644 index 0000000..c6a6f7a --- /dev/null +++ b/recompile.e @@ -0,0 +1,12 @@ +~ (entry pointer --) +: recompile + dup word-heading + dup guess-entry-end swap entry-to-execution-token 8 + + { 2dup < } + { space dup @ dup is-codeword + { execution-token-to-entry entry-to-name emitstring } + { . } if-else + 8 + } while newline ; + +' , recompile +bye diff --git a/transform.e b/transform.e index 1c325df..154f477 100644 --- a/transform.e +++ b/transform.e @@ -1,3 +1,73 @@ +~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~ ~~ Code transformation facility ~~ +~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~ +~ TODO explain what problem this is solving and why +~ +~ The label transform operates on code that compiles itself, and ensures +~ that the result of the compilation is suitable to be included in an +~ executable binary. To achieve this, it makes several changes to the +~ semantics of that code. The transform relies on the label facility, and +~ expects to run from within label-loop. +~ +~ The most fundamental change is that the label transform separates words +~ that run in compile mode from words that run immediately. There is no +~ distinction made between words running in immediate mode, and words declared +~ as immediate. Immediate words are looked up and executed based on their +~ "real", currently-executing definitions. Compiled words, including +~ literals, are looked up via the label facility. +~ +~ Since the label facility is able to resolve forward references, there is +~ no hard requirement that words be topologically sorted, but forward +~ references should still be kept to a minimum, since that's a significant +~ difference from un-transformed code that could easily become confusing. +~ +~ Compilation words do make extensive reference to the global variables +~ "here" and "latest". In particular, flow-control words such as if-else +~ expect the log to have recent compilation outputs on it, and to be able to +~ mutate them in-place. In order to make this work, we provide temporary +~ values of these two variables which point to the location of the output +~ buffer. This allows pointer resolution to work correctly without additional +~ effort, but notice that the buffer's address will differ from the address +~ the resulting program loads itself at. There's no simple way to avoid this +~ concern, since the variables must point to one of those addresses or the +~ other, not both. +~ +~ We resolve the issue by running our own, alternate versions of the words +~ "create", ":", ";", and ";asm" which use the label facility to compute the +~ addresses that will be needed at runtime. These alternates run instead of +~ the normal versions of these words. The code being compiled is responsible +~ for not doing anything else that would rely on "here" and "latest" matching +~ their runtime addresses, though it is otherwise allowed to modify and rely +~ on them in all the usual ways. The alternate versions are defined in this +~ file as their own words, "Lcreate", "L:", "L;", and "L;asm". The alternates +~ rely on various labels, all of which must be defined elsewhere: +~ +~ * origin +~ * docol +~ * exit +~ * : +~ * ; +~ * ;asm +~ +~ Note that these alternates are applied via a purely lexical +~ transformation: when a word would be looked up in the dictionary to +~ interpret, first check if it's one of these. That means the transformation +~ won't apply to indirect callers of these words, nor to tick-quotes of them. +~ The code being compiled is responsible for not doing either of those things. +~ +~ Notably, the transformation uses the same "interpreter-flags" variable as +~ the rest of Evocation. There's no need to keep it separate like there is +~ with the other variables. This makes it easy to change modes. +~ +~ All of these limitations result in the compiled code being, in effect, +~ written in a dialect which is like Evocation, but more restricted. This is +~ acceptable, because the label transform is intended for compiling code that +~ is an early part of Evocation itself, and the necessary code has all been +~ written to follow these restrictions. + + +~ TODO all this buffer stuff should be in its own file ~ (buffer size -- buffer address) : read-to-buffer dup allocate dup dup @@ -39,6 +109,7 @@ { ~ If not, leave the word start alone. 3roll pack8 } if-else } if-else } forever ; + ~ In logical terms, this modifies an input buffer metadata structure ~ in-place to push a new, zeroed one into the start of the linked list formed ~ through the next-source field. @@ -56,8 +127,8 @@ 2dup swap 6 8 * memcopy ~ (original metadata pointer, new metadata pointer) swap dup zero-input-buffer-metadata - input-buffer-next-source ! - ; + input-buffer-next-source ! ; + ~ This does the inverse of push-input-buffer. In the event that the ~ next-source field is null, it zeroes the buffer. @@ -73,58 +144,109 @@ dup { 6 8 * memcopy } { drop zero-input-buffer-metadata } if-else ; -: L: - ' L' entry-to-execution-token execute - { ' set-label entry-to-execution-token , } - { set-label } if-else - ; make-immediate - ~ TODO probably needs to do more : transform-state-saved-here ; : transform-state-saved-latest 8 + ; +: transform-state-output-buffer-start 2 8 * + ; : allocate-transform-state - 2 8 * allocate + 3 8 * allocate dup transform-state-saved-here 0 swap ! - dup transform-state-saved-latest 0 swap ! ; + dup transform-state-saved-latest 0 swap ! + dup transform-state-output-buffer-start 0 swap ! ; allocate-transform-state s" transform-state" variable -~ The label transform operates on code that compiles itself, and ensures -~ that the result of the compilation is suitable to be included in an -~ executable binary. To achieve this, it makes several changes to the -~ semantics of that code. The transform relies on the label facility, and -~ expects to run from within label-loop. -~ -~ The most fundamental change is that the label transform separates words -~ that run in compile mode from words that run immediately. There is no -~ distinction made between words running in immediate mode, and words declared -~ as immediate. Immediate words are looked up and executed based on their -~ "real", currently-executing definitions. Compiled words, including -~ literals, are looked up via the label facility. -~ -~ Since the label facility is able to resolve forward references, there is -~ no hard requirement that words be topologically sorted, but forward -~ references should still be kept to a minimum, since that's a significant -~ difference from un-transformed code that could easily become confusing. -~ -~ Compilation words do make extensive reference to the global variables -~ "here" and "latest". In particular, flow-control words such as if-else -~ expect the log to have recent compilation outputs on it, and to be able to -~ mutate them in-place. In order to make this work, we provide temporary -~ values of these two variables which point to the location of the output -~ buffer. This allows pointer resolution to work correctly without additional -~ effort, but notice that the buffer's address will differ from the address -~ the resulting program loads itself at. There's no simple way to avoid this -~ concern, since the variables must point to one of those addresses or the -~ other, not both. + +~ When calling the label facility during a transformation, it's necessary +~ to use the real, non-wrapped "heap" and "latest". +: swap-transform-variables + here @ transform-state transform-state-saved-here @ + here ! transform-state transform-state-saved-here ! + latest @ transform-state transform-state-saved-latest @ + latest ! transform-state transform-state-saved-latest ! ; + +~ (address within the output buffer -- address at generated binary's runtime) +: transform-offset + ~ Don't transform null pointers. + dup { transform-state transform-state-output-buffer-start @ - + swap-transform-variables L@' origin swap-transform-variables + + } if ; + + +~ This is the alternate version of "create" for use with the label +~ transform. Its code is the same as the regular "create" except as noted +~ below. It is likely to be extremely useful to read and understand "create" +~ in interpret.e before attempting to understand "Lcreate". +: Lcreate + dup stringlen 1 + dup 3unroll + here @ 10 + 3unroll memmove + here @ + ~ This value of "latest" is going into the generated output, so call + ~ transform-offset on it first. + latest @ transform-offset pack64 + 0 pack8 + 0 pack8 + + + 8 packalign + here @ latest ! + here ! ; + + +~ This is the alternate version of ":" for use with the label transform. Its +~ code is the same as the regular "create" except as noted below. It is likely +~ to be extremely useful to read and understand ":" in interpret.e before +~ attempting to understand "L:". +: L: + ~ This calls "Lcreate" instead of "create". + word value@ Lcreate dropstring + + ~ This looks up "docol" by label. + swap-transform-variables + L@' docol + L@' origin + swap-transform-variables + + , + + latest @ hide-entry ] ; + + +~ This is the alternate version of ";" for use with the label transform. Its +~ code is the same as the regular "create" except as noted below. It is likely +~ to be extremely useful to read and understand ";" in interpret.e before +~ attempting to understand "L;". +: L; + ~ This looks up "exit" by label. + swap-transform-variables + L@' exit L@' origin + swap-transform-variables + + , + + latest @ unhide-entry + + ~ Since [ is an immediate word, we have to go to extra trouble to compile + ~ it as part of ;. + [ ' [ entry-to-execution-token , ] + ; make-immediate + + +~ This is the alternate version of ";asm" for use with the label transform. +~ Its code is the same as the regular "create" except as noted below. It is +~ likely to be extremely useful to read and understand ";asm" in interpret.e +~ before attempting to understand "L;asm". +: L;asm + here @ pack-next 8 packalign here ! + latest @ dup unhide-entry entry-to-execution-token dup 8 + swap ! + + ~ Since [ is an immediate word, we have to go to extra trouble to compile + ~ it as part of ;asm. + [ ' [ entry-to-execution-token , ] + ; make-immediate + + +~ This implements the label transform for a single word. It is directly +~ analogous to "interpret", and reading interpret.e may help in understanding +~ it, though it's meant to still make sense on its own. ~ -~ We resolve the issue by running our own, alternate versions of the words -~ "create", ":", ";", and ";asm" which use the label facility to compute the -~ addresses that will be needed at runtime. These alternates run instead of -~ the normal versions of these words. The code being compiled is responsible -~ for not doing anything else that would rely on "here" and "latest" matching -~ their runtime addresses, though it is otherwise allowed to modify and rely -~ on them in all the usual ways. The alternate versions are defined in this -~ file as their own words, "Lcreate", "L:", "L;", and "L;asm". +~ It expects to be called from "transform", below, which loops. ~ ~ (-- done) : transform-one @@ -141,31 +263,80 @@ allocate-transform-state s" transform-state" variable ~ If it's the magic word, end the transformation. dup s" pyrzqxgl" stringcmp 0 = { drop dropstring 1 exit } if - transform-state transform-state-saved-latest @ swap find-in + ~ Check whether it's one of the words we have alternates for, and look up + ~ the alternate if so. + dup 0 swap + ~ (name as stack string, name pointer, placeholder, name pointer) + dup s" create" stringcmp 0 = { swap drop ' Lcreate swap } if + dup s" :" stringcmp 0 = { swap drop ' L: swap } if + dup s" ;" stringcmp 0 = { swap drop ' L; swap } if + dup s" ;asm" stringcmp 0 = { swap drop ' L;asm swap } if + drop swap + ~ (name as stack string, 0 or alternate entry pointer, name pointer) - ~ Check whether the word was found in the dictionary. - dup 0 != { - ~ If the word is in the dictionary, check what mode we're in, then... - dropstring-with-result - ~ (entry pointer) - interpreter-flags @ 0x01 & { - ~ ... if we're in compile mode, there's still a chance it's an immediate - ~ word, in which case we fall through to interpret mode... - dup entry-flags@ 1 & 0 = + ~ If an alternate was found, the alternate will be used in immediate mode. + ~ If not, we look up the word in the regular, non-transformed dictionary + ~ and use that for immediate mode. + over { dup + transform-state transform-state-saved-latest @ swap find-in + 3roll drop swap } unless + ~ (name as stack string, immediate entry pointer, name pointer) - ~ ... but it's a regular word, so append it to the heap. - ~ TODO why is ; being treated as a regular word - { entry-to-execution-token , 0 exit } if - } if + ~ For compile mode, we need to look the word up in the output buffer. We + ~ can't easily traverse the next-entry pointers in the output buffer's + ~ dictionary, so we use the label. + ~ + ~ Labels point to codewords (because that's what "Lcreate" does), so we + ~ have to convert it to get the entry pointer. Since we don't know the + ~ word's name statically, this is a rare scenario where we can't use the + ~ abbreviated label syntax, but that's easy enough. + ~ + ~ We do have to be careful of one thing: On the first run, the label may + ~ be zero! + swap-transform-variables + intern-label use-label + swap-transform-variables + dup { execution-token-to-entry } if + ~ (name as stack string, immediate entry pointer, compiled entry pointer) + + ~ In regular "interpret", we would check whether we found the word before + ~ checking the mode. However, we have three different places words could + ~ come from, so that's not a simple notion. So, we check the mode first. + interpreter-flags @ 0x01 & { + ~ If we're in compile mode, there's still a chance it's an immediate + ~ word. First check whether we have an immediate entry, then if so, check + ~ that entry's flags. Notice that this means the generated code can't + ~ override an immediate word with a non-immediate word of the same name. + over dup { entry-flags@ 0x01 & not } if - ~ ... if we're in interpret mode, or the word is immediate, run it. - entry-to-execution-token execute 0 exit + ~ Either there was no immediate entry, or the immediate entry wasn't + ~ flagged as an immediate word. So we treat this as a compilation, which + ~ means we append a word to the heap. Specificaly, of course, we use the + ~ compiled entry to do that. + { swap drop dropstring-with-result + entry-to-execution-token , + 0 exit } if } if - ~ If it's not in the dictionary, check whether it's a decimal number. + ~ If we got here, one of three things is true: We're in interpret mode; + ~ the word is immediate; or no word was found. Regardless, we don't need + ~ the compiled entry pointer anymore, so drop it. drop - ~ As before, we get the stack address and use it as a string pointer. - ~ (string) + ~ (name as stack string, immediate entry pointer) + + ~ If the immediate entry pointer is non-zero, run it. + dup { + dropstring-with-result entry-to-execution-token execute + 0 exit + } if + + ~ If we're still here, it wasn't in the dictionary. Also, we don't need + ~ the immediate entry pointer, either. + drop + ~ (name as stack string) + + ~ If it's not in the dictionary, check whether it's an integer literal. As + ~ before, we get the stack address and use it as a string pointer. value@ read-integer 0 = { ~ It's a number. interpreter-flags @ 0x01 & { @@ -175,7 +346,10 @@ allocate-transform-state s" transform-state" variable ~ require dealing with what happens if it's not found. ~ TODO this is wrong dropstring-with-result - [ ' lit entry-to-execution-token literal ] + + ~ We look up "lit" as a label. + swap-transform-variables L@' lit swap-transform-variables + transform-offset , , 0 exit } if @@ -191,28 +365,10 @@ allocate-transform-state s" transform-state" variable s" No such word: " emitstring value@ emitstring dropstring 0 ; -~ ." input " main-input-buffer dup .hex64 newline dup hexdump @ dup .hex64 newline bye -~ 1024 read-to-buffer -~ foo bar baz biff -~ pyrzqxgl -~ stackhex dup hexdump emitstring bye +~ This implements the label transform for all words in a region given as an +~ input string. It is directly analogous to "quit", in interpret.e, but is far +~ more complex. ~ -~ : breakza -~ ." original" newline -~ main-input-buffer dup 6 8 * hexdump-from -~ dup push-input-buffer -~ ." updated original" newline -~ dup 6 8 * hexdump-from -~ ." copy" newline -~ dup input-buffer-next-source @ 6 8 * hexdump-from -~ newline -~ stackhex -~ dup pop-input-buffer -~ ." copied back" newline -~ 6 8 * hexdump-from -~ stackhex -~ bye ; - ~ (output point, input string pointer -- output point) : transform main-input-buffer dup push-input-buffer @@ -226,6 +382,7 @@ allocate-transform-state s" transform-state" variable ~ loop, and set it back when the loop ends. here @ transform-state transform-state-saved-here ! latest @ transform-state transform-state-saved-latest ! + dup transform-state transform-state-output-buffer-start ! here ! 0 latest ! ~ Now the stack has nothing of ours on it, so client code can do its thing. @@ -248,23 +405,10 @@ allocate-transform-state s" transform-state" variable ~ invocation, for tidiness we zero it out. 0 transform-state transform-state-saved-here ! 0 transform-state transform-state-saved-latest ! + 0 transform-state transform-state-output-buffer-start ! ~ Also put the input source back how it was. main-input-buffer pop-input-buffer exit } if } forever ; -~ 1024 allocate dup -~ ." compilation output buffer" newline dup hexdump -~ transform -~ : za ." ZA" 12 13 - . ; -~ : ' word value@ find dropstring-with-result -~ interpreter-flags @ 1 & { literal } if ; make-immediate -~ ~ ' za . newline -~ pyrzqxgl -~ ." back back back " here @ .hex64 newline -~ ~ ." stack after " stackhex -~ ~ 2dup swap hexdump-between -~ ~ : piz ." PIZ" ' za . newline ; piz -~ bye - diff --git a/vim/syntax/evocation.vim b/vim/syntax/evocation.vim index 2aeefb3..de3bd66 100644 --- a/vim/syntax/evocation.vim +++ b/vim/syntax/evocation.vim @@ -1,3 +1,5 @@ +" Don't use keywords, they don't have the correct delimiters. + syn region evocationComment start="\(^\|\s\)\zs\~\($\|\s\)" end="$" syn region evocationString start=_\(^\|\s\)\zss"\ze\($\|\s\)_ end=_"_ @@ -9,9 +11,9 @@ syn match evocationTickWord "\(^\|\s\)\zsL'\s\+\S\+\ze\($\|\s\)" syn match evocationTickWord "\(^\|\s\)\zsL@'\s\+\S\+\ze\($\|\s\)" syn match evocationTickWord "\(^\|\s\)\zsL!'\s\+\S\+\ze\($\|\s\)" -syn keyword evocationStackManipulation swap drop 2drop 3drop ndrop -syn keyword evocationStackManipulation roll unroll 3roll 3unroll -syn keyword evocationStackManipulation dup 2dup 3dup ndup over pick +syn match evocationStackManipulation "\(^\|\s\)\zs\(swap\|drop\|2drop\|3drop\|ndrop\)\ze\($\|\s\)" +syn match evocationStackManipulation "\(^\|\s\)\zs\(roll\|unroll\|\|3roll\|3unroll\)\ze\($\|\s\)" +syn match evocationStackManipulation "\(^\|\s\)\zs\(dup\|2dup\|\|3dup\|ndup\|over\|pick\)\ze\($\|\s\)" syn match evocationNumber "\(^\|\s\)\zs-\?\d\+\ze\($\|\s\)" syn match evocationNumber "\(^\|\s\)\zs-\?0b[01]\+\ze\($\|\s\)" @@ -22,7 +24,7 @@ syn match evocationOperator "\(^\|\s\)\zs[.,&|@!]\ze\($\|\s\)" syn match evocationLogic "\(^\|\s\)\zs&&\ze\($\|\s\)" syn match evocationLogic "\(^\|\s\)\zs||\ze\($\|\s\)" -syn keyword evocationLogic not +syn match evocationLogic "\(^\|\s\)\zs\(not\)\ze\($\|\s\)" syn match evocationComparison "\(^\|\s\)\zs!=\ze\($\|\s\)" syn match evocationComparison "\(^\|\s\)\zs=\ze\($\|\s\)" @@ -37,7 +39,7 @@ syn match evocationArithmetic "\(^\|\s\)\zs\*\ze\($\|\s\)" syn match evocationArithmetic "\(^\|\s\)\zs/%\ze\($\|\s\)" syn match evocationArithmetic "\(^\|\s\)\zs1+\ze\($\|\s\)" syn match evocationArithmetic "\(^\|\s\)\zs1-\ze\($\|\s\)" -syn keyword evocationArithmetic negate max min +syn match evocationArithmetic "\(^\|\s\)\zs\(negate\|max\|min\)\ze\($\|\s\)" syn match evocationBlock "\(^\|\s\)\zs[\[\]{};]\ze\($\|\s\)" syn match evocationBlock "\(^\|\s\)\zs;asm\ze\($\|\s\)" |