diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..efa6632 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +bin/* \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100755 index 0000000..e2276f3 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "main.go", + "args": ["examples/test.vqe"] + }, + ] +} \ No newline at end of file diff --git a/LICENSE b/LICENSE index 2071b23..5edc3de 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) +Copyright (c) 2022 Zongor Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 7517f7d..4faebef --- a/README.md +++ b/README.md @@ -1,3 +1,13 @@ -# varaq-interpreter-go +# var'aq programming language -An implementation of the var'aq programming language in golang \ No newline at end of file +This is an implementation of the [var'aq programming language](https://www.oocities.org/connorbd/varaq/index.html) created by Brian Connors, with some help from Chris Pressey and Mark Shoulson. + +It follows the spec for the [original `var'aq` language](https://www.oocities.org/connorbd/varaq/varaqspec.html). + +This interpreter accepts both English and Klingon commands. + +I have created an [updated spec](./SPEC.md) that describes this implementation of the language without the flavortext or addendums of the original. + +Also included are some example programs written in the Klingon version and English version of `var'aq`. + +This is a fan made project not connected with the Star Trek people (i.e. Paramount/CBS). diff --git a/SPEC.md b/SPEC.md new file mode 100755 index 0000000..a1ebe36 --- /dev/null +++ b/SPEC.md @@ -0,0 +1,628 @@ +_var'aq_ Specification +================================== + +For better flavor text please read the [Original `var'aq` spec](https://www.oocities.org/connorbd/varaq/varaqspec.html) the following is a trimmed down version of the spec with a couple of fixes and addendums to how this implementation of the language works. + +## 1 Language Overview + +_var'aq_ is a stack-based, RPN programming language with points of similarity to Lisp, Forth, and PostScript. It is more of a functional language than an imperative (Algol-like) language; most operations are dependent on the stack and bypass the local variable store altogether. + +### 1.2 Filetypes + +* `.vq -- application/varaq` -- a standard _var'aq_ source file +* `.vqe -- application/varaq-engl` -- an English-keyword _var'aq_ source file + +## 2 Language Basics + +This section describes the fundamental _var'aq_ language constructs and data types. + +### 2.1 Stack Operations + +These operations directly manipulate the _var'aq_ operand stack. The operand stack can hold any of four kinds of data: numbers (real or integer), strings, functions, or arrays. It is best described as "translucent", similar to the transparent stack of Forth or PostScript but somewhat more restricted. The internal data representation of the stack is not available to the programmer. + +#### 2.1.1 pop/woD + +_obj `woD` -_ + +Pops and discards the top item on the stack. The literal meaning is _discard_. + +Errors: stackUnderflow + +#### 2.1.2 dup/latlh + +_obj `latlh` obj obj_ + +Duplicates the top object on the stack. + +Errors: stackUnderflow + +#### 2.1.3 exch/tam + +_obj1 obj2 `tam` obj2 obj1_ + +Inverts the order of the top two objects on the stack. + +Errors: stackUnderflow + +#### 2.1.4 clear/chImmoH + +_... obj `chIm` -_ + +Empties the stack. + +Errors: none + +#### 2.1.5 remember/qaw + +_\- `qaw` flag_ + +Puts a flag (like PostScript's `mark`) on the stack. The internal representation of the flag is not available to the programmer. + +Errors: none + +#### 2.1.6 forget/qawHa' + +_... flag ... `qawHa'` ..._ + +Clears the stack down to the flag and pops the flag. If there is no flag present, the stack is emptied completely. + +Errors: none + +#### 2.1.7 dump/Hotlh (lit. scan) + +_... `Hotlh` ..._ + +Prints the contents of the operand stack to STDOUT without changing them. _Note_: the _Hotlh_ operator is a debugging operator and is not intended for use in programs; it is merely documented here because it might be useful to a _var'aq_ developer. In particular, the output format of this operator is implementation-defined and will not be specified in this document. _Hotlh_ may be redefined to take such arguments as the implementor feels appropriate. + +Errors: implementation-defined. + +#### 2.1.8 disinter + +Returns the value just above the top mark on the stack without disturbing the stack above it. + +### 2.2 Data/Code Operations + +_var'aq_, like many similar languages, does not distinguish between code and data. These operations include operators to associate names with objects and executable procedures, as well as operators to define and manage data structures. Note that variables and procedures live in a common namespace, since the act of pushing the content of a variable is essentially the same as executing the variable's name. + +#### 2.2.1 ~ (quote/lI'moH) + +_\- `~` obj obj_ + +The ~ operator is a special form, as it is not a postfix operator. When the interpreter encounters a ~, it pushes the next token on the stack as is regardless of whether it is a defined name. (Attempting to push an undefined name without a ~ will generate an undefinedName error.) + +The literal meaning of this operator's name is "make useful". Errors: none + +#### 2.2.2 { + +Begins the creation of an anonymous procedure. The process is implementation-dependent. + +#### 2.2.3 } + +_\- `}` proc_ + +Completes procedure construction and pushes a reference to the completed procedure on the stack. Does not execute the procedure. + +Errors: noDefinedProc + +#### 2.2.4 name/pong + +_obj id `pong` -_ + +Associates _obj_ with _id_ and places it in the system lookup space. Conventionally used to associate new operator names with procedure objects. + +Example: _~ add3 { boq boq cha' } pong_ + +Pushes the name _add3_ and a procedure object on the stack, then binds the name to the procedure. + +Errors: stackUnderflow, noDefinedProc + +#### 2.2.5 set/cher + +_obj id `cher` -_ + +Reassigns the value of a value already in the system lookup space. Used primarily for variable assignments. + +Errors: stackUnderflow, noSuchName + +#### 2.2.6 (\* ... \*) (comment) + +Marks a comment in a program. All such comments are treated as single tokens and ignored. + +#### 2.2.7 //_name_ + +Causes the interpreter to import a file with the name _name_.vq(e) and execute it as if it is part of the currently executing program. Essentially equivalent to `#include` in C. + +An example can be found at the top of the [test.vqe](./examples/test.vqe) example. + +`Note:` you must use either absolute paths or paths relitive to the location of the interpeter + +### 2.3 Control Flow + +_var'aq_ supports a small but sufficient supply of conditional and iterative operators. + +#### 2.3.1 ifyes/HIja'chugh + +_bool proc `HIja'chugh` -_ + +Pops the proc object off the stack, then evaluates the boolean. If it's true, the proc object is evaluated; otherwise, it's thrown out. + +Errors: stackUnderflow, noDefinedProc + +#### 2.3.2 ifno/ghobe'chugh + +_bool proc `ghobe'chugh` -_ + +Similar to _HIja'chugh_ above, but executes proc only if bool is false. + +Errors: stackUnderFlow, noDefinedProc + +#### 2.3.3 choose/wIv + +_bool `wIv` bool bool_ + +Duplicates a boolean value on top of the stack. Allows paired HI'ja'chugh/ghobe'chugh clauses. + +`Note:` To the untrained eye, it may seem as though wIv and latlh are identical. This is true in the reference implementation, but may not be in any version that actually does some level of type checking. This bit of syntactic sugar should never be relied upon; always use wIv in this situation. + +#### 2.3.4 eval/chov + +_proc `chov` -_ + +Pops a proc object off the stack and executes it. + +Errors: stackUnderflow, noDefinedProc + +#### 2.3.5 escape/nargh + +_bool `nargh` -_ + +Exit the current procedure. Useful for exit conditions on loops. Will terminate the current session if used top-level. + +#### 2.3.6 repeat/vangqa' + +_val proc `vangqa'` -_ + +Pops val and proc off the stack and executes proc val times. + +### 2.4 List Operations + +_var'aq_ supports a series of operators for management of lists (_ghomHom_, which seems to mean something like "cluster"). These primitives are the language's primary way of managing aggregate objects and work much like similar operators in LISP; a more sophisticated paradigm, such as OO extensions or the like, can be built with these operators. + +Note that "objects" as they stand in _var'aq_ are largely singletons as in JavaScript; there is no inherent concept of object-orientation or anything like it in standard _var'aq_. + +#### 2.4.1 ( + +Begins a list definition. + +#### 2.4.2 ) + +_( item1 item2 ... `)` list_ + +Creates a list and pushes it onto the stack. + +#### 2.4.3 split/SIj + +_list `SIj` item1 list_ + +Pops a list off the stack and returns the first item and the rest of the list. + +#### 2.4.4 cons/muv + +_list item1 ... `muv` list_ + +Takes an object and adds it to the head of a list. Equivalent to the LISP `cons` operator. + +#### 2.4.5 shatter/ghorqu' + +_list `ghorqu'` item1 item2 ..._ + +Reduces a list to its component elements and pushes them on the stack in order. + +`Note:` The precise meaning of the construction _ghorqu'_ is a bit obscure; the rendering _shatter_ is idiomatic and may derive from a nonstandard dialect. Standard Klingon would generally prefer _jor_, meaning _explode_.) + +#### 2.4.6 empty?/chIm'a' + +_list `chIm'a'` bool_ + +Examines a list on the stack and returns 1 if its value is null (_pagh_), a 0 if it contains anything. + +#### 2.4.7 consume + +_obj1 obj2 ... mark `consume` list_ + +Pops all objects on the stack down to _mark_ and returns them in a list. + +`Note:` some implementations also have an operator known as bite/_chop_, equivalent to the Lisp _cdr_. This is not required in any standard _var'aq_ implementation and can easily be rendered by the function + +``` +~ chop { SIj woD } pong +``` + + +### 2.5 String Operators +_tlheghjangwI'mey_ + +#### 2.5.1 strtie/tlheghrar + +_str1 str2 `tlheghrar` str3_ + +Concatenates the top two strings on the stack into one. + +#### 2.5.2 compose/naQmoH + +_mark str1 str2 ... strn `naQmoH` strn'_ + +Pops objects (executing proc objects if necessary) off the stack until a marker (placed by `qaw`) is hit and combines them into one string. + +#### 2.5.3 streq?/tlheghrap'a' + +_str1 str2 `tlheghrap'a'` bool_ + +Pops the top two strings on the stack, compares them, and returns 1 if identical, 0 if not. + +#### 2.5.4 strcut/tlheghpe' + +_str startval endval `tlheghpe'` substr_ + +Pops two values and a string and returns the section of the string between character _startval_ and character _endval_. + +#### 2.5.5 strmeasure/tlheghjuv + +_str `tlheghjuv` val_ + +Pops a string off the stack and returns its length in characters. + +#### 2.5.6 explode/jor + +_str `jor` list_ + +Separates individual "words" in a string by whitespace. + +## 3 Mathematical Operators +_mI'jangwI'mey_ + +### 3.1 Arithmetic Operations +_toghwI'mey_ + +Arithmetic operators usually work with real numbers unless otherwise stated. The number operators (sec 3.3) can convert them to integers if necessary. + +#### 3.1.1 add/boq + +_a b `boq` sum_ + +Pops the top two values on the stack and replaces them with their sum. + +#### 3.1.2 sub/boqHa' + +_a b `boqHa'` difference_ + +Pops the top two values on the stack and replaces them with a - b. + +#### 3.1.3 mul/boq'egh + +_a b `boq'egh` product_ + +Pops the top two values on the stack and replaces them with their product. + +#### 3.1.4 div/boqHa''egh + +_a b `wav` quotient_ + +Pops the top two values on the stack and replaces them with a/b. + +#### 3.1.5 idiv/HabboqHa''egh (lit. full-divide) + +_a b `HabboqHa''egh` quotient_ + +Pops the top two values on the stack and replaces them with the results of an integer division operation. + +#### 3.1.6 mod/chuv (lit. leftover) + +_a b `chuv` remainder_ + +Pops the top two values and returns the remainder of a mod b. + +#### 3.1.7 pow/boqHa'qa' (lit. remultiply) + +_base exp `boqHa'qa'` real_ + +Pops the top two values and returns base^exp. + +#### 3.1.8 sqrt/loS'ar (lit. fourth-howmuch) + +_angle `loS'ar` real_ + +Returns the square root of val. + +#### 3.1.9 add1/wa'boq + +_a `wa'boq` a+1_ + +Increments the top value on the stack by one. + +#### 3.1.10 sub1/wa'boqHa' + +_a `wa'boqHa'` a-1_ + +Decrements the top value on the stack by one. + +### 3.2 Trigonometric and Logarithmic Operators +_SIHpojjangwI'mey 'ej ghurjangwI'mey_ + +#### 3.2.1 sin/yu'egh (lit. wave) + +_angle `yu'egh` real_ + +Returns the sine of val. + +#### 3.2.2 cos/yu'eghHa' (lit. counter-wave) + +_angle `yu'eghHa'` cos(val)_ + +Returns the cosine of val. + +#### 3.2.3 tan/qojmI' (lit. cliffnumber) + +_angle `qojmI'` tan(val)_ + +Returns the tangent of val. + +#### 3.2.4 atan/qojHa' (lit. anticliff) + +_num den `qojHa'` angle_ + +Returns the arctangent of _num / den_. + +#### 3.2.5 ln/ghurtaH + +_num `ghurtaH` real_ + +Returns the natural log of _num_. + +#### 3.2.6 log/maHghurtaH + +_num `maHghurtaH` real_ + +Returns the base 10 log of _num_. + +#### 3.2.7 log3/wejghurtaH + +_num `wejghurtaH` real_ + +Returns the base 3 log of _num_. + +### 3.3 Numerical Operators and Constants + +This section describes operators that operate on numbers themselves, as well as common system-level constants. + +#### 3.3.1 clip/poD + +_real `poD` int_ + +Removes the fractional portion of a real number (equivalent to floor(real). + +#### 3.3.2 smooth/Hab (lit. smooth) + +_real `Hab` int_ + +Rounds a number to the nearest integer. + +#### 3.3.3 howmuch/'ar + +_num `'ar` num2_ + +Returns the absolute value of _num_. + +#### 3.3.4 setrand/mIScher + +_num `mIScher` -_ + +Sets the random number generator seed value to _num_. Not common, since most _var'aq_ implementations have a rather arcane formula for picking a pseudo-random seed value. + +#### 3.3.5 rand/mIS + +_num `mIS` real_ + +Returns a random real number in the range 0 to _num_. If there is no meaningful input on the stack, + +#### 3.3.6 pi/HeHmI' + +Pushes the value pi (~3.14159...) onto the stack. The Klingon name literally means "edge-number". + +#### 3.3.7 e/ghurmI' + +Pushes the value e onto the stack. The Klingon name literally means "growth-number". + +#### 3.3.8 int?/HabmI''a' + +_val `HabmI''a'` bool_ Pops the top value on the stack and returns 1 if it is an integer, 0 if not. + +#### 3.3.9 number?/mI''a' + +_val `mI''a'` bool_ + +Pops the top value off the stack and returns 1 if it's a number, 0 if it's something else. + +#### 3.3.10 numberize/mI'moH + +_str `mi'moH` val_ + +Pops a string off the stack, converts it into a numerical value, and returns it. + +#### 3.4 Bitwise operators + +Though _var'aq_ makes no clear distinction between integers and reals, it is nevertheless useful to be able to manipulate a number on the bit level. The following operators assume that their operands will always be treated as integers; effects on floating-point values are undefined, and may be disallowed at the implementor's discretion. + +#### 3.4.1 isolate/mobmoH + +_a b `mobmoH` result_ + +Performs a bitwise AND on a and b. + +#### 3.4.2 mix/DuD + +_a b `DuD` result_ + +Performs a bitwise OR on a and b. + +#### 3.4.3 contradict/tlhoch + +_a b `tlhoch` result_ + +Performs a bitwise XOR on a and b. + +#### 3.4.4 compl/Qo'moH + +_val `Qo'moH` ~val_ + +Returns the one's-complement of val. `Note:` The literal meaning is something like "make it say no". + +#### 3.4.5 shiftright/nIHghoS + +_a b `nIHghoS` result_ + +Shifts a right b places, preserving the sign of the value. + +#### 3.4.6 shiftleft/poSghoS + +_a b `poSghoS` result_ + +Shifts a left b places. + +### 4 Relational and Logical Operators + +#### 4.1 Relational Operators and Predicate Functions +_yu'jangwI'mey_ + +#### 4.1.1 gt?/law''a' + +_a b `law''a'` bool_ + +Pops a and b off the stack, compares them, and returns a boolean value of true if a is larger. + +#### 4.1.2 lt?/puS'a' + +_a b `puS'a'` bool_ + +Pops a and b off the stack, compares them, and returns a boolean value of true if a is smaller. + +#### 4.1.3 eq?/rap'a' + +_a b `rap'a'` bool_ + +Pops a and b off the stack, compares them, and returns a boolean value of true if a is the same as b. + +#### 4.1.4 ge?/law'rap'a' + +_a b `law'rap'a'` bool_ + +Pops a and b off the stack, compares them, and returns a boolean value of true if a is greater than or equal to b. + +#### 4.1.5 le?/puSrap'a' + +_a b `puSrap'a'` bool_ + +Pops a and b off the stack, compares them, and returns a boolean value of true if a is less than or equal to b. + +#### 4.1.6 ne?/rapbe'a' + +_a b `rapbe'a'` bool_ + +Pops a and b off the stack, compares them, and returns a boolean value of true if a is not equal to b. + +#### 4.1.7 null?/pagh'a' + +_obj `pagh'a'` bool_ + +Examines the top object on the stack and returns a 1 if null, a 0 if not. + +#### 4.1.8 negative?/taH'a' + +_val `taH'a'` bool_ + +Pops the top number on the stack and returns a 1 if less than 0, a 0 if not. + +#### 4.2 Logical Operators +_vItjangwI'mey_ + +Note that these are strictly logical operators, not bitwise. + +#### 4.2.1 and/je + +_a b `je` bool_ + +Evaluates b and a and returns a 1 if both are true, a 0 if not. + +#### 4.2.2 or/joq + +_a b `joq` bool_ + +Evaluates b and a and returns a 1 if one or both are true, a 0 if both are false. + +#### 4.2.3 xor/ghap + +_a b `ghap` bool_ + +Evaluates b and a and returns a 1 if only one is true, a 0 otherwise. + +#### 4.2.4 not/ghobe' + +_val `ghobe'` bool_ + +Evaluates val and returns 1 if true, 0 if not. + +### 5 Input/Output and File Operators + +The _var'aq_ Level 0 specification essentially handles console I/O and files in a manner very similar to the UNIX model. + +#### 5.1 Console I/O + +The console I/O model at this point is very simple: write, read, error. + +#### 5.1.1 disp/cha' + +_obj `cha'` -_ + +Pops the top object on the stack and writes it to STDOUT. Note that certain types of objects will generate meaningless output, particularly anonymous proc objects. + +#### 5.1.2 listen/'Ij + +_\- `'Ij` str_ + +Reads one line of input and stores it as a string on top of the stack. + +#### 5.1.3 complain/bep + +_str `bep` -_ + +Pops _str_ and prints it to stderr. + +### 6 System Variables +_patSarwI'mey_ + +This section describes _var'aq_ keywords that do no more than put set values on the stack. Many of them are not precisely constants but more like environment variables. + +#### 6.1 I/O-related Constants + +#### 6.1.1 newline/chu'DonwI' + +Prints a carriage return. + +#### 6.1.2 tab/chu'tut + +Advances by one tab stop. + +#### 6.2 Environment Constants + +#### 6.2.1 whereami/nuqDaq\_jIH + +Returns the IP address of the machine the interpreter is running on. + +#### 6.2.2 version/pongmI' + +Returns the current interpreter version number. The reference interpreter returns a date stamp. + +#### 6.2.3 argv/taghDe' + +Pushes the command line arguments on the stack as a list. + diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..6f32444 --- /dev/null +++ b/build.sh @@ -0,0 +1,37 @@ +GOOS=plan9 GOARCH=386 go build -o bin/varaq-plan9-386 main.go +GOOS=plan9 GOARCH=amd64 go build -o bin/varaq-plan9-amd64 main.go +GOOS=plan9 GOARCH=arm go build -o bin/varaq-plan9-arm main.go +GOOS=aix GOARCH=ppc64 go build -o bin/varaq-aix-ppc64 main.go +GOOS=darwin GOARCH=amd64 go build -o bin/varaq-darwin-amd64 main.go +GOOS=darwin GOARCH=arm64 go build -o bin/varaq-darwin-arm64 main.go +GOOS=dragonfly GOARCH=amd64 go build -o bin/varaq-dragonfly-amd64 main.go +GOOS=freebsd GOARCH=386 go build -o bin/varaq-freebsd-386 main.go +GOOS=freebsd GOARCH=amd64 go build -o bin/varaq-freebsd-amd64 main.go +GOOS=freebsd GOARCH=arm go build -o bin/varaq-freebsd-arm main.go +GOOS=illumos GOARCH=amd64 go build -o bin/varaq-illumos-amd64 main.go +GOOS=js GOARCH=wasm go build -o bin/varaq.wasm main.go +GOOS=linux GOARCH=386 go build -o bin/varaq-linux-386 main.go +GOOS=linux GOARCH=amd64 go build -o bin/varaq-linux-amd64 main.go +GOOS=linux GOARCH=arm go build -o bin/varaq-linux-arm main.go +GOOS=linux GOARCH=arm64 go build -o bin/varaq-linux-arm64 main.go +GOOS=linux GOARCH=loong64 go build -o bin/varaq-linux-loong64 main.go +GOOS=linux GOARCH=mips go build -o bin/varaq-linux-mips main.go +GOOS=linux GOARCH=mipsle go build -o bin/varaq-linux-mipsle main.go +GOOS=linux GOARCH=mips64 go build -o bin/varaq-linux-mips64 main.go +GOOS=linux GOARCH=mips64le go build -o bin/varaq-linux-mips64le main.go +GOOS=linux GOARCH=ppc64 go build -o bin/varaq-linux-ppc64 main.go +GOOS=linux GOARCH=ppc64le go build -o bin/varaq-linux-ppc64le main.go +GOOS=linux GOARCH=riscv64 go build -o bin/varaq-linux-riscv64 main.go +GOOS=linux GOARCH=s390x go build -o bin/varaq-linux-s390x main.go +GOOS=netbsd GOARCH=386 go build -o bin/varaq-netbsd-386 main.go +GOOS=netbsd GOARCH=amd64 go build -o bin/varaq-netbsd-amd64 main.go +GOOS=netbsd GOARCH=arm go build -o bin/varaq-netbsd-arm main.go +GOOS=openbsd GOARCH=386 go build -o bin/varaq-openbsd-386 main.go +GOOS=openbsd GOARCH=amd64 go build -o bin/varaq-openbsd-amd64 main.go +GOOS=openbsd GOARCH=arm go build -o bin/varaq-openbsd-arm main.go +GOOS=openbsd GOARCH=arm64 go build -o bin/varaq-openbsd-arm64 main.go +GOOS=solaris GOARCH=amd64 go build -o bin/varaq-solaris-amd64 main.go +GOOS=windows GOARCH=386 go build -o bin/varaq-windows-386 main.go +GOOS=windows GOARCH=amd64 go build -o bin/varaq-windows-amd64 main.go +GOOS=windows GOARCH=arm go build -o bin/varaq-windows-arm main.go +GOOS=windows GOARCH=arm64 go build -o bin/varaq-windows-arm64 main.go diff --git a/examples/add.vqe b/examples/add.vqe new file mode 100755 index 0000000..86c9c8c --- /dev/null +++ b/examples/add.vqe @@ -0,0 +1 @@ +1 2 3 add disp diff --git a/examples/deadfish.vq b/examples/deadfish.vq new file mode 100755 index 0000000..1d4d43c --- /dev/null +++ b/examples/deadfish.vq @@ -0,0 +1,26 @@ +(* QopghotI' mughwI' var'aq *) + +0 ~ toD cher + +~ QopghotI'mughwI' { + + ">> " cha' + + 'Ij ~ Doch cher + + Doch 0 1 tlheghpe' ~ Doch cher + + Doch "i" tlheghrap'a' { toD 1 boq ~ toD cher } HIja'chugh + Doch "d" tlheghrap'a' { toD 1 boqHa' ~ toD cher } HIja'chugh + Doch "o" tlheghrap'a' { toD cha' chu'DonwI'} HIja'chugh + Doch "s" tlheghrap'a' { toD toD boq'egh ~ toD cher } HIja'chugh + + toD -1 rap'a' { 0 ~ toD cher } HIja'chugh + + toD 256 rap'a' { 0 ~ toD cher } HIja'chugh + + QopghotI'mughwI' + +} pong + +QopghotI'mughwI' diff --git a/examples/hello_world.vq b/examples/hello_world.vq new file mode 100755 index 0000000..20340a1 --- /dev/null +++ b/examples/hello_world.vq @@ -0,0 +1 @@ +"nuqneH 'u'?" cha' \ No newline at end of file diff --git a/examples/setting.vqe b/examples/setting.vqe new file mode 100755 index 0000000..541aa74 --- /dev/null +++ b/examples/setting.vqe @@ -0,0 +1,4 @@ +"test" ~ breakfast set +"coffee" ~ beverage set +"croissants with " beverage strtie ~ breakfast set +breakfast disp diff --git a/examples/test.vqe b/examples/test.vqe new file mode 100755 index 0000000..072bce9 --- /dev/null +++ b/examples/test.vqe @@ -0,0 +1,31 @@ +(* this is a comment *) + +// examples/setting.vqe + +5 ~ count set + +~ test { + 0 ~ num set + time + + 3.1415962 e sub pi dup mul le? + exch + disp + disp + + "Enter name:" disp + "hello" " " strtie + listen strtie + disp + + (1 2 3 4 "test this" pi(*test*)) + + num 1 add ~ num set + num disp +} name + +test +whereami +version +time +disp \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100755 index 0000000..b0ecaac --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module varaq + +go 1.18 \ No newline at end of file diff --git a/main.go b/main.go new file mode 100755 index 0000000..52f6d11 --- /dev/null +++ b/main.go @@ -0,0 +1,64 @@ +package main + +import ( + "bufio" + "fmt" + "io/ioutil" + "os" + "varaq/varaq" +) + +func main() { + argsCount := len(os.Args[1:]) + if argsCount >= 1 { + runFile(os.Args[1]) + } else { + repl() + } + os.Exit(0) +} + +func repl() { + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + fmt.Print("> ") + line := scanner.Text() + if len(line) == 0 { + return + } + err := interpret(line) + if err != nil { + fmt.Printf("%v\n", err) + } + } +} + +func runFile(path string) { + code, err := ioutil.ReadFile(path) // the file is inside the local directory + if err != nil { + fmt.Printf("%v\n", err) + os.Exit(65) + } + + err = interpret(string(code)) + if err != nil { + fmt.Printf("%v\n", err) + os.Exit(65) + } +} + +func interpret(src string) error { + tokens, err := varaq.Tokenize(src) + if err != nil { + return fmt.Errorf("%v", err) + } + + code := varaq.Parse(tokens) + err = varaq.Interpret(code, os.Args[1:]) + if err != nil { + fmt.Printf("%v\n", err) + return err + } + + return nil +} diff --git a/varaq/interpreter.go b/varaq/interpreter.go new file mode 100755 index 0000000..b4b3239 --- /dev/null +++ b/varaq/interpreter.go @@ -0,0 +1,1225 @@ +package varaq + +import ( + "fmt" + "math" + "math/rand" + "net" + "os" + "strconv" + "strings" + "time" +) + +var globals = map[string]Expr{} +var stack = make([]Expr, 0) + +func get(k string) Expr { + return globals[k] +} + +func put(k string, v Expr) { + globals[k] = v +} + +func push(o Expr) { + stack = append(stack, o) +} + +func top() (Expr, error) { + n := len(stack) - 1 + if n < 0 { + return Expr{}, fmt.Errorf("stack underflow") + } + return stack[n], nil +} + +func pop() (Expr, error) { + res, e := top() + if e != nil { + return Expr{}, e + } + n := len(stack) - 1 + stack = stack[:n] + return res, nil +} + +func identifierToObj(c Expr, idx int, argv []string) error { + k, k_ok := c.Value.(string) + if k_ok { + o := get(k) + if o.Tok == UNDEFINED { + return fmt.Errorf("identifier %s undefined @%v", k, idx) + } else if o.Tok == FUNCTION { + e := Interpret(o, argv) + if e != nil { + if e == fmt.Errorf("escape") { + return nil + } + return e + } + } else { + push(o) + } + } else { + return fmt.Errorf("idenifier not string @%v", idx) + } + + return nil +} + +func (c Expr) idToExpr(idx int, argv []string) (Expr, error) { + e := identifierToObj(c, idx, argv) + if e != nil { + return Expr{UNDEFINED, nil, nil, 0}, e + } + + vv, e2 := pop() + if e2 != nil { + return Expr{UNDEFINED, nil, nil, 0}, e2 + } + + return vv, nil +} + +func (c Expr) idToS(idx int, argv []string) (string, error) { + v, e := c.idToExpr(idx, argv) + if e != nil { + return "", e + } + + return fmt.Sprintf("%b", v.Value), nil +} + +func popArgs(idx int, argv []string) (Expr, Expr, error) { + b, e := pop() + if e != nil { + return Expr{UNDEFINED, nil, nil, 0}, Expr{UNDEFINED, nil, nil, 0}, e + } + if b.Tok == IDENTIFIER { + b, e = b.idToExpr(idx, argv) + if e != nil { + return Expr{UNDEFINED, nil, nil, 0}, Expr{UNDEFINED, nil, nil, 0}, e + } + } + a, e := pop() + if e != nil { + return Expr{UNDEFINED, nil, nil, 0}, Expr{UNDEFINED, nil, nil, 0}, e + } + if a.Tok == IDENTIFIER { + a, e = a.idToExpr(idx, argv) + if e != nil { + return Expr{UNDEFINED, nil, nil, 0}, Expr{UNDEFINED, nil, nil, 0}, e + } + } + + return a, b, nil +} + +func Interpret(code Expr, argv []string) error { + for idx, c := range code.Exprs { + switch c.Tok { + case NUMBER: + push(c) + case STRING: + push(c) + case LIST: + push(c) + case FUNCTION: + push(c) + case POP: + _, e := pop() + if e != nil { + return e + } + case DUP: + obj, e := pop() + if e != nil { + return e + } + push(obj) + push(obj) + case EXCH: + obj1, e1 := pop() + if e1 != nil { + return e1 + } + obj2, e2 := pop() + if e2 != nil { + return e2 + } + push(obj1) + push(obj2) + case CLEAR: + stack = nil + stack = make([]Expr, 0) + case IDENTIFIER: + if idx-1 >= 0 && code.Exprs[idx-1].Tok == TILDE { + push(c) + } else { + e := identifierToObj(c, idx, argv) + if e != nil { + return e + } + } + case REMEMBER: + push(Expr{REMEMBER, nil, "REMEMBER", 0}) + case FORGET: + v, e := pop() + if e != nil { + return e + } + for { + if v.Tok == REMEMBER || len(stack) == 0 { + break + } + v, e = pop() + if e != nil { + return e + } + } + case DUMP: + for n := len(stack); n > 0; n-- { + fmt.Println(stack[n]) + } + case NAME: + fun, e := pop() + if e != nil { + return e + } + name, en := pop() + if en != nil { + return en + } + namev, n_ok := name.Value.(string) + if fun.Tok == FUNCTION && name.Tok == IDENTIFIER && n_ok { + put(namev, fun) + } else { + return fmt.Errorf("cannot bind name @%v", idx) + } + case SET: + name, en := pop() + if en != nil { + return en + } + o, e := pop() + if e != nil { + return e + } + n, n_ok := name.Value.(string) + if (name.Tok == IDENTIFIER || name.Tok == STRING) && n_ok { + put(n, o) + } else { + return fmt.Errorf("cannot set, name is not a string @%v", idx) + } + case IFYES: + fun, ef := pop() + if ef != nil { + return ef + } + logical, el := pop() + if el != nil { + return el + } + if fun.Tok == FUNCTION && logical.Tok == BOOLEAN { + if logical.Value.(bool) { + Interpret(fun, argv) + } + } else { + return fmt.Errorf("cannot `ifyes` not a function or bool @%v", idx) + } + case IFNO: + fun, ef := pop() + if ef != nil { + return ef + } + logical, el := pop() + if el != nil { + return el + } + if fun.Tok == FUNCTION && logical.Tok == BOOLEAN { + if !logical.Value.(bool) { + Interpret(fun, argv) + } + } else { + return fmt.Errorf("cannot `ifno` not a function or bool @%v", idx) + } + case CHOOSE: + b, e := pop() + if e != nil { + return e + } + if b.Tok == BOOLEAN { + push(b) + push(b) + } else { + return fmt.Errorf("cannot `choose` not a bool @%v", idx) + } + case EVAL: + fun, e := pop() + if e != nil { + return e + } + if fun.Tok == FUNCTION { + Interpret(fun, argv) + } else { + return fmt.Errorf("cannot `eval` not a function @%v", idx) + } + case ESCAPE: + return fmt.Errorf("escape") + case REPEAT: + fun, e := pop() + if e != nil { + return e + } + count, ec := pop() + if ec != nil { + return ec + } + if fun.Tok == FUNCTION && count.Tok == NUMBER { + for n := int64(0); n < int64(count.Value.(float64)); n++ { + err := Interpret(fun, argv) + if err != nil { + return err + } + } + } else { + return fmt.Errorf("cannot `repeat` not a function or int @%v", idx) + } + case SPLIT: + list, e := pop() + if e != nil { + return e + } + if list.Tok == LIST { + n := len(list.Exprs) - 1 + push(list.Exprs[n]) + list.Exprs = list.Exprs[:n] + push(list) + } else { + return fmt.Errorf("cannot `split` not a list @%v", idx) + } + case CONS: + list, e := pop() + if e != nil { + return e + } + o, eo := pop() + if eo != nil { + return eo + } + if list.Tok == LIST { + list.Exprs = append(list.Exprs, o) + push(list) + } else { + return fmt.Errorf("cannot `cons` not a list @%v", idx) + } + case SHATTER: + list, e := pop() + if e != nil { + return e + } + if list.Tok == LIST { + for n := 0; n < list.Count; n++ { + push(list.Exprs[n]) + } + } else { + return fmt.Errorf("cannot `shatter` not a list @%v", idx) + } + case EMPTY: + list, e := pop() + if e != nil { + return e + } + if list.Tok == LIST { + push(Expr{BOOLEAN, nil, list.Count == 0, 0}) + } else { + return fmt.Errorf("cannot `shatter` not a list @%v", idx) + } + case COMPOSE: + v, e := pop() + if e != nil { + return e + } + var sb strings.Builder + for { + if v.Tok == REMEMBER || len(stack) == 0 { + break + } + v, e := pop() + if e != nil { + return e + } + if v.Tok == STRING { + sb.WriteString(v.Value.(string)) + } else { + return fmt.Errorf("cannot `compose` value is not a string @%v", idx) + } + } + push(Expr{STRING, nil, sb.String(), 0}) + case STREQ: + v1, e := pop() + if e != nil { + return e + } + v2, e2 := pop() + if e2 != nil { + return e2 + } + if v1.Tok == STRING && v2.Tok == STRING { + push(Expr{BOOLEAN, nil, v1.Value.(string) == v2.Value.(string), 0}) + } else { + return fmt.Errorf("cannot `streq?` value is not a string @%v", idx) + } + case STRTIE: + var e error + v2, e := pop() + if e != nil { + return e + } + v1, e := pop() + if e != nil { + return e + } + + var v1_s string + var v2_s string + + if v1.Tok == IDENTIFIER { + v1_s, e = v1.idToS(idx, argv) + if e != nil { + return e + } + } else if v1.Tok == STRING { + v1_s = v1.Value.(string) + } else { + var d float64 + err := false + switch v := v1.Value.(type) { + case float64: + d = float64(v) + default: + err = true + } + + if err { + v1_s = fmt.Sprintf("%v", v1.Value) + } else if d == float64(int64(d)) { + v1_s = fmt.Sprintf("%d", int64(v1.Value.(float64))) + } else { + v1_s = fmt.Sprintf("%f", v1.Value.(float64)) + } + } + + if v2.Tok == IDENTIFIER { + v2_s, e = v2.idToS(idx, argv) + if e != nil { + return e + } + } else if v2.Tok == STRING { + v2_s = v2.Value.(string) + } else { + var d float64 + err := false + switch v := v2.Value.(type) { + case float64: + d = float64(v) + default: + err = true + } + + if err { + v2_s = fmt.Sprintf("%v", v2.Value) + } else if d == float64(int64(d)) { + v2_s = fmt.Sprintf("%d", int64(v2.Value.(float64))) + } else { + v2_s = fmt.Sprintf("%f", v2.Value.(float64)) + } + } + + push(Expr{STRING, nil, v1_s + v2_s, 0}) + + case STRCUT: + endval, e := pop() + if e != nil { + return e + } + startval, e := pop() + if e != nil { + return e + } + str, e := pop() + if e != nil { + return e + } + + if str.Tok == STRING && startval.Tok == NUMBER && endval.Tok == NUMBER { + push(Expr{STRING, nil, str.Value.(string)[int64(startval.Value.(float64)):int64(endval.Value.(float64))], 0}) + } else { + return fmt.Errorf("cannot `strcut` value is not a string or start/end val is not an int @%v", idx) + } + case STRMEASURE: + v, e := pop() + if e != nil { + return e + } + if v.Tok == STRING { + push(Expr{NUMBER, nil, len(v.Value.(string)), 0}) + } else { + return fmt.Errorf("cannot `strmeasure` value is not a string @%v", idx) + } + case EXPLODE: + str, e := pop() + if e != nil { + return e + } + if str.Tok == STRING { + words := strings.Fields(str.Value.(string)) + for _, s := range words { + push(Expr{STRING, nil, s, 0}) + } + } + case ADD: + a, b, e := popArgs(idx, argv) + if e != nil { + return e + } + + if a.Tok == NUMBER && b.Tok == NUMBER { + push(Expr{NUMBER, nil, a.Value.(float64) + (b.Value.(float64)), 0}) + } else { + return fmt.Errorf("cannot `add` values are not numbers @%v", idx) + } + case SUB: + a, b, e := popArgs(idx, argv) + if e != nil { + return e + } + + if a.Tok == NUMBER && b.Tok == NUMBER { + push(Expr{NUMBER, nil, a.Value.(float64) - (b.Value.(float64)), 0}) + } else { + return fmt.Errorf("cannot `sub` values are not numbers @%v", idx) + } + case MUL: + a, b, e := popArgs(idx, argv) + if e != nil { + return e + } + + if a.Tok == NUMBER && b.Tok == NUMBER { + push(Expr{NUMBER, nil, a.Value.(float64) * (b.Value.(float64)), 0}) + } else { + return fmt.Errorf("cannot `mul` values are not numbers @%v", idx) + } + case DIV: + a, b, e := popArgs(idx, argv) + if e != nil { + return e + } + + if a.Tok == NUMBER && b.Tok == NUMBER { + push(Expr{NUMBER, nil, a.Value.(float64) / (b.Value.(float64)), 0}) + } else { + return fmt.Errorf("cannot `div` values are not numbers @%v", idx) + } + case IDIV: + a, b, e := popArgs(idx, argv) + if e != nil { + return e + } + + if a.Tok == NUMBER && b.Tok == NUMBER { + push(Expr{NUMBER, nil, math.Round(a.Value.(float64)) / math.Round(b.Value.(float64)), 0}) + } else { + return fmt.Errorf("cannot `idiv` values are not numbers @%v", idx) + } + case MOD: + a, b, e := popArgs(idx, argv) + if e != nil { + return e + } + + if a.Tok == NUMBER && b.Tok == NUMBER { + push(Expr{NUMBER, nil, float64(int64(a.Value.(float64)) % int64(b.Value.(float64))), 0}) + } else { + return fmt.Errorf("cannot `mod` values are not numbers @%v", idx) + } + case POW: + a, b, e := popArgs(idx, argv) + if e != nil { + return e + } + + if a.Tok == NUMBER && b.Tok == NUMBER { + push(Expr{NUMBER, nil, math.Pow(a.Value.(float64), b.Value.(float64)), 0}) + } + case SQRT: + angle, e := pop() + if e != nil { + return e + } + if angle.Tok == IDENTIFIER { + angle = get(angle.Value.(string)) + } + + if angle.Tok == NUMBER { + push(Expr{NUMBER, nil, math.Sqrt(angle.Value.(float64)), 0}) + } else { + return fmt.Errorf("cannot `sqrt` value is not number or float @%v", idx) + } + case ADD1: + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + if a.Tok == NUMBER { + push(Expr{NUMBER, nil, a.Value.(float64) + 1.0, 0}) + } else { + return fmt.Errorf("cannot `add1` value is not a number @%v", idx) + } + case SUB1: + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + if a.Tok == NUMBER { + push(Expr{NUMBER, nil, a.Value.(float64) - 1.0, 0}) + } else { + return fmt.Errorf("cannot `sub1` value is not a number @%v", idx) + } + case SIN: + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + if a.Tok == NUMBER { + push(Expr{NUMBER, nil, math.Sin(a.Value.(float64)), 0}) + } else { + return fmt.Errorf("cannot `sin` values are not numbers or floats @%v", idx) + } + case COS: + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + if a.Tok == NUMBER { + push(Expr{NUMBER, nil, math.Cos(a.Value.(float64)), 0}) + } else { + return fmt.Errorf("cannot `cos` values are not numbers or floats @%v", idx) + } + case TAN: + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + if a.Tok == NUMBER { + push(Expr{NUMBER, nil, math.Tan(a.Value.(float64)), 0}) + } else { + return fmt.Errorf("cannot `tan` values are not numbers or floats @%v", idx) + } + case ATAN: + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + den, e := pop() + if e != nil { + return e + } + if den.Tok == IDENTIFIER { + den = get(den.Value.(string)) + } + + if a.Tok == NUMBER && den.Tok == NUMBER { + push(Expr{NUMBER, nil, math.Atan(a.Value.(float64) / den.Value.(float64)), 0}) + } else { + return fmt.Errorf("cannot `atan` values are not numbers or floats @%v", idx) + } + case LN: + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + if a.Tok == NUMBER { + push(Expr{NUMBER, nil, math.Log(a.Value.(float64)), 0}) + } else { + return fmt.Errorf("cannot `ln` values are not numbers or floats @%v", idx) + } + case LOG: + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + if a.Tok == NUMBER { + push(Expr{NUMBER, nil, math.Log10(a.Value.(float64)), 0}) + } else { + return fmt.Errorf("cannot `log` values are not numbers or floats @%v", idx) + } + case LOG3: + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + if a.Tok == NUMBER { + push(Expr{NUMBER, nil, math.Log(a.Value.(float64)) / math.Log(3.0), 0}) + } else { + return fmt.Errorf("cannot `log3` values are not numbers or floats @%v", idx) + } + case CLIP: + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + if a.Tok == NUMBER { + push(Expr{NUMBER, nil, math.Floor(a.Value.(float64)), 0}) + } else { + return fmt.Errorf("cannot `clip` values are not numbers or floats @%v", idx) + } + case SMOOTH: + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + if a.Tok == NUMBER { + push(Expr{NUMBER, nil, math.Round(a.Value.(float64)), 0}) + } else { + return fmt.Errorf("cannot `smooth` values are not numbers or floats @%v", idx) + } + case HOWMUCH: + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + if a.Tok == NUMBER { + push(Expr{NUMBER, nil, math.Abs(a.Value.(float64)), 0}) + } else { + return fmt.Errorf("cannot `howmuch` values are not numbers or floats @%v", idx) + } + case SETRAND: + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + if a.Tok == NUMBER { + rand.Seed(int64(a.Value.(float64))) + } else { + return fmt.Errorf("cannot `setrand` values are not numbers or floats @%v", idx) + } + case RAND: + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + if a.Tok == NUMBER { + max := a.Value.(float64) + r := rand.Intn(int(max)) + push(Expr{NUMBER, nil, float64(r), 0}) + } else { + return fmt.Errorf("cannot `rand` values are not numbers or floats @%v", idx) + } + case PI: + push(Expr{NUMBER, nil, 3.14159265358979323846, 0}) + case E: + push(Expr{NUMBER, nil, 2.71828182845904523536, 0}) + case ISINT: + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + push(Expr{BOOLEAN, nil, a.Value.(float64) == float64(int64(a.Value.(float64))), 0}) + case ISNUMBER: + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + push(Expr{BOOLEAN, nil, (a.Tok == NUMBER), 0}) + case NUMBERIZE: + str, e := pop() + if e != nil { + return e + } + if str.Tok != STRING { + return fmt.Errorf("cannot `numberize` not a string @%v", idx) + } + + num, err := strconv.ParseFloat(str.Value.(string), 64) + if err != nil { + return err + } + + push(Expr{NUMBER, nil, num, 0}) + case ISOLATE: + b, e := pop() + if e != nil { + return e + } + if b.Tok == IDENTIFIER { + b = get(b.Value.(string)) + } + + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + if a.Tok == NUMBER && b.Tok == NUMBER { + push(Expr{NUMBER, nil, float64(int64(a.Value.(float64)) & int64(b.Value.(float64))), 0}) + } else { + return fmt.Errorf("cannot `isolate` values are not numbers @%v", idx) + } + case MIX: + b, e := pop() + if e != nil { + return e + } + if b.Tok == IDENTIFIER { + b = get(b.Value.(string)) + } + + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + if a.Tok == NUMBER && b.Tok == NUMBER { + push(Expr{NUMBER, nil, float64(int64(a.Value.(float64)) | int64(b.Value.(float64))), 0}) + } else { + return fmt.Errorf("cannot `mix` values are not numbers @%v", idx) + } + case CONTRADICT: + b, e := pop() + if e != nil { + return e + } + if b.Tok == IDENTIFIER { + b = get(b.Value.(string)) + } + + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + if a.Tok == NUMBER && b.Tok == NUMBER { + push(Expr{NUMBER, nil, float64(int64(a.Value.(float64)) ^ int64(b.Value.(float64))), 0}) + } else { + return fmt.Errorf("cannot `contradict` values are not numbers @%v", idx) + } + case COMPL: + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + if a.Tok == NUMBER { + push(Expr{NUMBER, nil, float64(^int64(a.Value.(float64))), 0}) + } else { + return fmt.Errorf("cannot `compl` values are not numbers @%v", idx) + } + case SHIFTRIGHT: + b, e := pop() + if e != nil { + return e + } + if b.Tok == IDENTIFIER { + b = get(b.Value.(string)) + } + + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + if a.Tok == NUMBER && b.Tok == NUMBER { + push(Expr{NUMBER, nil, float64(int64(a.Value.(float64)) >> int64(b.Value.(float64))), 0}) + } else { + return fmt.Errorf("cannot `shiftright` values are not numbers @%v", idx) + } + case SHIFTLEFT: + b, e := pop() + if e != nil { + return e + } + if b.Tok == IDENTIFIER { + b = get(b.Value.(string)) + } + + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + if a.Tok == NUMBER && b.Tok == NUMBER { + push(Expr{NUMBER, nil, float64(int64(a.Value.(float64)) << int64(b.Value.(float64))), 0}) + } else { + return fmt.Errorf("cannot `shiftleft` values are not numbers @%v", idx) + } + case GT: + b, e := pop() + if e != nil { + return e + } + if b.Tok == IDENTIFIER { + b = get(b.Value.(string)) + } + + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + if a.Tok == NUMBER && b.Tok == NUMBER { + push(Expr{BOOLEAN, nil, a.Value.(float64) > (b.Value.(float64)), 0}) + } else { + return fmt.Errorf("cannot `gt` values are not numbers @%v", idx) + } + case LT: + b, e := pop() + if e != nil { + return e + } + if b.Tok == IDENTIFIER { + b = get(b.Value.(string)) + } + + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + if a.Tok == NUMBER && b.Tok == NUMBER { + push(Expr{BOOLEAN, nil, a.Value.(float64) < (b.Value.(float64)), 0}) + + } else { + return fmt.Errorf("cannot `lt` values are not numbers @%v", idx) + } + case EQ: + b, e := pop() + if e != nil { + return e + } + if b.Tok == IDENTIFIER { + b = get(b.Value.(string)) + } + + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + if a.Tok == NUMBER && b.Tok == NUMBER { + push(Expr{BOOLEAN, nil, a.Value.(float64) == (b.Value.(float64)), 0}) + } else { + return fmt.Errorf("cannot `eq` values are not numbers @%v", idx) + } + case GE: + b, e := pop() + if e != nil { + return e + } + if b.Tok == IDENTIFIER { + b = get(b.Value.(string)) + } + + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + if a.Tok == NUMBER && b.Tok == NUMBER { + push(Expr{BOOLEAN, nil, a.Value.(float64) >= (b.Value.(float64)), 0}) + } else { + return fmt.Errorf("cannot `ge` values are not numbers @%v", idx) + } + case LE: + b, e := pop() + if e != nil { + return e + } + if b.Tok == IDENTIFIER { + b = get(b.Value.(string)) + } + + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + if a.Tok == NUMBER && b.Tok == NUMBER { + push(Expr{BOOLEAN, nil, a.Value.(float64) <= (b.Value.(float64)), 0}) + } else { + return fmt.Errorf("cannot `le` values are not numbers @%v", idx) + } + case NE: + b, e := pop() + if e != nil { + return e + } + if b.Tok == IDENTIFIER { + b = get(b.Value.(string)) + } + + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + if a.Tok == NUMBER && b.Tok == NUMBER { + push(Expr{BOOLEAN, nil, a.Value.(float64) != (b.Value.(float64)), 0}) + } else { + return fmt.Errorf("cannot `ne` values are not numbers @%v", idx) + } + case ISNULL: + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + push(Expr{BOOLEAN, nil, (a.Tok == NULL || a.Value == nil), 0}) + case NEGATIVE: + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + if a.Tok == NUMBER { + push(Expr{BOOLEAN, nil, a.Value.(float64) < 0.0, 0}) + } else { + return fmt.Errorf("cannot `negative` values are not numbers @%v", idx) + } + case AND: + b, e := pop() + if e != nil { + return e + } + if b.Tok == IDENTIFIER { + b = get(b.Value.(string)) + } + + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + if a.Tok == BOOLEAN && b.Tok == BOOLEAN { + push(Expr{BOOLEAN, nil, (a.Value.(bool) && b.Value.(bool)), 0}) + } else { + return fmt.Errorf("cannot `and` values are not numbers @%v", idx) + } + case OR: + b, e := pop() + if e != nil { + return e + } + if b.Tok == IDENTIFIER { + b = get(b.Value.(string)) + } + + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + if a.Tok == BOOLEAN && b.Tok == BOOLEAN { + push(Expr{BOOLEAN, nil, (a.Value.(bool) || b.Value.(bool)), 0}) + } else { + return fmt.Errorf("cannot `or` values are not numbers @%v", idx) + } + case XOR: + b, e := pop() + if e != nil { + return e + } + if b.Tok == IDENTIFIER { + b = get(b.Value.(string)) + } + + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + + if a.Tok == BOOLEAN && b.Tok == BOOLEAN { + push(Expr{BOOLEAN, nil, (a.Value.(bool) != b.Value.(bool)), 0}) + } else { + return fmt.Errorf("cannot `xor` values are not numbers @%v", idx) + } + case DISP: + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + fmt.Printf("%v\n", get(a.Value.(string))) + } else if a.Tok == NUMBER { + if a.Value.(float64) == float64(int64(a.Value.(float64))) { + fmt.Printf("%d\n", int64(a.Value.(float64))) + } else { + fmt.Printf("%f\n", a.Value.(float64)) + } + } else { + fmt.Printf("%v\n", a.Value) + } + case LISTEN: + var str string + _, err := fmt.Scanln(&str) + + if err != nil { + return err + } + push(Expr{STRING, nil, str, 0}) + case COMPLAIN: + a, e := pop() + if e != nil { + return e + } + if a.Tok == IDENTIFIER { + a = get(a.Value.(string)) + } + fmt.Fprintf(os.Stderr, "%v", a.Value) + case NEWLINE: + fmt.Println() + case TAB: + fmt.Println("\t") + case WHEREAMI: + conn, e := net.Dial("udp", "8.8.8.8:80") + if e != nil { + return e + } + defer conn.Close() + + localAddr := conn.LocalAddr().(*net.UDPAddr) + + fmt.Println(localAddr.IP.String()) + case VERSION: + fmt.Println("var'aq -- 0.9.0 Martoq") + case ARGV: + arg := Expr{LIST, make([]Expr, 0), nil, 0} + for _, v := range argv { + arg.Exprs = append(arg.Exprs, Expr{STRING, nil, v, 0}) + } + push(arg) + case TIME: + push(Expr{NUMBER, nil, float64(time.Now().Unix()), 0}) + case TILDE: + default: + return fmt.Errorf("unknown opcode") + } + } + return nil +} diff --git a/varaq/parser.go b/varaq/parser.go new file mode 100755 index 0000000..ed160d8 --- /dev/null +++ b/varaq/parser.go @@ -0,0 +1,81 @@ +package varaq + +import ( + "fmt" + "strings" +) + +type Expr struct { + Tok TokenType + Exprs []Expr + Value any + Count int +} + +type Parser struct { + tokens []Token + pos int + end int +} + +func (p Parser) curr() Token { + return p.tokens[p.pos] +} + +func parse(p Parser, stop TokenType, tt TokenType) (Expr, int) { + code := Expr{tt, make([]Expr, 0), nil, 0} + + for { + if p.pos < 0 || p.pos > p.end-1 { + break + } + + if p.curr().Toktype == stop { + p.pos += 1 + break + } + + if p.curr().Toktype == LEFTBRACE { + p.pos += 1 + ex, pos := parse(p, RIGHTBRACE, FUNCTION) + p.pos = pos + code.Exprs = append(code.Exprs, ex) + code.Count += 1 + } else if p.curr().Toktype == LEFTPAREN { + p.pos += 1 + ex, pos := parse(p, RIGHTPAREN, LIST) + p.pos = pos + code.Exprs = append(code.Exprs, ex) + code.Count += 1 + } else { + code.Exprs = append(code.Exprs, Expr{p.curr().Toktype, nil, p.curr().Literal, 0}) + p.pos += 1 + } + } + + return code, p.pos +} + +func Parse(tokens []Token) Expr { + parser := Parser{tokens, 0, len(tokens)} + ex, _ := parse(parser, EOF, SCRIPT) + return ex +} + +func PrintDump(code Expr, depth int) { + for _, c := range code.Exprs { + if c.Tok == FUNCTION { + depth += 1 + fmt.Printf("%s %s\n", strings.Repeat("-", depth), c.Tok) + PrintDump(c, depth) + depth -= 1 + } else if c.Tok == LIST { + depth += 1 + fmt.Printf("%s %s\n", strings.Repeat("-", depth), c.Tok) + PrintDump(c, depth) + depth -= 1 + } else { + fmt.Printf("%s %s: %v\n", strings.Repeat("-", depth), c.Tok, c.Value) + } + } +} diff --git a/varaq/scanner.go b/varaq/scanner.go new file mode 100755 index 0000000..35ae03b --- /dev/null +++ b/varaq/scanner.go @@ -0,0 +1,425 @@ +package varaq + +import ( + "bufio" + "fmt" + "io/ioutil" + "strconv" + "strings" +) + +var keywords = map[string]TokenType{ + "false": FALSE, + "true": TRUE, + "pi": PI, + "e": E, + "pop": POP, + "dup": DUP, + "exch": EXCH, + "clear": CLEAR, + "remember": REMEMBER, + "forget": FORGET, + "dump": DUMP, + "name": NAME, + "set": SET, + "ifyes": IFYES, + "ifno": IFNO, + "choose": CHOOSE, + "eval": EVAL, + "escape": ESCAPE, + "repeat": REPEAT, + "split": SPLIT, + "cons": CONS, + "shatter": SHATTER, + "empty?": EMPTY, + "compose": COMPOSE, + "streq?": STREQ, + "strcut": STRCUT, + "strmeasure": STRMEASURE, + "strtie": STRTIE, + "explode": EXPLODE, + "add": ADD, + "sub": SUB, + "mul": MUL, + "div": DIV, + "idiv": IDIV, + "mod": MOD, + "pow": POW, + "sqrt": SQRT, + "add1": ADD1, + "sub1": SUB1, + "sin": SIN, + "cos": COS, + "tan": TAN, + "atan": ATAN, + "ln": LN, + "log": LOG, + "log3": LOG3, + "clip": CLIP, + "smooth": SMOOTH, + "howmuch": HOWMUCH, + "setrand": SETRAND, + "rand": RAND, + "numberize": NUMBERIZE, + "isolate": ISOLATE, + "mix": MIX, + "contradict": CONTRADICT, + "compl": COMPL, + "shiftright": SHIFTRIGHT, + "shiftleft": SHIFTLEFT, + "gt?": GT, + "lt?": LT, + "eq?": EQ, + "ge?": GE, + "le?": LE, + "ne?": NE, + "null": NULL, + "null?": ISNULL, + "int?": ISINT, + "number?": ISNUMBER, + "negative?": NEGATIVE, + "and": AND, + "or": OR, + "xor": XOR, + "disp": DISP, + "listen": LISTEN, + "complain": COMPLAIN, + "time": TIME, + "gc": GARBAGECOLLECT, + "newline": NEWLINE, + "tab": TAB, + "whereami": WHEREAMI, + "version": VERSION, + "argv": ARGV, + "chu'tut": TAB, + "chu'DonwI'": NEWLINE, + "bep": COMPLAIN, + "chImmoH": CLEAR, + "chIm'a'": EMPTY, + "cher": SET, + "boq": ADD, + "chov": EVAL, + "chuv": MOD, + "cha'": DISP, + "DuD": MIX, + "ghap": XOR, + "ghurmI'": E, + "ghurtaH": LN, + "ghorqu'": SHATTER, + "ghobe'chugh": IFNO, + "HIja'chugh": IFYES, + "Hotlh": DUMP, + "HeHmI'": PI, + "Habwav": IDIV, + "je": AND, + "jor": EXPLODE, + "joq": OR, + "loS'ar": SQRT, + "latlh": DUP, + "boq'egh": MUL, + "boqHa'qa'": POW, + "law''a'": GT, + "law'rap'a'": GE, + "maHghurtaH": LOG, + "mi'moH": NUMBERIZE, + "muv": CONS, + "mobmoH": ISOLATE, + "mIScher": SETRAND, + "mIS": RAND, + "nIHghoS": SHIFTRIGHT, + "nargh": ESCAPE, + "naQmoH": COMPOSE, + "pagh'a'": NULL, + "pong": NAME, + "poSghoS": SHIFTLEFT, + "puS'a'": LT, + "puSrap'a'": LE, + "qaw": REMEMBER, + "qawHa'": FORGET, + "qojmI'": TAN, + "qojHa'": ATAN, + "Qo'moH": COMPL, + "rap'a'": EQ, + "rapbe'a'": NE, + "tlheghrar": STRTIE, + "poD": CLIP, + "Hab": SMOOTH, + "'ar": HOWMUCH, + "SIj": SPLIT, + "boqHa'": SUB, + "tam": EXCH, + "taH'a'": NEGATIVE, + "tlhoch": CONTRADICT, + "tlheghpe'": STRCUT, + "tlheghjuv": STRMEASURE, + "tlheghrap'a'": STREQ, + "vangqa'": REPEAT, + "wIv": CHOOSE, + "woD": POP, + "boqHa''egh": DIV, + "wa'boqHa'": SUB1, + "wa'boq": ADD1, + "wejghurtaH": LOG3, + "'Ij": LISTEN, + "poH": TIME, + // Wrong word in original spec, old one meant "waving hands or flapping" + // Also fixes the conflicting joq issue meaning sin or 'or' + "yu'eghHa'": COS, + "yu'egh": SIN, + // This one has a special case too as it is the same as the '~' operator + "lI'moH": TILDE, + "woDHa'": GARBAGECOLLECT, + "taghDe'": ARGV, + "pongmI'": VERSION, + "nuqDaq_jIH": WHEREAMI, +} + +func isDigit(s string) bool { + return (s >= "0" && s <= "9") || s == "-" +} + +func isAlpha(s string) bool { + return (s >= "a" && s <= "z") || + (s >= "A" && s <= "Z") || + s == "_" || s == "'" || s == "?" +} + +func isSpecialChar(s string) bool { + return s == "(" || s == ")" || s == "{" || s == "}" +} + +func isAlphaNumeric(s string) bool { + return isAlpha(s) || isDigit(s) +} + +func isEOF(scanner *bufio.Scanner) bool { + return scanner.Text() == "" +} + +func peek(scanner *bufio.Scanner) string { + return scanner.Text() +} + +func next(scanner *bufio.Scanner) string { + scanner.Scan() + return scanner.Text() +} + +func match(scanner *bufio.Scanner, expected string) bool { + if isEOF(scanner) { + return false + } + if scanner.Text() != expected { + return false + } + + return true +} + +func str(scanner *bufio.Scanner, tokens []Token, line_no int) ([]Token, int, error) { + value := "" + for { + char := next(scanner) + if char == "\"" || isEOF(scanner) { + break + } + if char == "\n" { + line_no++ + } + value += char + } + + if isEOF(scanner) { + return tokens, line_no, fmt.Errorf("unterminated string") + } + + return append(tokens, Token{STRING, value, value, line_no}), line_no, nil +} + +func num(scanner *bufio.Scanner, tokens []Token, line_no int) ([]Token, int, error) { + value := peek(scanner) + no_advance := false + + for { + digit := next(scanner) + if !isDigit(digit) { + break + } + value += digit + } + + if peek(scanner) == "." && isDigit(next(scanner)) { + value += "." + for { + digit := peek(scanner) + if !isDigit(digit) { + break + } + value += digit + next(scanner) + } + } else { + no_advance = true + } + + num, err := strconv.ParseFloat(value, 64) + if err != nil { + return tokens, line_no, fmt.Errorf("string to rational error") + } + + if no_advance { + tokens = append(tokens, Token{NUMBER, value, num, line_no}) + return scanToken(scanner, tokens, line_no, true) + } + + return append(tokens, Token{NUMBER, value, num, line_no}), line_no, nil + +} + +func identifier(scanner *bufio.Scanner, tokens []Token, line_no int) ([]Token, int, error) { + value := peek(scanner) + no_advance := false + for { + chr := next(scanner) + if !isAlphaNumeric(chr) { + if isSpecialChar(chr) { + no_advance = true + } + break + } + + value += chr + } + + typ, prs := keywords[value] + if prs { + if no_advance { // TODO: ugly but golang wont let me do what I want + tokens = append(tokens, Token{typ, value, value, line_no}) + return scanToken(scanner, tokens, line_no, true) + } + return append(tokens, Token{typ, value, value, line_no}), line_no, nil + } + + if no_advance { + tokens = append(tokens, Token{IDENTIFIER, value, value, line_no}) + return scanToken(scanner, tokens, line_no, true) + } + return append(tokens, Token{IDENTIFIER, value, value, line_no}), line_no, nil +} + +func include(scanner *bufio.Scanner, tokens []Token, line_no int) ([]Token, int, error) { + value := "" + for { + char := next(scanner) + if char == "\n" || isEOF(scanner) { + break + } + if char != " " && char != "\r" && char != "\t" { + value += char + } + } + + if isEOF(scanner) { + return tokens, line_no, fmt.Errorf("unterminated string") + } + + code, err := ioutil.ReadFile(value) // the file is inside the local directory + if err != nil { + return nil, 0, err + } + + toks, err := Tokenize(string(code)) + if err != nil { + return nil, 0, err + } + + // need to remove EOF from script import + n := len(toks) - 1 + toks = toks[:n] + + return append(tokens, toks...), line_no, nil +} + +func scanToken(scanner *bufio.Scanner, tokens []Token, line_no int, no_advance bool) ([]Token, int, error) { + var c string + if no_advance { + c = peek(scanner) + } else { + c = next(scanner) + } + switch c { + case "(": + if next(scanner) == "*" { + for { + if next(scanner) == "*" { + if next(scanner) != ")" { + return tokens, line_no, fmt.Errorf("did not close comment at line number %d", line_no) + } + break + } + } + } else { + tokens = append(tokens, Token{LEFTPAREN, c, nil, line_no}) + return scanToken(scanner, tokens, line_no, true) + } + case ")": + return append(tokens, Token{RIGHTPAREN, c, nil, line_no}), line_no, nil + case "{": + return append(tokens, Token{LEFTBRACE, c, nil, line_no}), line_no, nil + case "}": + return append(tokens, Token{RIGHTBRACE, c, nil, line_no}), line_no, nil + case "~": + return append(tokens, Token{TILDE, c, nil, line_no}), line_no, nil + case "/": + if match(scanner, "/") { // TODO: make this an import token + next(scanner) + return include(scanner, tokens, line_no) + } else { + return append(tokens, Token{SLASH, c, nil, line_no}), line_no, nil + } + case "*": + return append(tokens, Token{STAR, c, nil, line_no}), line_no, nil + case " ": + case "\r": + case "\t": + // Ignore whitespace. + break + case "\n": + line_no++ + case "\"": + return str(scanner, tokens, line_no) + default: + if isDigit(peek(scanner)) { + return num(scanner, tokens, line_no) + } else if isAlpha(peek(scanner)) { + return identifier(scanner, tokens, line_no) + } else { + return tokens, line_no, fmt.Errorf("unixpected character %d", line_no) + } + } + + return tokens, line_no, nil +} + +func Tokenize(source string) ([]Token, error) { + var err error + + line_no := 1 + tokens := make([]Token, 0) + + scanner := bufio.NewScanner(strings.NewReader(source)) + scanner.Split(bufio.ScanBytes) + + for { + tokens, line_no, err = scanToken(scanner, tokens, line_no, false) + if isEOF(scanner) { + break + } + if err != nil { + return tokens, err + } + } + + tokens = append(tokens, Token{EOF, "", nil, line_no}) + return tokens, nil +} diff --git a/varaq/token.go b/varaq/token.go new file mode 100755 index 0000000..18f2c10 --- /dev/null +++ b/varaq/token.go @@ -0,0 +1,217 @@ +package varaq + +type TokenType int64 + +const ( + UNDEFINED TokenType = iota + IDENTIFIER + STRING + NUMBER + BOOLEAN + LIST + SCRIPT + FUNCTION + ERROR + LEFTPAREN + RIGHTPAREN + LEFTBRACE + RIGHTBRACE + TILDE + SLASH + STAR + FALSE + TRUE + PI + E + EOF + POP + DUP + EXCH + CLEAR + REMEMBER + FORGET + DUMP + NAME + SET + IFYES + IFNO + CHOOSE + EVAL + ESCAPE + REPEAT + SPLIT + CONS + SHATTER + EMPTY + COMPOSE + STREQ + STRCUT + STRMEASURE + STRTIE + EXPLODE + ADD + SUB + MUL + DIV + IDIV + MOD + POW + SQRT + ADD1 + SUB1 + SIN + COS + TAN + ATAN + LN + LOG + LOG3 + CLIP + SMOOTH + HOWMUCH + SETRAND + RAND + NUMBERIZE + ISOLATE + MIX + CONTRADICT + COMPL + SHIFTRIGHT + SHIFTLEFT + GT + LT + EQ + GE + LE + NE + NULL + NEGATIVE + ISNULL + ISINT + ISNUMBER + AND + OR + XOR + DISP + LISTEN + COMPLAIN + NEWLINE + TAB + WHEREAMI + VERSION + ARGV + TIME + GARBAGECOLLECT +) + +var tokens = [...]string{ + UNDEFINED: "UNDEFINED", + IDENTIFIER: "IDENTIFIER", + STRING: "STRING", + NUMBER: "NUMBER", + LIST: "LIST", + ERROR: "ERROR", + SCRIPT: "SCRIPT", + FUNCTION: "FUNCTION", + LEFTPAREN: "LEFTPAREN", + RIGHTPAREN: "RIGHTPAREN", + LEFTBRACE: "LEFTBRACE", + RIGHTBRACE: "RIGHTBRACE", + TILDE: "TILDE", + SLASH: "SLASH", + STAR: "STAR", + FALSE: "FALSE", + TRUE: "TRUE", + PI: "PI", + E: "E", + EOF: "EOF", + POP: "POP", + DUP: "DUP", + EXCH: "EXCH", + CLEAR: "CLEAR", + REMEMBER: "REMEMBER", + FORGET: "FORGET", + DUMP: "DUMP", + NAME: "NAME", + SET: "SET", + IFYES: "YES?", + IFNO: "NO?", + CHOOSE: "CHOOSE", + EVAL: "EVAL", + ESCAPE: "ESCAPE", + REPEAT: "REPEAT", + SPLIT: "SPLIT", + CONS: "CONS", + SHATTER: "SHATTER", + EMPTY: "EMPTY?", + COMPOSE: "COMPOSE", + STREQ: "STREQ?", + STRCUT: "STRCUT", + STRMEASURE: "STRMEASURE", + STRTIE: "STRTIE", + EXPLODE: "EXPLODE", + ADD: "ADD", + SUB: "SUB", + MUL: "MUL", + DIV: "DIV", + IDIV: "IDIV", + MOD: "MOD", + POW: "POW", + SQRT: "SQRT", + ADD1: "ADD1", + SUB1: "SUB1", + SIN: "SIN", + COS: "COS", + TAN: "TAN", + ATAN: "ATAN", + LN: "LN", + LOG: "LOG", + LOG3: "LOG3", + CLIP: "CLIP", + SMOOTH: "SMOOTH", + HOWMUCH: "HOWMUCH?", + SETRAND: "SETRAND", + RAND: "RAND", + NUMBERIZE: "NUMBERIZE", + ISOLATE: "ISOLATE", + MIX: "MIX", + CONTRADICT: "CONTRADICT", + COMPL: "COMPL", + SHIFTRIGHT: "SHIFTRIGHT", + SHIFTLEFT: "SHIFTLEFT", + GT: "GT?", + LT: "LT?", + EQ: "EQ?", + GE: "GE?", + LE: "LE?", + NE: "NE?", + NULL: "NULL", + NEGATIVE: "NEGATIVE", + ISNULL: "NULL?", + ISINT: "INT?", + ISNUMBER: "NUMBER?", + AND: "AND", + OR: "OR", + XOR: "XOR", + DISP: "DISP", + LISTEN: "LISTEN", + COMPLAIN: "COMPLAIN", + NEWLINE: "NEWLINE", + TAB: "TAB", + WHEREAMI: "WHEREAMI", + VERSION: "VERSION", + ARGV: "ARGV", + TIME: "TIME", + GARBAGECOLLECT: "GARBAGECOLLECT", +} + +func (me TokenType) String() string { + return tokens[me] +} + +type Token struct { + Toktype TokenType + Lexeme string + Literal any + Line int +}