Compare commits
10 Commits
c1dbcd1233
...
0e5a9acccf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e5a9acccf | ||
|
|
2e2712704e | ||
|
|
ed233c17de | ||
|
|
b377d4e69f | ||
|
|
fcd7c31ea7 | ||
|
|
9e6f0c454a | ||
|
|
d8166a9ff6 | ||
|
|
004788bbfb | ||
|
|
15d65269aa | ||
|
|
83965b82e8 |
15
Cargo.lock
generated
15
Cargo.lock
generated
@@ -4,22 +4,13 @@ version = 3
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "plxml"
|
name = "plxml"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"roxmltree",
|
"roxmltree",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "roxmltree"
|
name = "roxmltree"
|
||||||
version = "0.14.1"
|
version = "0.19.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "921904a62e410e37e215c40381b7117f830d9d89ba60ab5236170541dd25646b"
|
checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f"
|
||||||
dependencies = [
|
|
||||||
"xmlparser",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "xmlparser"
|
|
||||||
version = "0.13.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "114ba2b24d2167ef6d67d7d04c8cc86522b87f490025f39f0303b7db5bf5e3d8"
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "plxml"
|
name = "plxml"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
default-run = "interpreter"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
roxmltree = "0.14.1"
|
roxmltree = "0.19"
|
||||||
@@ -1,3 +1,8 @@
|
|||||||
# PL/XML
|
# PL/XML
|
||||||
|
|
||||||
Please don't ever use this language. Thank you.
|
Please don't ever use this language. Thank you.
|
||||||
|
|
||||||
|
However, if, for some reason, you feel like sharing
|
||||||
|
my suffering, check out the [syntax](doc/syntax.md),
|
||||||
|
[documentation](doc/language.md), and
|
||||||
|
[standard library](doc/stl.md)!
|
||||||
|
|||||||
436
doc/language.md
Normal file
436
doc/language.md
Normal file
@@ -0,0 +1,436 @@
|
|||||||
|
# PL/XML: The Handbook
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
As do many ideas, PL/XML sprang from the need
|
||||||
|
to do something extremely useless just for fun,
|
||||||
|
to see how it would turn out. The original premise
|
||||||
|
was a programming language based on XML syntax.
|
||||||
|
Its name came from (PL/SQL)[https://en.wikipedia.org/wiki/PL/SQL],
|
||||||
|
some kind of torture device for IT students.
|
||||||
|
|
||||||
|
In order not to make this project completely useless,
|
||||||
|
I wrote the original interpreter in Rust (WIIR!)
|
||||||
|
to get better acquainted with the language.
|
||||||
|
|
||||||
|
The result is a dynamically-typed procedural language,
|
||||||
|
which is provably Turing-complete since I was able to
|
||||||
|
write a blazing slow (Brainfuck interpreter)[../sample/bf.pl.xml]
|
||||||
|
with it. All the convenient aspects are overshadowed
|
||||||
|
by the utter agony that manually writing XML is.
|
||||||
|
|
||||||
|
## Quick guide
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<program name="primer">
|
||||||
|
<function name="my-print">
|
||||||
|
<arguments>
|
||||||
|
<argument name="the-text" />
|
||||||
|
</arguments>
|
||||||
|
<body>
|
||||||
|
<call function="print-line">
|
||||||
|
<arguments>
|
||||||
|
<value variable="the-text" />
|
||||||
|
</arguments>
|
||||||
|
</call>
|
||||||
|
</body>
|
||||||
|
</function>
|
||||||
|
<main>
|
||||||
|
<assign variable="text">
|
||||||
|
<string value="Hello, world!">
|
||||||
|
</assign>
|
||||||
|
<call function="my-print">
|
||||||
|
<arguments>
|
||||||
|
<value variable="text" />
|
||||||
|
</arguments>
|
||||||
|
</call>
|
||||||
|
</main>
|
||||||
|
</program>
|
||||||
|
```
|
||||||
|
|
||||||
|
This slightly over-engineered hello world program
|
||||||
|
contains some basics of PL/XML, such as program structure,
|
||||||
|
variable assignment and retrieval, instanciation, and function
|
||||||
|
definition and calls.
|
||||||
|
|
||||||
|
### Program structure
|
||||||
|
|
||||||
|
Every PL/XML program should be wrapped in a `program`
|
||||||
|
node specifying its name. This program must contain a
|
||||||
|
`main` node that will be executed first, and can define
|
||||||
|
a set of functions using `function` nodes. Inside `main`
|
||||||
|
and the function `body` nodes is actual code that will be
|
||||||
|
sequentially executed.
|
||||||
|
|
||||||
|
### Values
|
||||||
|
|
||||||
|
PL/XML has a few value types. The first two are the signed numeric
|
||||||
|
`integer` and `real` types, which have no precision guarantee.
|
||||||
|
Another type is the usual character `string`, which may or may not
|
||||||
|
support Unicode. The `array` type is a generic iterable collection
|
||||||
|
of any value, including arrays. Functions are values as well, and
|
||||||
|
as such can be (and technically are) stored in variables.
|
||||||
|
|
||||||
|
Integer, Real and String values can be instanciated by using the
|
||||||
|
eponymous node with a `value` attribute. For instance:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<integer value="1" />
|
||||||
|
<real value="2.5" />
|
||||||
|
<string value="hello!" />
|
||||||
|
```
|
||||||
|
|
||||||
|
Value nodes `integer`, `real`, and `string` can also be used
|
||||||
|
to cast a value to another type. For instance, a `string` value
|
||||||
|
can be parsed into a `real`, and a `real` value can be rounded down
|
||||||
|
by casting it to an `integer`.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<real>
|
||||||
|
<integer>
|
||||||
|
<real value="2.5" />
|
||||||
|
</integer>
|
||||||
|
</real>
|
||||||
|
```
|
||||||
|
|
||||||
|
Arrays can be initialized empty or with contained elements. Array
|
||||||
|
manipulation is performed through [standard library](stl.md) functions.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<array />
|
||||||
|
|
||||||
|
<array>
|
||||||
|
<integer value="0" />
|
||||||
|
<integer value="1" />
|
||||||
|
<integer value="2" />
|
||||||
|
<integer value="3" />
|
||||||
|
<real value="3.14" />
|
||||||
|
</array>
|
||||||
|
```
|
||||||
|
|
||||||
|
When boolean-like values are needed, all values
|
||||||
|
are considered truthy, except the integer 0.
|
||||||
|
|
||||||
|
### Variable manipulation
|
||||||
|
|
||||||
|
A value can be assigned to and retrieved from a variable.
|
||||||
|
Variables are dynamically-typed, meaning you can assign any
|
||||||
|
type to any variable, no matter its previous type.
|
||||||
|
|
||||||
|
To assign a value to a variable, use an `assign` node with a
|
||||||
|
`variable` attribute specifying the name of the variable, and
|
||||||
|
add a child node containing any value-returning node, such as
|
||||||
|
`string`.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<assign variable="my-variable">
|
||||||
|
<string value="hello!" />
|
||||||
|
</assign>
|
||||||
|
```
|
||||||
|
|
||||||
|
To retrieve a value, use a `value` node with the same
|
||||||
|
`variable` attribute.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<value variable="my-variable" />
|
||||||
|
```
|
||||||
|
|
||||||
|
Variables have some sort of scoping which is function body-bound:
|
||||||
|
there is a global scope containing standard and user-defined
|
||||||
|
functions from which local scopes inherit.
|
||||||
|
|
||||||
|
### Function calls
|
||||||
|
|
||||||
|
A `call` node is used to call functions. Function arguments
|
||||||
|
are passed as child nodes to a `arguments` node. The short
|
||||||
|
syntax uses the `function` attribute to specify the function
|
||||||
|
to call.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<call function="my-print">
|
||||||
|
<arguments>
|
||||||
|
<string value="text" />
|
||||||
|
</arguments>
|
||||||
|
</call>
|
||||||
|
```
|
||||||
|
|
||||||
|
Functions are values that can also be stored and retrieved
|
||||||
|
through variables. Thus exists a longer syntax allowing dynamic
|
||||||
|
calls, without the `function` attribute but putting the function
|
||||||
|
value as a child of the `call` node.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<call>
|
||||||
|
<value variable="my-print" />
|
||||||
|
<arguments>
|
||||||
|
<string value="text" />
|
||||||
|
</arguments>
|
||||||
|
</call>
|
||||||
|
```
|
||||||
|
|
||||||
|
You can find a variety of utility functions in the
|
||||||
|
[standard library](stl.md).
|
||||||
|
|
||||||
|
### Function definition
|
||||||
|
|
||||||
|
Functions are defined at the top level, as child nodes to the
|
||||||
|
`program` node. The `name` attribute specifies the function name
|
||||||
|
to use when called. They have a `arguments` node, defining to which
|
||||||
|
local variables arguments will be assigned, in order of `argument` nodes,
|
||||||
|
and a `body` node containing the code that will be executed when the function
|
||||||
|
is called.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<function name="my-print">
|
||||||
|
<arguments>
|
||||||
|
<argument name="the-text" />
|
||||||
|
</arguments>
|
||||||
|
<body>
|
||||||
|
<call function="print-line">
|
||||||
|
<arguments>
|
||||||
|
<value variable="the-text" />
|
||||||
|
</arguments>
|
||||||
|
</call>
|
||||||
|
</body>
|
||||||
|
</function>
|
||||||
|
```
|
||||||
|
|
||||||
|
This function, named "my-print" takes one argument, called "the-text".
|
||||||
|
It uses this value to call the standard library "print-line" function.
|
||||||
|
|
||||||
|
Functions can return values. Wrap a value in a `return` node to use it as
|
||||||
|
a return value for the function. Subsequent code will not be executed, and
|
||||||
|
the caller can use the `call` node as any other value.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<function name="sum-plus-two">
|
||||||
|
<arguments>
|
||||||
|
<argument name="number1" />
|
||||||
|
<argument name="number2" />
|
||||||
|
</arguments>
|
||||||
|
<body>
|
||||||
|
<return>
|
||||||
|
<add>
|
||||||
|
<value variable="number1" />
|
||||||
|
<value variable="number2" />
|
||||||
|
<integer value="2" />
|
||||||
|
</add>
|
||||||
|
</return>
|
||||||
|
</body>
|
||||||
|
</function>
|
||||||
|
```
|
||||||
|
|
||||||
|
This function takes two arguments and adds them together,
|
||||||
|
adding two to the sum, and returns the result.
|
||||||
|
|
||||||
|
### Built-in operations
|
||||||
|
|
||||||
|
The previous example uses an `add` node to sum integer values.
|
||||||
|
PL/XML has multiple usual arithmetic and logic operators to
|
||||||
|
manipulate values, used directly as nodes containing them.
|
||||||
|
|
||||||
|
Only compatible values will be used together. Integers will
|
||||||
|
automatically be promoted to reals if needed.
|
||||||
|
|
||||||
|
`add` and `multiply` both take any number of number arguments and will
|
||||||
|
compute their sum or product. `add` can also be used to concatenate
|
||||||
|
string values.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<add>
|
||||||
|
<integer value="9" />
|
||||||
|
<integer value="33" />
|
||||||
|
</add>
|
||||||
|
|
||||||
|
<add>
|
||||||
|
<string value="hello, " />
|
||||||
|
<string value="world!" />
|
||||||
|
</add>
|
||||||
|
|
||||||
|
<multiply>
|
||||||
|
<integer value="6" />
|
||||||
|
<real value="7" />
|
||||||
|
</multiply>
|
||||||
|
```
|
||||||
|
|
||||||
|
`subtract` and `divide` take at least one numeric argument, which will be
|
||||||
|
subtracted from or divided using subsequent arguments.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<subtract>
|
||||||
|
<integer value="51" />
|
||||||
|
<integer value="9" />
|
||||||
|
</subtract>
|
||||||
|
|
||||||
|
<divide>
|
||||||
|
<integer value="126" />
|
||||||
|
<integer value="3" />
|
||||||
|
</divide>
|
||||||
|
```
|
||||||
|
|
||||||
|
`and` and `or` also take at least one argument, and will chain their
|
||||||
|
corresponding logic operation on all arguments.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<and>
|
||||||
|
<integer value="1" />
|
||||||
|
<string value="yes" />
|
||||||
|
</and>
|
||||||
|
|
||||||
|
<or>
|
||||||
|
<integer value="0" />
|
||||||
|
<string value="no" />
|
||||||
|
</or>
|
||||||
|
```
|
||||||
|
|
||||||
|
`not` takes exactly one argument, and will give a truthy value
|
||||||
|
(the integer 1) if the argument is falsy (the integer 0), and
|
||||||
|
a falsy value otherwise.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<not>
|
||||||
|
<string value="make me falsy" />
|
||||||
|
</not>
|
||||||
|
```
|
||||||
|
|
||||||
|
`equal`, `greater`, and `lower` all take exactly two arguments,
|
||||||
|
and will give a truthy value if the first is respectively
|
||||||
|
equal to, greater than, or lower than the second, and a falsy
|
||||||
|
value otherwise.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<equal>
|
||||||
|
<integer value="5" />
|
||||||
|
<integer value="5" />
|
||||||
|
</equal>
|
||||||
|
|
||||||
|
<greater>
|
||||||
|
<integer value="4" />
|
||||||
|
<integer value="2" />
|
||||||
|
</greater>
|
||||||
|
|
||||||
|
<lower>
|
||||||
|
<integer value="11" />
|
||||||
|
<integer value="16" />
|
||||||
|
</lower>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Control structures
|
||||||
|
|
||||||
|
As in many imperative languages, control structures are
|
||||||
|
used to manipulate the flow of code execution. The first
|
||||||
|
one is the `if` structure. Its first child is the value checked
|
||||||
|
for truthyness, after which a `then` block contains the code
|
||||||
|
to execute if it is truthy, otherwise the code contained in the
|
||||||
|
optional `else` block will be executed.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<if>
|
||||||
|
<value variable="my-condition" />
|
||||||
|
<then>
|
||||||
|
<call function="print-line">
|
||||||
|
<arguments>
|
||||||
|
<string value="truthy" />
|
||||||
|
</arguments>
|
||||||
|
</call>
|
||||||
|
</then>
|
||||||
|
<else>
|
||||||
|
<call function="print-line">
|
||||||
|
<arguments>
|
||||||
|
<string value="falsy" />
|
||||||
|
</arguments>
|
||||||
|
</call>
|
||||||
|
</else>
|
||||||
|
</if>
|
||||||
|
```
|
||||||
|
|
||||||
|
Three other structures give access to loops. `while` loops
|
||||||
|
contain the condition to check, which will be executed at the beginning
|
||||||
|
of each loop turn, and a `do` node containing the code to execute.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<while>
|
||||||
|
<integer value="1" />
|
||||||
|
<do>
|
||||||
|
<call function="print-line">
|
||||||
|
<arguments>
|
||||||
|
<string value="forever!" />
|
||||||
|
</arguments>
|
||||||
|
</call>
|
||||||
|
</do>
|
||||||
|
</while>
|
||||||
|
```
|
||||||
|
|
||||||
|
The `for` loop takes `from`, `to`, and `step` child nodes, which should
|
||||||
|
evaluate to integer values. Code contained in the `do` child node
|
||||||
|
will be executed with a variable whose name is specified in the `variable`
|
||||||
|
attribute on the `for` node containing the current iteration value.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<for variable="i">
|
||||||
|
<from><integer value="0" /></from>
|
||||||
|
<to><integer value="10" /></to>
|
||||||
|
<step><integer value="1" /></from>
|
||||||
|
<do>
|
||||||
|
<call function="print-line">
|
||||||
|
<arguments>
|
||||||
|
<add>
|
||||||
|
<string value="iteration #" />
|
||||||
|
<string>
|
||||||
|
<value variable="i" />
|
||||||
|
</string>
|
||||||
|
</add>
|
||||||
|
</arguments>
|
||||||
|
</call>
|
||||||
|
</do>
|
||||||
|
</for>
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, the `each` loop iterates over an array, assigning its values
|
||||||
|
in order to the specified `variable`.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<each variable="v">
|
||||||
|
<value variable="my-array" />
|
||||||
|
<do>
|
||||||
|
<call function="print-line">
|
||||||
|
<arguments>
|
||||||
|
<add>
|
||||||
|
<string value="value = " />
|
||||||
|
<string>
|
||||||
|
<value variable="v" />
|
||||||
|
</string>
|
||||||
|
</add>
|
||||||
|
</arguments>
|
||||||
|
</call>
|
||||||
|
</do>
|
||||||
|
</each>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error handling
|
||||||
|
|
||||||
|
Some standard library functions or language nodes may raise
|
||||||
|
errors during execution. In a `handle` node, errors can be
|
||||||
|
caught inside a `try` node to use them in a `catch` node, which
|
||||||
|
will only be executed if an error was raised.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<handle>
|
||||||
|
<try>
|
||||||
|
<divide>
|
||||||
|
<integer value="1" />
|
||||||
|
<integer value="0" />
|
||||||
|
</divide>
|
||||||
|
</try>
|
||||||
|
<catch variable="error">
|
||||||
|
<call function="print-line">
|
||||||
|
<arguments>
|
||||||
|
<add>
|
||||||
|
<string value="eroor caught = " />
|
||||||
|
<value variable="error" />
|
||||||
|
</add>
|
||||||
|
</arguments>
|
||||||
|
</call>
|
||||||
|
</catch>
|
||||||
|
</handle>
|
||||||
|
```
|
||||||
371
doc/stl.md
Normal file
371
doc/stl.md
Normal file
@@ -0,0 +1,371 @@
|
|||||||
|
# Standard Library
|
||||||
|
|
||||||
|
The PL/XML Standard Library allows access to system functions
|
||||||
|
such as input/output, file access and array manipulation.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [PRINT](#print)
|
||||||
|
- [PRINT-LINE](#print-line)
|
||||||
|
- [INPUT](#input)
|
||||||
|
- [STRING-SPLIT](#string-split)
|
||||||
|
- [ARRAY-SET](#array-set)
|
||||||
|
- [ARRAY-PUSH](#array-push)
|
||||||
|
- [ARRAY-POP](#array-pop)
|
||||||
|
- [ARRAY-GET](#array-get)
|
||||||
|
- [ARRAY-LENGTH](#array-length)
|
||||||
|
- [TO-ASCII](#to-ascii)
|
||||||
|
- [FROM-ASCII](#from-ascii)
|
||||||
|
- [GET-ARGS](#get-args)
|
||||||
|
- [WRITE-FILE](#write-file)
|
||||||
|
- [READ-FILE](#read-file)
|
||||||
|
|
||||||
|
## PRINT
|
||||||
|
|
||||||
|
Writes a string as-is to the standard output
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
|
||||||
|
- `string` value to print
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
Nothing
|
||||||
|
|
||||||
|
### Minimal example
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<call function="print">
|
||||||
|
<arguments>
|
||||||
|
<string value="hello world!
" />
|
||||||
|
</arguments>
|
||||||
|
</call>
|
||||||
|
```
|
||||||
|
|
||||||
|
## PRINT-LINE
|
||||||
|
|
||||||
|
Writes a string to the standard output, appending a new line
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
|
||||||
|
- `string` value to print
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
Nothing
|
||||||
|
|
||||||
|
### Minimal example
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<call function="print-line">
|
||||||
|
<arguments>
|
||||||
|
<string value="hello world!" />
|
||||||
|
</arguments>
|
||||||
|
</call>
|
||||||
|
```
|
||||||
|
|
||||||
|
## INPUT
|
||||||
|
|
||||||
|
Reads from the standard input
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
|
||||||
|
Nothing
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
`string` value read
|
||||||
|
|
||||||
|
### Minimal example
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<call function="input">
|
||||||
|
<arguments />
|
||||||
|
</call>
|
||||||
|
```
|
||||||
|
|
||||||
|
## STRING-SPLIT
|
||||||
|
|
||||||
|
Splits a string into a vector of single-character strings
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
|
||||||
|
- `string` value to split
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
`array` value of strings
|
||||||
|
|
||||||
|
### Minimal examples
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<call function="string-split">
|
||||||
|
<arguments>
|
||||||
|
<string value="abcdef" />
|
||||||
|
<string value="" />
|
||||||
|
</arguments>
|
||||||
|
</call>
|
||||||
|
```
|
||||||
|
```xml
|
||||||
|
<call function="string-split">
|
||||||
|
<arguments>
|
||||||
|
<string value="id,name,type,value" />
|
||||||
|
<string value="," />
|
||||||
|
</arguments>
|
||||||
|
</call>
|
||||||
|
```
|
||||||
|
|
||||||
|
## ARRAY-SET
|
||||||
|
|
||||||
|
Sets a value at a specific index of an array.
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
|
||||||
|
- `array` to update
|
||||||
|
- `integer` index
|
||||||
|
- `any` value to set
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
Nothing
|
||||||
|
|
||||||
|
### Minimal example
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<assign variable="arr">
|
||||||
|
<array>
|
||||||
|
<string value="hello">
|
||||||
|
</array>
|
||||||
|
</assign>
|
||||||
|
<call function="array-set">
|
||||||
|
<arguments>
|
||||||
|
<value variable="arr" />
|
||||||
|
<integer value="0" />
|
||||||
|
<string value="world">
|
||||||
|
</arguments>
|
||||||
|
</call>
|
||||||
|
```
|
||||||
|
|
||||||
|
## ARRAY-PUSH
|
||||||
|
|
||||||
|
Pushes a value at the end of an array
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
|
||||||
|
- `array` to update
|
||||||
|
- `any` value to push
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
Nothing
|
||||||
|
|
||||||
|
### Minimal example
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<assign variable="arr">
|
||||||
|
<array>
|
||||||
|
<string value="hello">
|
||||||
|
</array>
|
||||||
|
</assign>
|
||||||
|
<call function="array-push">
|
||||||
|
<arguments>
|
||||||
|
<value variable="arr" />
|
||||||
|
<string value="world">
|
||||||
|
</arguments>
|
||||||
|
</call>
|
||||||
|
```
|
||||||
|
|
||||||
|
## ARRAY-POP
|
||||||
|
|
||||||
|
Removes and returns the value at the end of an array
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
|
||||||
|
- `array` to update
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
`any` value
|
||||||
|
|
||||||
|
### Minimal example
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<assign variable="arr">
|
||||||
|
<array>
|
||||||
|
<string value="hello">
|
||||||
|
</array>
|
||||||
|
</assign>
|
||||||
|
<call function="array-pop">
|
||||||
|
<arguments>
|
||||||
|
<value variable="arr" />
|
||||||
|
</arguments>
|
||||||
|
</call>
|
||||||
|
```
|
||||||
|
|
||||||
|
## ARRAY-GET
|
||||||
|
|
||||||
|
Returns the value at index of an array
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
|
||||||
|
- `array` to query
|
||||||
|
- `integer` index
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
`any` value
|
||||||
|
|
||||||
|
### Minimal example
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<assign variable="arr">
|
||||||
|
<array>
|
||||||
|
<string value="hello">
|
||||||
|
</array>
|
||||||
|
</assign>
|
||||||
|
<call function="array-get">
|
||||||
|
<arguments>
|
||||||
|
<value variable="arr" />
|
||||||
|
<integer value="0" />
|
||||||
|
</arguments>
|
||||||
|
</call>
|
||||||
|
```
|
||||||
|
|
||||||
|
## ARRAY-LENGTH
|
||||||
|
|
||||||
|
Returns the length of an array
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
|
||||||
|
- `array` to query
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
`integer` length
|
||||||
|
|
||||||
|
### Minimal example
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<assign variable="arr">
|
||||||
|
<array>
|
||||||
|
<string value="hello">
|
||||||
|
</array>
|
||||||
|
</assign>
|
||||||
|
<call function="array-length">
|
||||||
|
<arguments>
|
||||||
|
<value variable="arr" />
|
||||||
|
</arguments>
|
||||||
|
</call>
|
||||||
|
```
|
||||||
|
|
||||||
|
## TO-ASCII
|
||||||
|
|
||||||
|
Converts an integer value into an ASCII character string
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
|
||||||
|
- `integer` to convert
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
`string` corresponding ASCII character
|
||||||
|
|
||||||
|
### Minimal example
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<call function="to-ascii">
|
||||||
|
<arguments>
|
||||||
|
<integer value="65" />
|
||||||
|
</arguments>
|
||||||
|
</call>
|
||||||
|
```
|
||||||
|
|
||||||
|
## FROM-ASCII
|
||||||
|
|
||||||
|
Converts a one-character string into its ASCII integer value
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
|
||||||
|
- `string` character to convert
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
`integer` ASCII value
|
||||||
|
|
||||||
|
### Minimal example
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<call function="from-ascii">
|
||||||
|
<arguments>
|
||||||
|
<string value="a" />
|
||||||
|
</arguments>
|
||||||
|
</call>
|
||||||
|
```
|
||||||
|
|
||||||
|
## GET-ARGS
|
||||||
|
|
||||||
|
Returns an array of arguments passed to the program
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
|
||||||
|
Nothing
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
`array` of strings
|
||||||
|
|
||||||
|
### Minimal example
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<call function="get-args">
|
||||||
|
<arguments />
|
||||||
|
</call>
|
||||||
|
```
|
||||||
|
|
||||||
|
## WRITE-FILE
|
||||||
|
|
||||||
|
Writes a string to a file, optionally appending
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
|
||||||
|
- `string` filename
|
||||||
|
- `string` to write
|
||||||
|
- `any` falsy to replace, truthy to append
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
Nothing
|
||||||
|
|
||||||
|
### Minimal example
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<call function="write-file">
|
||||||
|
<arguments>
|
||||||
|
<string value="file.txt" />
|
||||||
|
<string value="Hello world!" />
|
||||||
|
<integer value="0" />
|
||||||
|
</arguments>
|
||||||
|
</call>
|
||||||
|
```
|
||||||
|
|
||||||
|
## READ-FILE
|
||||||
|
|
||||||
|
Reads a file into a string
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
|
||||||
|
- `string` filename
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
- `string` file contents
|
||||||
|
|
||||||
|
### Minimal example
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<call function="read-file">
|
||||||
|
<arguments>
|
||||||
|
<string value="file.txt" />
|
||||||
|
</arguments>
|
||||||
|
</call>
|
||||||
|
```
|
||||||
196
doc/syntax.md
Normal file
196
doc/syntax.md
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
# PL/XML Syntax
|
||||||
|
|
||||||
|
Programs that do not respect this syntax may (and probably
|
||||||
|
will) still work, but with no guarantee.
|
||||||
|
|
||||||
|
## McKeeman Form
|
||||||
|
|
||||||
|
```
|
||||||
|
plxml
|
||||||
|
program
|
||||||
|
|
||||||
|
program
|
||||||
|
"<program name=" tag ">" functions main functions "</program>"
|
||||||
|
|
||||||
|
tag
|
||||||
|
'"' characters '"'
|
||||||
|
|
||||||
|
characters
|
||||||
|
''
|
||||||
|
character characters
|
||||||
|
|
||||||
|
character
|
||||||
|
'0020' . '10FFFF' - '"' - '0027' - '<' - '>' - '&'
|
||||||
|
'&' escapes ';'
|
||||||
|
|
||||||
|
ws
|
||||||
|
""
|
||||||
|
'0020' ws
|
||||||
|
'000A' ws
|
||||||
|
'000D' ws
|
||||||
|
'0009' ws
|
||||||
|
|
||||||
|
escapes
|
||||||
|
"quot"
|
||||||
|
"apos"
|
||||||
|
"lt"
|
||||||
|
"gt"
|
||||||
|
"amp"
|
||||||
|
|
||||||
|
main
|
||||||
|
ws "<main>" instructions "</main>" ws
|
||||||
|
|
||||||
|
functions
|
||||||
|
""
|
||||||
|
function functions
|
||||||
|
|
||||||
|
function
|
||||||
|
ws "<function name=" tag ">" arguments body "</function>" ws
|
||||||
|
|
||||||
|
arguments
|
||||||
|
ws "<arguments>" arguments "</arguments>" ws
|
||||||
|
|
||||||
|
_arguments
|
||||||
|
""
|
||||||
|
argument _arguments
|
||||||
|
|
||||||
|
argument
|
||||||
|
ws "<argument name=" tag "/>" ws
|
||||||
|
|
||||||
|
body
|
||||||
|
ws "<body>" instructions "</body>" ws
|
||||||
|
|
||||||
|
instructions
|
||||||
|
ws "" ws
|
||||||
|
instruction instructions
|
||||||
|
|
||||||
|
instruction
|
||||||
|
ws _instruction ws
|
||||||
|
|
||||||
|
_instruction
|
||||||
|
value
|
||||||
|
assign
|
||||||
|
integer
|
||||||
|
real
|
||||||
|
string
|
||||||
|
array
|
||||||
|
add
|
||||||
|
subtract
|
||||||
|
multiply
|
||||||
|
divide
|
||||||
|
and
|
||||||
|
or
|
||||||
|
not
|
||||||
|
equal
|
||||||
|
greater
|
||||||
|
lower
|
||||||
|
call
|
||||||
|
return
|
||||||
|
if
|
||||||
|
for
|
||||||
|
each
|
||||||
|
while
|
||||||
|
handle
|
||||||
|
|
||||||
|
value
|
||||||
|
"<value variable=" tag "/>"
|
||||||
|
|
||||||
|
assign
|
||||||
|
"<assign variable=" tag ">" instruction "</assign>"
|
||||||
|
|
||||||
|
integer
|
||||||
|
"<integer value=" tag "/>"
|
||||||
|
"<integer>" instruction "</integer>"
|
||||||
|
|
||||||
|
real
|
||||||
|
"<real value=" tag "/>"
|
||||||
|
"<real>" instruction "</real>"
|
||||||
|
|
||||||
|
string
|
||||||
|
"<string value=" tag "/>"
|
||||||
|
"<string>" instruction "</string>"
|
||||||
|
|
||||||
|
array
|
||||||
|
"<array>" instructions "</array>"
|
||||||
|
"<array />"
|
||||||
|
|
||||||
|
add
|
||||||
|
"<add>" instructions "</add>"
|
||||||
|
|
||||||
|
subtract
|
||||||
|
"<subtract>" instruction instructions "</subtract>"
|
||||||
|
|
||||||
|
multiply
|
||||||
|
"<multiply>" instructions "</multiply>"
|
||||||
|
|
||||||
|
divide
|
||||||
|
"<divide>" instruction instructions "</divide>"
|
||||||
|
|
||||||
|
and
|
||||||
|
"<and>" instruction instructions "</and>"
|
||||||
|
|
||||||
|
or
|
||||||
|
"<or>" instruction instructions "</or>"
|
||||||
|
|
||||||
|
not
|
||||||
|
"<not>" instruction "</not>"
|
||||||
|
|
||||||
|
equal
|
||||||
|
"<equal>" instruction instruction "</equal>"
|
||||||
|
|
||||||
|
greater
|
||||||
|
"<greater>" instruction instruction "</greater>"
|
||||||
|
|
||||||
|
lower
|
||||||
|
"<lower>" instruction instruction "</lower>"
|
||||||
|
|
||||||
|
call
|
||||||
|
"<call function=" tag ">" call_arguments "</call>"
|
||||||
|
"<call>" instruction call_arguments ws "</call>"
|
||||||
|
|
||||||
|
call_arguments
|
||||||
|
ws "<arguments>" instructions "</arguments>" ws
|
||||||
|
|
||||||
|
return
|
||||||
|
"<return>" instruction "</return>"
|
||||||
|
|
||||||
|
if
|
||||||
|
"<if>" instruction then "</if>"
|
||||||
|
"<if>" instruction then else "</if>"
|
||||||
|
|
||||||
|
then
|
||||||
|
ws "<then>" instructions "</then>" ws
|
||||||
|
|
||||||
|
else
|
||||||
|
ws "<else>" instructions "</else>" ws
|
||||||
|
|
||||||
|
each
|
||||||
|
"<each variable=" tag ">" instruction do "</each>"
|
||||||
|
|
||||||
|
while
|
||||||
|
"<while>" instruction do "</while>"
|
||||||
|
|
||||||
|
for
|
||||||
|
"<for variable=" tag ">" ws from to step do "</for>"
|
||||||
|
|
||||||
|
from
|
||||||
|
ws "<from>" instruction "</from>" ws
|
||||||
|
|
||||||
|
to
|
||||||
|
ws "<to>" instruction "</to>" ws
|
||||||
|
|
||||||
|
step
|
||||||
|
ws "<step>" instruction "</step>" ws
|
||||||
|
|
||||||
|
do
|
||||||
|
ws "<do>" instructions "</do>" ws
|
||||||
|
|
||||||
|
handle
|
||||||
|
ws "<handle>" try catch "</handle>" ws
|
||||||
|
|
||||||
|
try
|
||||||
|
ws "<try>" instructions "</try>" ws
|
||||||
|
|
||||||
|
catch
|
||||||
|
ws "<catch variable=" tag ">" instructions "</catch>" ws
|
||||||
|
```
|
||||||
30
sample/cp.pl.xml
Normal file
30
sample/cp.pl.xml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<program name="files">
|
||||||
|
<main>
|
||||||
|
<assign variable="args">
|
||||||
|
<call function="get-args">
|
||||||
|
<arguments />
|
||||||
|
</call>
|
||||||
|
</assign>
|
||||||
|
<call function="write-file">
|
||||||
|
<arguments>
|
||||||
|
<call function="array-get">
|
||||||
|
<arguments>
|
||||||
|
<value variable="args" />
|
||||||
|
<integer value="2" />
|
||||||
|
</arguments>
|
||||||
|
</call>
|
||||||
|
<call function="read-file">
|
||||||
|
<arguments>
|
||||||
|
<call function="array-get">
|
||||||
|
<arguments>
|
||||||
|
<value variable="args" />
|
||||||
|
<integer value="1" />
|
||||||
|
</arguments>
|
||||||
|
</call>
|
||||||
|
</arguments>
|
||||||
|
</call>
|
||||||
|
<integer value="0" />
|
||||||
|
</arguments>
|
||||||
|
</call>
|
||||||
|
</main>
|
||||||
|
</program>
|
||||||
26
sample/handle.pl.xml
Normal file
26
sample/handle.pl.xml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<program name="handle">
|
||||||
|
<main>
|
||||||
|
<handle>
|
||||||
|
<try>
|
||||||
|
<integer value="non" />
|
||||||
|
<call function="print-line">
|
||||||
|
<arguments>
|
||||||
|
<string value="there is no bug" />
|
||||||
|
</arguments>
|
||||||
|
</call>
|
||||||
|
</try>
|
||||||
|
<catch variable="err">
|
||||||
|
<call function="print-line">
|
||||||
|
<arguments>
|
||||||
|
<string value="oh no there is a bug" />
|
||||||
|
</arguments>
|
||||||
|
</call>
|
||||||
|
<call function="print-line">
|
||||||
|
<arguments>
|
||||||
|
<value variable="err" />
|
||||||
|
</arguments>
|
||||||
|
</call>
|
||||||
|
</catch>
|
||||||
|
</handle>
|
||||||
|
</main>
|
||||||
|
</program>
|
||||||
@@ -7,14 +7,14 @@
|
|||||||
</call>
|
</call>
|
||||||
<assign variable="arr">
|
<assign variable="arr">
|
||||||
<array>
|
<array>
|
||||||
<float value="0.1" />
|
<real value="0.1" />
|
||||||
</array>
|
</array>
|
||||||
</assign>
|
</assign>
|
||||||
<call function="array-set">
|
<call function="array-set">
|
||||||
<arguments>
|
<arguments>
|
||||||
<value variable="arr" />
|
<value variable="arr" />
|
||||||
<integer value="0" />
|
<integer value="0" />
|
||||||
<float value="0.5" />
|
<real value="0.5" />
|
||||||
</arguments>
|
</arguments>
|
||||||
</call>
|
</call>
|
||||||
<each variable="v">
|
<each variable="v">
|
||||||
|
|||||||
6
src/bin/compiler.rs
Normal file
6
src/bin/compiler.rs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
fn main() {
|
||||||
|
let args: Vec<String> = std::env::args().collect();
|
||||||
|
if let Err(e) = plxml::compile_file(&args[1]) {
|
||||||
|
eprintln!("Error occurred: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/bin/interpreter.rs
Normal file
6
src/bin/interpreter.rs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
fn main() {
|
||||||
|
let args: Vec<String> = std::env::args().collect();
|
||||||
|
if let Err(e) = plxml::run_file(&args[1]) {
|
||||||
|
eprintln!("Error occurred: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
42
src/compiler/compile.rs
Normal file
42
src/compiler/compile.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
use roxmltree::Document;
|
||||||
|
|
||||||
|
use crate::error::{InvalidProgram, MissingChild};
|
||||||
|
use crate::instruction::Instruction;
|
||||||
|
use crate::util;
|
||||||
|
|
||||||
|
pub fn compile_file(filename: &str) -> Result<(), Box<dyn Error>> {
|
||||||
|
let contents = fs::read_to_string(filename)?;
|
||||||
|
compile(contents)
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Compile {
|
||||||
|
fn compile(&self);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Compile for Instruction {
|
||||||
|
fn compile(&self) {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compile(program: String) -> Result<(), Box<dyn Error>> {
|
||||||
|
let doc = Document::parse(&program)?;
|
||||||
|
let root = doc.root();
|
||||||
|
|
||||||
|
let main = root
|
||||||
|
.first_element_child()
|
||||||
|
.ok_or(InvalidProgram)?
|
||||||
|
.children()
|
||||||
|
.find(|node| util::tag_name(&node) == "main")
|
||||||
|
.ok_or(MissingChild("program", "main"))?;
|
||||||
|
let main_ast = Instruction::from_children(main)?;
|
||||||
|
|
||||||
|
for ins in main_ast {
|
||||||
|
ins.compile();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
1
src/compiler/mod.rs
Normal file
1
src/compiler/mod.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub mod compile;
|
||||||
21
src/error.rs
21
src/error.rs
@@ -17,18 +17,22 @@ pub struct MissingAttribute(pub &'static str, pub &'static str);
|
|||||||
|
|
||||||
impl fmt::Display for MissingAttribute {
|
impl fmt::Display for MissingAttribute {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "missing '{}' child in '{}' node", self.1, self.0)
|
write!(f, "missing '{}' attribute in '{}' node", self.1, self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error for MissingAttribute {}
|
impl Error for MissingAttribute {}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct BadArgumentCount(pub &'static str, pub usize);
|
pub struct BadArgumentCount(pub &'static str, pub usize, pub usize);
|
||||||
|
|
||||||
impl fmt::Display for BadArgumentCount {
|
impl fmt::Display for BadArgumentCount {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "bad argument count ({}) in call to '{}'", self.1, self.0)
|
write!(
|
||||||
|
f,
|
||||||
|
"bad argument count ({}, expected {}) in call to '{}'",
|
||||||
|
self.1, self.2, self.0
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,3 +118,14 @@ impl fmt::Display for UnknownVariable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Error for UnknownVariable {}
|
impl Error for UnknownVariable {}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct InaccessibleFile(pub String);
|
||||||
|
|
||||||
|
impl fmt::Display for InaccessibleFile {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "unable to access file '{}'", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for InaccessibleFile {}
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
use super::error::{
|
use super::error::{BadChildCount, MissingAttribute, MissingChild};
|
||||||
BadChildCount, IncompatibleValues, InvalidValue, MissingAttribute, MissingChild,
|
use crate::util;
|
||||||
UnknownVariable,
|
|
||||||
};
|
|
||||||
use super::{util, Context, Value};
|
|
||||||
use roxmltree::Node;
|
use roxmltree::Node;
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Instruction {
|
pub enum Instruction {
|
||||||
@@ -14,8 +9,8 @@ pub enum Instruction {
|
|||||||
Assign(String, Box<Instruction>),
|
Assign(String, Box<Instruction>),
|
||||||
Integer(String),
|
Integer(String),
|
||||||
IntegerCast(Box<Instruction>),
|
IntegerCast(Box<Instruction>),
|
||||||
Float(String),
|
Real(String),
|
||||||
FloatCast(Box<Instruction>),
|
RealCast(Box<Instruction>),
|
||||||
String(String),
|
String(String),
|
||||||
StringCast(Box<Instruction>),
|
StringCast(Box<Instruction>),
|
||||||
Array(Vec<Instruction>),
|
Array(Vec<Instruction>),
|
||||||
@@ -43,6 +38,7 @@ pub enum Instruction {
|
|||||||
},
|
},
|
||||||
Each(String, Box<Instruction>, Vec<Instruction>),
|
Each(String, Box<Instruction>, Vec<Instruction>),
|
||||||
While(Box<Instruction>, Vec<Instruction>),
|
While(Box<Instruction>, Vec<Instruction>),
|
||||||
|
Handle(Vec<Instruction>, Vec<Instruction>, String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Instruction {
|
impl Instruction {
|
||||||
@@ -72,13 +68,13 @@ impl Instruction {
|
|||||||
Err(MissingAttribute("integer", "value"))?
|
Err(MissingAttribute("integer", "value"))?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"float" => {
|
"real" => {
|
||||||
if let Some(v) = node.attribute("value") {
|
if let Some(v) = node.attribute("value") {
|
||||||
Instruction::Float(String::from(v))
|
Instruction::Real(String::from(v))
|
||||||
} else if let Some(n) = node.first_element_child() {
|
} else if let Some(n) = node.first_element_child() {
|
||||||
Instruction::FloatCast(Box::new(Instruction::new(n)?))
|
Instruction::RealCast(Box::new(Instruction::new(n)?))
|
||||||
} else {
|
} else {
|
||||||
Err(MissingAttribute("float", "value"))?
|
Err(MissingAttribute("real", "value"))?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"string" => {
|
"string" => {
|
||||||
@@ -230,6 +226,19 @@ impl Instruction {
|
|||||||
util::find_node(&node, "do").ok_or(MissingChild("while", "from"))?,
|
util::find_node(&node, "do").ok_or(MissingChild("while", "from"))?,
|
||||||
)?,
|
)?,
|
||||||
),
|
),
|
||||||
|
"handle" => Instruction::Handle(
|
||||||
|
Instruction::from_children(
|
||||||
|
util::find_node(&node, "try").ok_or(MissingChild("handle", "try"))?,
|
||||||
|
)?,
|
||||||
|
Instruction::from_children(
|
||||||
|
util::find_node(&node, "catch").ok_or(MissingChild("handle", "try"))?,
|
||||||
|
)?,
|
||||||
|
util::find_node(&node, "catch")
|
||||||
|
.ok_or(MissingChild("handle", "try"))?
|
||||||
|
.attribute("variable")
|
||||||
|
.ok_or(MissingAttribute("catch", "variable"))?
|
||||||
|
.to_string(),
|
||||||
|
),
|
||||||
tag => Err(format!("unknown tag '{}'", tag))?,
|
tag => Err(format!("unknown tag '{}'", tag))?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -240,467 +249,4 @@ impl Instruction {
|
|||||||
.map(Instruction::new)
|
.map(Instruction::new)
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add(vals: Vec<Value>) -> Result<Value, Box<dyn Error>> {
|
|
||||||
if vals.iter().all(|v| matches!(v, Value::Integer(_))) {
|
|
||||||
Ok(Value::Integer(
|
|
||||||
vals.iter()
|
|
||||||
.map(|v| {
|
|
||||||
if let Value::Integer(i) = v {
|
|
||||||
Ok(i)
|
|
||||||
} else {
|
|
||||||
Err(InvalidValue("add"))?
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.sum::<Result<i64, Box<dyn Error>>>()?,
|
|
||||||
))
|
|
||||||
} else if vals
|
|
||||||
.iter()
|
|
||||||
.all(|v| matches!(v, Value::Integer(_)) || matches!(v, Value::Float(_)))
|
|
||||||
{
|
|
||||||
Ok(Value::Float(
|
|
||||||
vals.iter()
|
|
||||||
.map(|v| {
|
|
||||||
if let Value::Integer(i) = v {
|
|
||||||
Ok(*i as f64)
|
|
||||||
} else if let Value::Float(f) = v {
|
|
||||||
Ok(*f)
|
|
||||||
} else {
|
|
||||||
Err(InvalidValue("add"))?
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.sum::<Result<f64, Box<dyn Error>>>()?,
|
|
||||||
))
|
|
||||||
} else if vals.iter().all(|v| {
|
|
||||||
matches!(v, Value::Integer(_))
|
|
||||||
|| matches!(v, Value::Float(_))
|
|
||||||
|| matches!(v, Value::String(_))
|
|
||||||
}) {
|
|
||||||
Ok(Value::String(
|
|
||||||
vals.iter()
|
|
||||||
.map(|v| {
|
|
||||||
Ok(if let Value::String(s) = v {
|
|
||||||
s.to_string()
|
|
||||||
} else if let Value::Integer(i) = v {
|
|
||||||
i.to_string()
|
|
||||||
} else if let Value::Float(f) = v {
|
|
||||||
f.to_string()
|
|
||||||
} else {
|
|
||||||
Err(InvalidValue("add"))?
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<String>, Box<dyn Error>>>()?
|
|
||||||
.join(""),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
Err(InvalidValue("add"))?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn subtract(vals: Vec<Value>) -> Result<Value, Box<dyn Error>> {
|
|
||||||
Ok(if vals.iter().all(|v| matches!(v, Value::Integer(_))) {
|
|
||||||
let first = match vals.first().ok_or(BadChildCount("subtract", 0usize))? {
|
|
||||||
Value::Integer(i) => i,
|
|
||||||
_ => Err(InvalidValue("subtract"))?,
|
|
||||||
};
|
|
||||||
Value::Integer(
|
|
||||||
*first
|
|
||||||
- vals
|
|
||||||
.iter()
|
|
||||||
.skip(1)
|
|
||||||
.map(|v| {
|
|
||||||
if let Value::Integer(i) = v {
|
|
||||||
Ok(i)
|
|
||||||
} else {
|
|
||||||
Err(InvalidValue("subtract"))?
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.sum::<Result<i64, Box<dyn Error>>>()?,
|
|
||||||
)
|
|
||||||
} else if vals
|
|
||||||
.iter()
|
|
||||||
.all(|v| matches!(v, Value::Integer(_)) || matches!(v, Value::Float(_)))
|
|
||||||
{
|
|
||||||
let first = match vals.first().ok_or(BadChildCount("subtract", 0usize))? {
|
|
||||||
Value::Integer(v) => *v as f64,
|
|
||||||
Value::Float(v) => *v,
|
|
||||||
_ => Err(InvalidValue("subtract"))?,
|
|
||||||
};
|
|
||||||
Value::Float(
|
|
||||||
first
|
|
||||||
- vals
|
|
||||||
.iter()
|
|
||||||
.skip(1)
|
|
||||||
.map(|val| {
|
|
||||||
Ok(match val {
|
|
||||||
Value::Integer(v) => *v as f64,
|
|
||||||
Value::Float(v) => *v,
|
|
||||||
_ => Err(InvalidValue("subtract"))?,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.sum::<Result<f64, Box<dyn Error>>>()?,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Err(InvalidValue("subtract"))?
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn multiply(vals: Vec<Value>) -> Result<Value, Box<dyn Error>> {
|
|
||||||
if vals.iter().all(|v| matches!(v, Value::Integer(_))) {
|
|
||||||
Ok(Value::Integer(
|
|
||||||
vals.iter()
|
|
||||||
.map(|v| {
|
|
||||||
if let Value::Integer(i) = v {
|
|
||||||
Ok(i)
|
|
||||||
} else {
|
|
||||||
Err(InvalidValue("multiply"))?
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.product::<Result<i64, Box<dyn Error>>>()?,
|
|
||||||
))
|
|
||||||
} else if vals
|
|
||||||
.iter()
|
|
||||||
.all(|v| matches!(v, Value::Integer(_)) || matches!(v, Value::Float(_)))
|
|
||||||
{
|
|
||||||
Ok(Value::Float(
|
|
||||||
vals.iter()
|
|
||||||
.map(|val| {
|
|
||||||
Ok(match val {
|
|
||||||
Value::Integer(v) => *v as f64,
|
|
||||||
Value::Float(v) => *v,
|
|
||||||
_ => Err(InvalidValue("multiply"))?,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.product::<Result<f64, Box<dyn Error>>>()?,
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
Err(InvalidValue("multiply"))?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn divide(vals: Vec<Value>) -> Result<Value, Box<dyn Error>> {
|
|
||||||
if vals
|
|
||||||
.iter()
|
|
||||||
.all(|v| matches!(v, Value::Integer(_)) || matches!(v, Value::Float(_)))
|
|
||||||
{
|
|
||||||
let first = match vals.first().ok_or(BadChildCount("divide", 0))? {
|
|
||||||
Value::Integer(v) => *v as f64,
|
|
||||||
Value::Float(v) => *v,
|
|
||||||
_ => Err(InvalidValue("divide"))?,
|
|
||||||
};
|
|
||||||
Ok(Value::Float(
|
|
||||||
first
|
|
||||||
* vals
|
|
||||||
.iter()
|
|
||||||
.skip(1)
|
|
||||||
.map(|val| {
|
|
||||||
Ok(match val {
|
|
||||||
Value::Integer(v) => 1.0 / (*v as f64),
|
|
||||||
Value::Float(v) => 1.0 / *v,
|
|
||||||
_ => Err(InvalidValue("divide"))?,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.product::<Result<f64, Box<dyn Error>>>()?,
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
Err(InvalidValue("divide"))?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn and(vals: Vec<Value>) -> Value {
|
|
||||||
Value::Integer(if vals.iter().all(Value::to_bool) {
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn or(vals: Vec<Value>) -> Value {
|
|
||||||
Value::Integer(if vals.iter().any(Value::to_bool) {
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compare(v1: Value, v2: Value) -> Result<i64, Box<dyn Error>> {
|
|
||||||
use std::cmp::Ordering;
|
|
||||||
match v1 {
|
|
||||||
Value::Integer(i1) => match v2 {
|
|
||||||
Value::Integer(i2) => Ok(i1 - i2),
|
|
||||||
Value::Float(f2) => Ok(
|
|
||||||
match (i1 as f64).partial_cmp(&f2).ok_or(IncompatibleValues)? {
|
|
||||||
Ordering::Less => -1,
|
|
||||||
Ordering::Equal => 0,
|
|
||||||
Ordering::Greater => 1,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
_ => Err(IncompatibleValues)?,
|
|
||||||
},
|
|
||||||
Value::Float(f1) => match v2 {
|
|
||||||
Value::Integer(i2) => Ok(
|
|
||||||
match f1.partial_cmp(&(i2 as f64)).ok_or(IncompatibleValues)? {
|
|
||||||
Ordering::Less => -1,
|
|
||||||
Ordering::Equal => 0,
|
|
||||||
Ordering::Greater => 1,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Value::Float(f2) => Ok(match f1.partial_cmp(&f2).ok_or(IncompatibleValues)? {
|
|
||||||
Ordering::Less => -1,
|
|
||||||
Ordering::Equal => 0,
|
|
||||||
Ordering::Greater => 1,
|
|
||||||
}),
|
|
||||||
_ => Err(IncompatibleValues)?,
|
|
||||||
},
|
|
||||||
Value::String(s1) => {
|
|
||||||
if let Value::String(s2) = v2 {
|
|
||||||
Ok(match s1.cmp(&s2) {
|
|
||||||
Ordering::Less => -1,
|
|
||||||
Ordering::Equal => 0,
|
|
||||||
Ordering::Greater => 1,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Err(IncompatibleValues)?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => Err(IncompatibleValues)?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_all(
|
|
||||||
ins: &Vec<Instruction>,
|
|
||||||
ctx: &mut Context,
|
|
||||||
globals: &Context,
|
|
||||||
) -> Result<Option<Vec<Value>>, Box<dyn Error>> {
|
|
||||||
ins.iter().map(|i| i.run(ctx, globals)).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(
|
|
||||||
&self,
|
|
||||||
ctx: &mut Context,
|
|
||||||
globals: &Context,
|
|
||||||
) -> Result<Option<Value>, Box<dyn Error>> {
|
|
||||||
Ok(if let None = ctx.value(&String::from("__return")) {
|
|
||||||
match self {
|
|
||||||
Instruction::Value(key) => {
|
|
||||||
Some(match ctx.value(key).ok_or(UnknownVariable(key.clone()))? {
|
|
||||||
Value::Array(vecrc) => Value::Array(Rc::clone(vecrc)),
|
|
||||||
val => val.clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Instruction::Assign(key, ins) => {
|
|
||||||
let v = ins.run(ctx, globals)?.ok_or(InvalidValue("assign"))?;
|
|
||||||
ctx.assign(key.clone(), v);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
Instruction::Integer(val) => Some(Value::Integer(val.parse()?)),
|
|
||||||
Instruction::IntegerCast(ins) => Some(Value::Integer(
|
|
||||||
match ins.run(ctx, globals)?.ok_or(InvalidValue("integer"))? {
|
|
||||||
Value::Integer(i) => i,
|
|
||||||
Value::Float(f) => f as i64,
|
|
||||||
Value::String(s) => s.parse()?,
|
|
||||||
_ => Err(InvalidValue("integer"))?,
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
Instruction::Float(val) => Some(Value::Float(val.parse()?)),
|
|
||||||
Instruction::FloatCast(ins) => Some(Value::Float(
|
|
||||||
match ins.run(ctx, globals)?.ok_or(InvalidValue("float"))? {
|
|
||||||
Value::Integer(i) => i as f64,
|
|
||||||
Value::Float(f) => f,
|
|
||||||
Value::String(s) => s.parse()?,
|
|
||||||
_ => Err(InvalidValue("float"))?,
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
Instruction::String(val) => Some(Value::String(val.clone())),
|
|
||||||
Instruction::StringCast(ins) => Some(Value::String(
|
|
||||||
match ins.run(ctx, globals)?.ok_or(InvalidValue("string"))? {
|
|
||||||
Value::Integer(i) => i.to_string(),
|
|
||||||
Value::Float(f) => f.to_string(),
|
|
||||||
Value::String(s) => s,
|
|
||||||
_ => Err(InvalidValue("string"))?,
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
Instruction::Array(args) => Some(Value::Array(Rc::new(RefCell::new(
|
|
||||||
Instruction::run_all(args, ctx, globals)?.ok_or(InvalidValue("array"))?,
|
|
||||||
)))),
|
|
||||||
Instruction::Add(args) => {
|
|
||||||
let vals =
|
|
||||||
Instruction::run_all(args, ctx, globals)?.ok_or(InvalidValue("add"))?;
|
|
||||||
Some(Instruction::add(vals)?)
|
|
||||||
}
|
|
||||||
Instruction::Subtract(args) => {
|
|
||||||
let vals = Instruction::run_all(args, ctx, globals)?
|
|
||||||
.ok_or(InvalidValue("subtract"))?;
|
|
||||||
Some(Instruction::subtract(vals)?)
|
|
||||||
}
|
|
||||||
Instruction::Multiply(args) => {
|
|
||||||
let vals = Instruction::run_all(args, ctx, globals)?
|
|
||||||
.ok_or(InvalidValue("multiply"))?;
|
|
||||||
Some(Instruction::multiply(vals)?)
|
|
||||||
}
|
|
||||||
Instruction::Divide(args) => {
|
|
||||||
let vals =
|
|
||||||
Instruction::run_all(args, ctx, globals)?.ok_or(InvalidValue("divide"))?;
|
|
||||||
Some(Instruction::divide(vals)?)
|
|
||||||
}
|
|
||||||
Instruction::And(args) => {
|
|
||||||
let vals =
|
|
||||||
Instruction::run_all(args, ctx, globals)?.ok_or(InvalidValue("and"))?;
|
|
||||||
Some(Instruction::and(vals))
|
|
||||||
}
|
|
||||||
Instruction::Or(args) => {
|
|
||||||
let vals =
|
|
||||||
Instruction::run_all(args, ctx, globals)?.ok_or(InvalidValue("or"))?;
|
|
||||||
Some(Instruction::or(vals))
|
|
||||||
}
|
|
||||||
Instruction::Not(arg) => Some(Value::Integer(
|
|
||||||
if arg.run(ctx, globals)?.ok_or(InvalidValue("not"))?.to_bool() {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
1
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
Instruction::Equal(v1, v2) => Some(Value::Integer(
|
|
||||||
if Instruction::compare(
|
|
||||||
v1.run(ctx, globals)?.ok_or(InvalidValue("equal"))?,
|
|
||||||
v2.run(ctx, globals)?.ok_or(InvalidValue("equal"))?,
|
|
||||||
)? == 0
|
|
||||||
{
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
Instruction::Greater(v1, v2) => Some(Value::Integer(
|
|
||||||
if Instruction::compare(
|
|
||||||
v1.run(ctx, globals)?.ok_or(InvalidValue("greater"))?,
|
|
||||||
v2.run(ctx, globals)?.ok_or(InvalidValue("greater"))?,
|
|
||||||
)? > 0
|
|
||||||
{
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
Instruction::Lower(v1, v2) => Some(Value::Integer(
|
|
||||||
if Instruction::compare(
|
|
||||||
v1.run(ctx, globals)?.ok_or(InvalidValue("lower"))?,
|
|
||||||
v2.run(ctx, globals)?.ok_or(InvalidValue("lower"))?,
|
|
||||||
)? < 0
|
|
||||||
{
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
Instruction::Call(fct_ins, args) => {
|
|
||||||
let vals =
|
|
||||||
Instruction::run_all(args, ctx, globals)?.ok_or(InvalidValue("call"))?;
|
|
||||||
let fct_val = fct_ins.run(ctx, globals)?.ok_or(InvalidValue("call"))?;
|
|
||||||
if let Value::Function(f) = fct_val {
|
|
||||||
let mut fun_ctx = Context::new(Some(globals));
|
|
||||||
f.run(vals, &mut fun_ctx, globals)?
|
|
||||||
} else if let Value::StdFunction(f) = fct_val {
|
|
||||||
f(vals)?
|
|
||||||
} else {
|
|
||||||
Err(InvalidValue("call"))?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Instruction::CallNamed(fct_name, args) => {
|
|
||||||
let vals: Vec<Value> =
|
|
||||||
Instruction::run_all(args, ctx, globals)?.ok_or(InvalidValue("call"))?;
|
|
||||||
let fct_val = ctx
|
|
||||||
.value(&fct_name)
|
|
||||||
.ok_or(UnknownVariable(fct_name.clone()))?;
|
|
||||||
if let Value::Function(f) = fct_val {
|
|
||||||
let mut local = ctx.clone();
|
|
||||||
f.run(vals, &mut local, globals)?
|
|
||||||
} else if let Value::StdFunction(f) = fct_val {
|
|
||||||
f(vals)?
|
|
||||||
} else {
|
|
||||||
Err(InvalidValue("call"))?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Instruction::Return(ins) => {
|
|
||||||
let v = ins.run(ctx, globals)?.ok_or(InvalidValue("return"))?;
|
|
||||||
ctx.assign(String::from("__return"), v);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
Instruction::If(cond, then) => {
|
|
||||||
if cond.run(ctx, globals)?.ok_or(InvalidValue("if"))?.to_bool() {
|
|
||||||
for i in then {
|
|
||||||
i.run(ctx, globals)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
Instruction::IfElse(cond, then, els) => {
|
|
||||||
if cond.run(ctx, globals)?.ok_or(InvalidValue("if"))?.to_bool() {
|
|
||||||
for i in then {
|
|
||||||
i.run(ctx, globals)?;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for i in els {
|
|
||||||
i.run(ctx, globals)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
Instruction::For {
|
|
||||||
variable,
|
|
||||||
from,
|
|
||||||
to,
|
|
||||||
step,
|
|
||||||
body,
|
|
||||||
} => {
|
|
||||||
if let Value::Integer(f) = from.run(ctx, globals)?.ok_or(InvalidValue("for"))? {
|
|
||||||
if let Value::Integer(t) =
|
|
||||||
to.run(ctx, globals)?.ok_or(InvalidValue("for"))?
|
|
||||||
{
|
|
||||||
if let Value::Integer(s) =
|
|
||||||
step.run(ctx, globals)?.ok_or(InvalidValue("for"))?
|
|
||||||
{
|
|
||||||
for i in (f..t).step_by(s.try_into()?) {
|
|
||||||
ctx.assign(variable.clone(), Value::Integer(i));
|
|
||||||
for ins in body {
|
|
||||||
ins.run(ctx, globals)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
Instruction::Each(variable, array_ins, body) => {
|
|
||||||
if let Value::Array(v) =
|
|
||||||
array_ins.run(ctx, globals)?.ok_or(InvalidValue("each"))?
|
|
||||||
{
|
|
||||||
for i in v.borrow().iter() {
|
|
||||||
ctx.assign(variable.clone(), i.clone());
|
|
||||||
for ins in body {
|
|
||||||
ins.run(ctx, globals)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(InvalidValue("each"))?
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
Instruction::While(cond, body) => {
|
|
||||||
while cond
|
|
||||||
.run(ctx, globals)?
|
|
||||||
.ok_or(InvalidValue("while"))?
|
|
||||||
.to_bool()
|
|
||||||
{
|
|
||||||
for ins in body {
|
|
||||||
ins.run(ctx, globals)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use super::value::Value;
|
use crate::interpreter::value::Value;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
4
src/interpreter/mod.rs
Normal file
4
src/interpreter/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
mod context;
|
||||||
|
pub mod run;
|
||||||
|
mod stl;
|
||||||
|
mod value;
|
||||||
538
src/interpreter/run.rs
Normal file
538
src/interpreter/run.rs
Normal file
@@ -0,0 +1,538 @@
|
|||||||
|
use std::{cell::RefCell, error::Error, fs, rc::Rc};
|
||||||
|
|
||||||
|
use roxmltree::Document;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::{
|
||||||
|
BadChildCount, IncompatibleValues, InvalidProgram, InvalidValue, MissingChild,
|
||||||
|
UnknownVariable, Unnamed,
|
||||||
|
},
|
||||||
|
instruction::Instruction,
|
||||||
|
interpreter::context::Context,
|
||||||
|
interpreter::stl,
|
||||||
|
interpreter::value::{Function, Value},
|
||||||
|
util,
|
||||||
|
};
|
||||||
|
|
||||||
|
trait RunUtil {
|
||||||
|
fn add(vals: Vec<Value>) -> Result<Value, Box<dyn Error>>;
|
||||||
|
fn subtract(vals: Vec<Value>) -> Result<Value, Box<dyn Error>>;
|
||||||
|
fn multiply(vals: Vec<Value>) -> Result<Value, Box<dyn Error>>;
|
||||||
|
fn divide(vals: Vec<Value>) -> Result<Value, Box<dyn Error>>;
|
||||||
|
fn compare(v1: Value, v2: Value) -> Result<i64, Box<dyn Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RunUtil for Instruction {
|
||||||
|
fn add(vals: Vec<Value>) -> Result<Value, Box<dyn Error>> {
|
||||||
|
if vals.iter().all(|v| matches!(v, Value::Integer(_))) {
|
||||||
|
Ok(Value::Integer(
|
||||||
|
vals.iter()
|
||||||
|
.map(|v| {
|
||||||
|
if let Value::Integer(i) = v {
|
||||||
|
Ok(i)
|
||||||
|
} else {
|
||||||
|
Err(InvalidValue("add"))?
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.sum::<Result<i64, Box<dyn Error>>>()?,
|
||||||
|
))
|
||||||
|
} else if vals
|
||||||
|
.iter()
|
||||||
|
.all(|v| matches!(v, Value::Integer(_)) || matches!(v, Value::Real(_)))
|
||||||
|
{
|
||||||
|
Ok(Value::Real(
|
||||||
|
vals.iter()
|
||||||
|
.map(|v| {
|
||||||
|
if let Value::Integer(i) = v {
|
||||||
|
Ok(*i as f64)
|
||||||
|
} else if let Value::Real(f) = v {
|
||||||
|
Ok(*f)
|
||||||
|
} else {
|
||||||
|
Err(InvalidValue("add"))?
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.sum::<Result<f64, Box<dyn Error>>>()?,
|
||||||
|
))
|
||||||
|
} else if vals.iter().all(|v| {
|
||||||
|
matches!(v, Value::Integer(_))
|
||||||
|
|| matches!(v, Value::Real(_))
|
||||||
|
|| matches!(v, Value::String(_))
|
||||||
|
}) {
|
||||||
|
Ok(Value::String(
|
||||||
|
vals.iter()
|
||||||
|
.map(|v| {
|
||||||
|
Ok(if let Value::String(s) = v {
|
||||||
|
s.to_string()
|
||||||
|
} else if let Value::Integer(i) = v {
|
||||||
|
i.to_string()
|
||||||
|
} else if let Value::Real(f) = v {
|
||||||
|
f.to_string()
|
||||||
|
} else {
|
||||||
|
Err(InvalidValue("add"))?
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<String>, Box<dyn Error>>>()?
|
||||||
|
.join(""),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Err(InvalidValue("add"))?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn subtract(vals: Vec<Value>) -> Result<Value, Box<dyn Error>> {
|
||||||
|
Ok(if vals.iter().all(|v| matches!(v, Value::Integer(_))) {
|
||||||
|
let first = match vals.first().ok_or(BadChildCount("subtract", 0usize))? {
|
||||||
|
Value::Integer(i) => i,
|
||||||
|
_ => Err(InvalidValue("subtract"))?,
|
||||||
|
};
|
||||||
|
Value::Integer(
|
||||||
|
*first
|
||||||
|
- vals
|
||||||
|
.iter()
|
||||||
|
.skip(1)
|
||||||
|
.map(|v| {
|
||||||
|
if let Value::Integer(i) = v {
|
||||||
|
Ok(i)
|
||||||
|
} else {
|
||||||
|
Err(InvalidValue("subtract"))?
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.sum::<Result<i64, Box<dyn Error>>>()?,
|
||||||
|
)
|
||||||
|
} else if vals
|
||||||
|
.iter()
|
||||||
|
.all(|v| matches!(v, Value::Integer(_)) || matches!(v, Value::Real(_)))
|
||||||
|
{
|
||||||
|
let first = match vals.first().ok_or(BadChildCount("subtract", 0usize))? {
|
||||||
|
Value::Integer(v) => *v as f64,
|
||||||
|
Value::Real(v) => *v,
|
||||||
|
_ => Err(InvalidValue("subtract"))?,
|
||||||
|
};
|
||||||
|
Value::Real(
|
||||||
|
first
|
||||||
|
- vals
|
||||||
|
.iter()
|
||||||
|
.skip(1)
|
||||||
|
.map(|val| {
|
||||||
|
Ok(match val {
|
||||||
|
Value::Integer(v) => *v as f64,
|
||||||
|
Value::Real(v) => *v,
|
||||||
|
_ => Err(InvalidValue("subtract"))?,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.sum::<Result<f64, Box<dyn Error>>>()?,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Err(InvalidValue("subtract"))?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn multiply(vals: Vec<Value>) -> Result<Value, Box<dyn Error>> {
|
||||||
|
if vals.iter().all(|v| matches!(v, Value::Integer(_))) {
|
||||||
|
Ok(Value::Integer(
|
||||||
|
vals.iter()
|
||||||
|
.map(|v| {
|
||||||
|
if let Value::Integer(i) = v {
|
||||||
|
Ok(i)
|
||||||
|
} else {
|
||||||
|
Err(InvalidValue("multiply"))?
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.product::<Result<i64, Box<dyn Error>>>()?,
|
||||||
|
))
|
||||||
|
} else if vals
|
||||||
|
.iter()
|
||||||
|
.all(|v| matches!(v, Value::Integer(_)) || matches!(v, Value::Real(_)))
|
||||||
|
{
|
||||||
|
Ok(Value::Real(
|
||||||
|
vals.iter()
|
||||||
|
.map(|val| {
|
||||||
|
Ok(match val {
|
||||||
|
Value::Integer(v) => *v as f64,
|
||||||
|
Value::Real(v) => *v,
|
||||||
|
_ => Err(InvalidValue("multiply"))?,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.product::<Result<f64, Box<dyn Error>>>()?,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Err(InvalidValue("multiply"))?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn divide(vals: Vec<Value>) -> Result<Value, Box<dyn Error>> {
|
||||||
|
if vals
|
||||||
|
.iter()
|
||||||
|
.all(|v| matches!(v, Value::Integer(_)) || matches!(v, Value::Real(_)))
|
||||||
|
{
|
||||||
|
let first = match vals.first().ok_or(BadChildCount("divide", 0))? {
|
||||||
|
Value::Integer(v) => *v as f64,
|
||||||
|
Value::Real(v) => *v,
|
||||||
|
_ => Err(InvalidValue("divide"))?,
|
||||||
|
};
|
||||||
|
Ok(Value::Real(
|
||||||
|
first
|
||||||
|
* vals
|
||||||
|
.iter()
|
||||||
|
.skip(1)
|
||||||
|
.map(|val| {
|
||||||
|
Ok(match val {
|
||||||
|
Value::Integer(v) => 1.0 / (*v as f64),
|
||||||
|
Value::Real(v) => 1.0 / *v,
|
||||||
|
_ => Err(InvalidValue("divide"))?,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.product::<Result<f64, Box<dyn Error>>>()?,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Err(InvalidValue("divide"))?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare(v1: Value, v2: Value) -> Result<i64, Box<dyn Error>> {
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
match v1 {
|
||||||
|
Value::Integer(i1) => match v2 {
|
||||||
|
Value::Integer(i2) => Ok(i1 - i2),
|
||||||
|
Value::Real(f2) => Ok(
|
||||||
|
match (i1 as f64).partial_cmp(&f2).ok_or(IncompatibleValues)? {
|
||||||
|
Ordering::Less => -1,
|
||||||
|
Ordering::Equal => 0,
|
||||||
|
Ordering::Greater => 1,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
_ => Err(IncompatibleValues)?,
|
||||||
|
},
|
||||||
|
Value::Real(f1) => match v2 {
|
||||||
|
Value::Integer(i2) => Ok(
|
||||||
|
match f1.partial_cmp(&(i2 as f64)).ok_or(IncompatibleValues)? {
|
||||||
|
Ordering::Less => -1,
|
||||||
|
Ordering::Equal => 0,
|
||||||
|
Ordering::Greater => 1,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Value::Real(f2) => Ok(match f1.partial_cmp(&f2).ok_or(IncompatibleValues)? {
|
||||||
|
Ordering::Less => -1,
|
||||||
|
Ordering::Equal => 0,
|
||||||
|
Ordering::Greater => 1,
|
||||||
|
}),
|
||||||
|
_ => Err(IncompatibleValues)?,
|
||||||
|
},
|
||||||
|
Value::String(s1) => {
|
||||||
|
if let Value::String(s2) = v2 {
|
||||||
|
Ok(match s1.cmp(&s2) {
|
||||||
|
Ordering::Less => -1,
|
||||||
|
Ordering::Equal => 0,
|
||||||
|
Ordering::Greater => 1,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(IncompatibleValues)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err(IncompatibleValues)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Run {
|
||||||
|
fn run(&self, ctx: &mut Context, globals: &Context) -> Result<Option<Value>, Box<dyn Error>>;
|
||||||
|
fn run_all(
|
||||||
|
ins: &Vec<Instruction>,
|
||||||
|
ctx: &mut Context,
|
||||||
|
globals: &Context,
|
||||||
|
) -> Result<Option<Vec<Value>>, Box<dyn Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Run for Instruction {
|
||||||
|
fn run_all(
|
||||||
|
ins: &Vec<Instruction>,
|
||||||
|
ctx: &mut Context,
|
||||||
|
globals: &Context,
|
||||||
|
) -> Result<Option<Vec<Value>>, Box<dyn Error>> {
|
||||||
|
ins.iter().map(|i| i.run(ctx, globals)).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, ctx: &mut Context, globals: &Context) -> Result<Option<Value>, Box<dyn Error>> {
|
||||||
|
Ok(if let None = ctx.value(&String::from("__return")) {
|
||||||
|
match self {
|
||||||
|
Instruction::Value(key) => {
|
||||||
|
Some(match ctx.value(key).ok_or(UnknownVariable(key.clone()))? {
|
||||||
|
Value::Array(vecrc) => Value::Array(Rc::clone(&vecrc)),
|
||||||
|
val => val.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Instruction::Assign(key, ins) => {
|
||||||
|
let v = ins.run(ctx, globals)?.ok_or(InvalidValue("assign"))?;
|
||||||
|
ctx.assign(key.clone(), v);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Instruction::Integer(val) => Some(Value::Integer(val.parse()?)),
|
||||||
|
Instruction::IntegerCast(ins) => Some(Value::Integer(
|
||||||
|
match ins.run(ctx, globals)?.ok_or(InvalidValue("integer"))? {
|
||||||
|
Value::Integer(i) => i,
|
||||||
|
Value::Real(f) => f as i64,
|
||||||
|
Value::String(s) => s.parse()?,
|
||||||
|
_ => Err(InvalidValue("integer"))?,
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
Instruction::Real(val) => Some(Value::Real(val.parse()?)),
|
||||||
|
Instruction::RealCast(ins) => Some(Value::Real(
|
||||||
|
match ins.run(ctx, globals)?.ok_or(InvalidValue("real"))? {
|
||||||
|
Value::Integer(i) => i as f64,
|
||||||
|
Value::Real(f) => f,
|
||||||
|
Value::String(s) => s.parse()?,
|
||||||
|
_ => Err(InvalidValue("real"))?,
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
Instruction::String(val) => Some(Value::String(val.clone())),
|
||||||
|
Instruction::StringCast(ins) => Some(Value::String(
|
||||||
|
match ins.run(ctx, globals)?.ok_or(InvalidValue("string"))? {
|
||||||
|
Value::Integer(i) => i.to_string(),
|
||||||
|
Value::Real(f) => f.to_string(),
|
||||||
|
Value::String(s) => s,
|
||||||
|
_ => Err(InvalidValue("string"))?,
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
Instruction::Array(args) => Some(Value::Array(Rc::new(RefCell::new(
|
||||||
|
Instruction::run_all(args, ctx, globals)?.ok_or(InvalidValue("array"))?,
|
||||||
|
)))),
|
||||||
|
Instruction::Add(args) => {
|
||||||
|
let vals =
|
||||||
|
Instruction::run_all(args, ctx, globals)?.ok_or(InvalidValue("add"))?;
|
||||||
|
Some(Instruction::add(vals)?)
|
||||||
|
}
|
||||||
|
Instruction::Subtract(args) => {
|
||||||
|
let vals = Instruction::run_all(args, ctx, globals)?
|
||||||
|
.ok_or(InvalidValue("subtract"))?;
|
||||||
|
Some(Instruction::subtract(vals)?)
|
||||||
|
}
|
||||||
|
Instruction::Multiply(args) => {
|
||||||
|
let vals = Instruction::run_all(args, ctx, globals)?
|
||||||
|
.ok_or(InvalidValue("multiply"))?;
|
||||||
|
Some(Instruction::multiply(vals)?)
|
||||||
|
}
|
||||||
|
Instruction::Divide(args) => {
|
||||||
|
let vals =
|
||||||
|
Instruction::run_all(args, ctx, globals)?.ok_or(InvalidValue("divide"))?;
|
||||||
|
Some(Instruction::divide(vals)?)
|
||||||
|
}
|
||||||
|
Instruction::And(args) => {
|
||||||
|
let vals =
|
||||||
|
Instruction::run_all(args, ctx, globals)?.ok_or(InvalidValue("and"))?;
|
||||||
|
Some(Value::Integer(if vals.iter().all(Value::to_bool) {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
Instruction::Or(args) => {
|
||||||
|
let vals =
|
||||||
|
Instruction::run_all(args, ctx, globals)?.ok_or(InvalidValue("or"))?;
|
||||||
|
Some(Value::Integer(if vals.iter().any(Value::to_bool) {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
Instruction::Not(arg) => Some(Value::Integer(
|
||||||
|
if arg.run(ctx, globals)?.ok_or(InvalidValue("not"))?.to_bool() {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
Instruction::Equal(v1, v2) => Some(Value::Integer(
|
||||||
|
if Instruction::compare(
|
||||||
|
v1.run(ctx, globals)?.ok_or(InvalidValue("equal"))?,
|
||||||
|
v2.run(ctx, globals)?.ok_or(InvalidValue("equal"))?,
|
||||||
|
)? == 0
|
||||||
|
{
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
Instruction::Greater(v1, v2) => Some(Value::Integer(
|
||||||
|
if Instruction::compare(
|
||||||
|
v1.run(ctx, globals)?.ok_or(InvalidValue("greater"))?,
|
||||||
|
v2.run(ctx, globals)?.ok_or(InvalidValue("greater"))?,
|
||||||
|
)? > 0
|
||||||
|
{
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
Instruction::Lower(v1, v2) => Some(Value::Integer(
|
||||||
|
if Instruction::compare(
|
||||||
|
v1.run(ctx, globals)?.ok_or(InvalidValue("lower"))?,
|
||||||
|
v2.run(ctx, globals)?.ok_or(InvalidValue("lower"))?,
|
||||||
|
)? < 0
|
||||||
|
{
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
Instruction::Call(fct_ins, args) => {
|
||||||
|
let vals =
|
||||||
|
Instruction::run_all(args, ctx, globals)?.ok_or(InvalidValue("call"))?;
|
||||||
|
let fct_val = fct_ins.run(ctx, globals)?.ok_or(InvalidValue("call"))?;
|
||||||
|
if let Value::Function(f) = fct_val {
|
||||||
|
let mut fun_ctx = Context::new(Some(globals));
|
||||||
|
f.run(vals, &mut fun_ctx, globals)?
|
||||||
|
} else if let Value::StdFunction(f) = fct_val {
|
||||||
|
f(vals)?
|
||||||
|
} else {
|
||||||
|
Err(InvalidValue("call"))?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Instruction::CallNamed(fct_name, args) => {
|
||||||
|
let vals: Vec<Value> =
|
||||||
|
Instruction::run_all(args, ctx, globals)?.ok_or(InvalidValue("call"))?;
|
||||||
|
let fct_val = ctx
|
||||||
|
.value(&fct_name)
|
||||||
|
.ok_or(UnknownVariable(fct_name.clone()))?;
|
||||||
|
if let Value::Function(f) = fct_val {
|
||||||
|
let mut local = ctx.clone();
|
||||||
|
f.run(vals, &mut local, globals)?
|
||||||
|
} else if let Value::StdFunction(f) = fct_val {
|
||||||
|
f(vals)?
|
||||||
|
} else {
|
||||||
|
Err(InvalidValue("call"))?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Instruction::Return(ins) => {
|
||||||
|
let v = ins.run(ctx, globals)?.ok_or(InvalidValue("return"))?;
|
||||||
|
ctx.assign(String::from("__return"), v);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Instruction::If(cond, then) => {
|
||||||
|
if cond.run(ctx, globals)?.ok_or(InvalidValue("if"))?.to_bool() {
|
||||||
|
for i in then {
|
||||||
|
i.run(ctx, globals)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Instruction::IfElse(cond, then, els) => {
|
||||||
|
if cond.run(ctx, globals)?.ok_or(InvalidValue("if"))?.to_bool() {
|
||||||
|
for i in then {
|
||||||
|
i.run(ctx, globals)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for i in els {
|
||||||
|
i.run(ctx, globals)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Instruction::For {
|
||||||
|
variable,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
step,
|
||||||
|
body,
|
||||||
|
} => {
|
||||||
|
if let Value::Integer(f) = from.run(ctx, globals)?.ok_or(InvalidValue("for"))? {
|
||||||
|
if let Value::Integer(t) =
|
||||||
|
to.run(ctx, globals)?.ok_or(InvalidValue("for"))?
|
||||||
|
{
|
||||||
|
if let Value::Integer(s) =
|
||||||
|
step.run(ctx, globals)?.ok_or(InvalidValue("for"))?
|
||||||
|
{
|
||||||
|
for i in (f..t).step_by(s.try_into()?) {
|
||||||
|
ctx.assign(variable.clone(), Value::Integer(i));
|
||||||
|
for ins in body {
|
||||||
|
ins.run(ctx, globals)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Instruction::Each(variable, array_ins, body) => {
|
||||||
|
if let Value::Array(v) =
|
||||||
|
array_ins.run(ctx, globals)?.ok_or(InvalidValue("each"))?
|
||||||
|
{
|
||||||
|
for i in v.borrow().iter() {
|
||||||
|
ctx.assign(variable.clone(), i.clone());
|
||||||
|
for ins in body {
|
||||||
|
ins.run(ctx, globals)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(InvalidValue("each"))?
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Instruction::While(cond, body) => {
|
||||||
|
while cond
|
||||||
|
.run(ctx, globals)?
|
||||||
|
.ok_or(InvalidValue("while"))?
|
||||||
|
.to_bool()
|
||||||
|
{
|
||||||
|
for ins in body {
|
||||||
|
ins.run(ctx, globals)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Instruction::Handle(try_block, catch_block, variable) => {
|
||||||
|
for ins in try_block {
|
||||||
|
if let Err(e) = ins.run(ctx, globals) {
|
||||||
|
ctx.assign(variable.clone(), Value::String(e.to_string()));
|
||||||
|
for ins in catch_block {
|
||||||
|
ins.run(ctx, globals)?;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_file(filename: &str) -> Result<(), Box<dyn Error>> {
|
||||||
|
let contents = fs::read_to_string(filename)?;
|
||||||
|
run(contents)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(program: String) -> Result<(), Box<dyn Error>> {
|
||||||
|
let doc = Document::parse(&program)?;
|
||||||
|
let root = doc.root();
|
||||||
|
|
||||||
|
let mut ctx = Context::new(None);
|
||||||
|
stl::inject_all(&mut ctx);
|
||||||
|
|
||||||
|
let main = root
|
||||||
|
.first_element_child()
|
||||||
|
.ok_or(InvalidProgram)?
|
||||||
|
.children()
|
||||||
|
.find(|node| util::tag_name(&node) == "main")
|
||||||
|
.ok_or(MissingChild("program", "main"))?;
|
||||||
|
let main_ast = Instruction::from_children(main)?;
|
||||||
|
|
||||||
|
let functions = root
|
||||||
|
.first_element_child()
|
||||||
|
.ok_or(InvalidProgram)?
|
||||||
|
.children()
|
||||||
|
.filter(|node| node.tag_name().name() == String::from("function"));
|
||||||
|
|
||||||
|
for fun in functions {
|
||||||
|
ctx.assign(
|
||||||
|
String::from(fun.attribute("name").ok_or(Unnamed("function"))?),
|
||||||
|
Value::Function(Function::from(&fun)?),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut main_ctx = Context::new(Some(&ctx));
|
||||||
|
|
||||||
|
for ins in main_ast {
|
||||||
|
ins.run(&mut main_ctx, &ctx)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,12 +1,16 @@
|
|||||||
use super::error::{BadArgumentCount, InvalidArgument};
|
use crate::error::{BadArgumentCount, InaccessibleFile, InvalidArgument};
|
||||||
use super::{Context, Value};
|
use crate::interpreter::context::Context;
|
||||||
|
use crate::interpreter::value::Value;
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::io;
|
use std::fs::{self, OpenOptions};
|
||||||
|
use std::io::{stdin, stdout, Write};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
pub fn inject_all(ctx: &mut Context) {
|
pub fn inject_all(ctx: &mut Context) {
|
||||||
ctx.assign(String::from("print"), Value::StdFunction(print));
|
ctx.assign(String::from("print"), Value::StdFunction(print));
|
||||||
|
ctx.assign(String::from("print-line"), Value::StdFunction(print_line));
|
||||||
ctx.assign(String::from("input"), Value::StdFunction(input));
|
ctx.assign(String::from("input"), Value::StdFunction(input));
|
||||||
ctx.assign(
|
ctx.assign(
|
||||||
String::from("string-split"),
|
String::from("string-split"),
|
||||||
@@ -23,30 +27,48 @@ pub fn inject_all(ctx: &mut Context) {
|
|||||||
ctx.assign(String::from("to-ascii"), Value::StdFunction(to_ascii));
|
ctx.assign(String::from("to-ascii"), Value::StdFunction(to_ascii));
|
||||||
ctx.assign(String::from("from-ascii"), Value::StdFunction(from_ascii));
|
ctx.assign(String::from("from-ascii"), Value::StdFunction(from_ascii));
|
||||||
ctx.assign(String::from("get-args"), Value::StdFunction(get_args));
|
ctx.assign(String::from("get-args"), Value::StdFunction(get_args));
|
||||||
|
ctx.assign(String::from("write-file"), Value::StdFunction(write_file));
|
||||||
|
ctx.assign(String::from("read-file"), Value::StdFunction(read_file));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print(vals: Vec<Value>) -> Result<Option<Value>, Box<dyn Error>> {
|
fn print(vals: Vec<Value>) -> Result<Option<Value>, Box<dyn Error>> {
|
||||||
|
if vals.len() == 1 {
|
||||||
|
match &vals[0] {
|
||||||
|
Value::Integer(i) => print!("{}", i),
|
||||||
|
Value::Real(f) => print!("{}", f),
|
||||||
|
Value::String(s) => print!("{}", s),
|
||||||
|
v => print!("{:?}", v), // _ => Err("unprintable value")?,
|
||||||
|
};
|
||||||
|
let _ = stdout().flush();
|
||||||
|
Ok(Some(vals[0].clone()))
|
||||||
|
} else {
|
||||||
|
Err(BadArgumentCount("print", vals.len(), 1).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_line(vals: Vec<Value>) -> Result<Option<Value>, Box<dyn Error>> {
|
||||||
if vals.len() == 1 {
|
if vals.len() == 1 {
|
||||||
match &vals[0] {
|
match &vals[0] {
|
||||||
Value::Integer(i) => println!("{}", i),
|
Value::Integer(i) => println!("{}", i),
|
||||||
Value::Float(f) => println!("{}", f),
|
Value::Real(f) => println!("{}", f),
|
||||||
Value::String(s) => println!("{}", s),
|
Value::String(s) => println!("{}", s),
|
||||||
v => println!("{:?}", v), // _ => Err("unprintable value")?,
|
v => println!("{:?}", v), // _ => Err("unprintable value")?,
|
||||||
};
|
};
|
||||||
|
let _ = stdout().flush();
|
||||||
Ok(Some(vals[0].clone()))
|
Ok(Some(vals[0].clone()))
|
||||||
} else {
|
} else {
|
||||||
Err(BadArgumentCount("print", vals.len()).into())
|
Err(BadArgumentCount("print-line", vals.len(), 1).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn input(vals: Vec<Value>) -> Result<Option<Value>, Box<dyn Error>> {
|
fn input(vals: Vec<Value>) -> Result<Option<Value>, Box<dyn Error>> {
|
||||||
if vals.len() == 0 {
|
if vals.len() == 0 {
|
||||||
let mut line = String::new();
|
let mut line = String::new();
|
||||||
io::stdin().read_line(&mut line)?;
|
stdin().read_line(&mut line)?;
|
||||||
line.pop();
|
line.pop();
|
||||||
Ok(Some(Value::String(line)))
|
Ok(Some(Value::String(line)))
|
||||||
} else {
|
} else {
|
||||||
Err(BadArgumentCount("input", vals.len()).into())
|
Err(BadArgumentCount("input", vals.len(), 0).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +90,7 @@ fn string_split(vals: Vec<Value>) -> Result<Option<Value>, Box<dyn Error>> {
|
|||||||
Err(InvalidArgument("string-split", "target").into())
|
Err(InvalidArgument("string-split", "target").into())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(BadArgumentCount("string-split", vals.len()).into())
|
Err(BadArgumentCount("string-split", vals.len(), 2).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +112,7 @@ fn array_set(vals: Vec<Value>) -> Result<Option<Value>, Box<dyn Error>> {
|
|||||||
Err(InvalidArgument("array-set", "array").into())
|
Err(InvalidArgument("array-set", "array").into())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(BadArgumentCount("array-set", vals.len()).into())
|
Err(BadArgumentCount("array-set", vals.len(), 3).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +125,7 @@ fn array_push(vals: Vec<Value>) -> Result<Option<Value>, Box<dyn Error>> {
|
|||||||
Err(InvalidArgument("array-push", "array").into())
|
Err(InvalidArgument("array-push", "array").into())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(BadArgumentCount("array-push", vals.len()).into())
|
Err(BadArgumentCount("array-push", vals.len(), 2).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +141,7 @@ fn array_pop(vals: Vec<Value>) -> Result<Option<Value>, Box<dyn Error>> {
|
|||||||
Err(InvalidArgument("array-pop", "array").into())
|
Err(InvalidArgument("array-pop", "array").into())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(BadArgumentCount("array-pop", vals.len()).into())
|
Err(BadArgumentCount("array-pop", vals.len(), 1).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +162,7 @@ fn array_get(vals: Vec<Value>) -> Result<Option<Value>, Box<dyn Error>> {
|
|||||||
Err(InvalidArgument("array-set", "array").into())
|
Err(InvalidArgument("array-set", "array").into())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(BadArgumentCount("array-get", vals.len()).into())
|
Err(BadArgumentCount("array-get", vals.len(), 2).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,7 +174,7 @@ fn array_length(vals: Vec<Value>) -> Result<Option<Value>, Box<dyn Error>> {
|
|||||||
Err(InvalidArgument("array-length", "array").into())
|
Err(InvalidArgument("array-length", "array").into())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(BadArgumentCount("array-length", vals.len()).into())
|
Err(BadArgumentCount("array-length", vals.len(), 1).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +190,7 @@ fn to_ascii(vals: Vec<Value>) -> Result<Option<Value>, Box<dyn Error>> {
|
|||||||
Err(InvalidArgument("to-ascii", "integer").into())
|
Err(InvalidArgument("to-ascii", "integer").into())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(BadArgumentCount("to-ascii", vals.len()).into())
|
Err(BadArgumentCount("to-ascii", vals.len(), 1).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,7 +206,7 @@ fn from_ascii(vals: Vec<Value>) -> Result<Option<Value>, Box<dyn Error>> {
|
|||||||
Err(InvalidArgument("from-ascii", "string").into())
|
Err(InvalidArgument("from-ascii", "string").into())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(BadArgumentCount("from-ascii", vals.len()).into())
|
Err(BadArgumentCount("from-ascii", vals.len(), 1).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,6 +219,51 @@ fn get_args(vals: Vec<Value>) -> Result<Option<Value>, Box<dyn Error>> {
|
|||||||
.collect(),
|
.collect(),
|
||||||
)))))
|
)))))
|
||||||
} else {
|
} else {
|
||||||
Err(BadArgumentCount("get-args", vals.len()).into())
|
Err(BadArgumentCount("get-args", vals.len(), 0).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_file(vals: Vec<Value>) -> Result<Option<Value>, Box<dyn Error>> {
|
||||||
|
if vals.len() == 3 {
|
||||||
|
if let Value::String(path) = &vals[0] {
|
||||||
|
if let Value::String(contents) = &vals[1] {
|
||||||
|
if let Ok(mut file) = OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.append(Value::to_bool(&vals[2]))
|
||||||
|
.open(path)
|
||||||
|
{
|
||||||
|
if let Ok(_) = write!(file, "{}", contents) {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
Err(InaccessibleFile(path.clone()).into())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(InaccessibleFile(path.clone()).into())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(InvalidArgument("write-file", "string").into())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(InvalidArgument("write-file", "string").into())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(BadArgumentCount("write-file", vals.len(), 3).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_file(vals: Vec<Value>) -> Result<Option<Value>, Box<dyn Error>> {
|
||||||
|
if vals.len() == 1 {
|
||||||
|
if let Value::String(path) = &vals[0] {
|
||||||
|
if let Ok(contents) = fs::read_to_string(path) {
|
||||||
|
Ok(Some(Value::String(contents)))
|
||||||
|
} else {
|
||||||
|
Err(InaccessibleFile(path.clone()).into())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(InvalidArgument("read-file", "string").into())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(BadArgumentCount("read-file", vals.len(), 1).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
use super::error::{BadArgumentCount, MissingChild, Unnamed};
|
|
||||||
use super::{util, Context, Instruction};
|
|
||||||
use roxmltree::Node;
|
use roxmltree::Node;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use crate::error::{BadArgumentCount, MissingChild, Unnamed};
|
||||||
|
use crate::instruction::Instruction;
|
||||||
|
use crate::interpreter::context::Context;
|
||||||
|
use crate::interpreter::run::Run;
|
||||||
|
use crate::util;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Function {
|
pub struct Function {
|
||||||
pub args: Vec<String>,
|
pub args: Vec<String>,
|
||||||
@@ -19,7 +23,7 @@ impl Function {
|
|||||||
globals: &Context,
|
globals: &Context,
|
||||||
) -> Result<Option<Value>, Box<dyn Error>> {
|
) -> Result<Option<Value>, Box<dyn Error>> {
|
||||||
if args.len() != self.args.len() {
|
if args.len() != self.args.len() {
|
||||||
Err(BadArgumentCount("function", args.len()))?
|
Err(BadArgumentCount("function", args.len(), self.args.len()))?
|
||||||
}
|
}
|
||||||
self.args
|
self.args
|
||||||
.iter()
|
.iter()
|
||||||
@@ -50,7 +54,7 @@ impl Function {
|
|||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
Integer(i64),
|
Integer(i64),
|
||||||
Float(f64),
|
Real(f64),
|
||||||
String(String),
|
String(String),
|
||||||
Array(Rc<RefCell<Vec<Value>>>),
|
Array(Rc<RefCell<Vec<Value>>>),
|
||||||
Function(Function),
|
Function(Function),
|
||||||
@@ -61,7 +65,7 @@ impl Value {
|
|||||||
pub fn to_bool(&self) -> bool {
|
pub fn to_bool(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Value::Integer(i) => *i != 0,
|
Value::Integer(i) => *i != 0,
|
||||||
Value::Float(f) => *f != 0.0,
|
Value::Real(f) => *f != 0.0,
|
||||||
Value::String(s) => s.len() != 0,
|
Value::String(s) => s.len() != 0,
|
||||||
Value::Array(v) => v.borrow().len() != 0,
|
Value::Array(v) => v.borrow().len() != 0,
|
||||||
_ => true,
|
_ => true,
|
||||||
57
src/lib.rs
57
src/lib.rs
@@ -1,58 +1,9 @@
|
|||||||
use std::error::Error;
|
|
||||||
use std::fs;
|
|
||||||
|
|
||||||
use roxmltree::Document;
|
|
||||||
|
|
||||||
mod context;
|
|
||||||
mod error;
|
mod error;
|
||||||
mod instruction;
|
mod instruction;
|
||||||
mod stl;
|
|
||||||
mod util;
|
mod util;
|
||||||
mod value;
|
|
||||||
|
|
||||||
use context::Context;
|
mod compiler;
|
||||||
use error::{InvalidProgram, MissingChild, Unnamed};
|
mod interpreter;
|
||||||
use instruction::Instruction;
|
|
||||||
use value::{Function, Value};
|
|
||||||
|
|
||||||
pub fn run_file(filename: &str) -> Result<(), Box<dyn Error>> {
|
pub use compiler::compile::{compile, compile_file};
|
||||||
let contents = fs::read_to_string(filename)?;
|
pub use interpreter::run::{run, run_file};
|
||||||
run(contents)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(program: String) -> Result<(), Box<dyn Error>> {
|
|
||||||
let doc = Document::parse(&program)?;
|
|
||||||
let root = doc.root();
|
|
||||||
|
|
||||||
let mut ctx = Context::new(None);
|
|
||||||
stl::inject_all(&mut ctx);
|
|
||||||
|
|
||||||
let main = root
|
|
||||||
.first_element_child()
|
|
||||||
.ok_or(InvalidProgram)?
|
|
||||||
.children()
|
|
||||||
.find(|node| util::tag_name(&node) == "main")
|
|
||||||
.ok_or(MissingChild("program", "main"))?;
|
|
||||||
let main_ast = Instruction::from_children(main)?;
|
|
||||||
|
|
||||||
let functions = root
|
|
||||||
.first_element_child()
|
|
||||||
.ok_or(InvalidProgram)?
|
|
||||||
.children()
|
|
||||||
.filter(|node| node.tag_name().name() == String::from("function"));
|
|
||||||
|
|
||||||
for fun in functions {
|
|
||||||
ctx.assign(
|
|
||||||
String::from(fun.attribute("name").ok_or(Unnamed("function"))?),
|
|
||||||
Value::Function(Function::from(&fun)?),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut main_ctx = Context::new(Some(&ctx));
|
|
||||||
|
|
||||||
for ins in main_ast {
|
|
||||||
ins.run(&mut main_ctx, &ctx)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
use plxml::run_file;
|
|
||||||
use std::env;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let args: Vec<String> = env::args().collect();
|
|
||||||
if let Err(e) = run_file(&args[1]) {
|
|
||||||
eprintln!("Error occurred: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user