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]]
|
||||
name = "plxml"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"roxmltree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roxmltree"
|
||||
version = "0.14.1"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "921904a62e410e37e215c40381b7117f830d9d89ba60ab5236170541dd25646b"
|
||||
dependencies = [
|
||||
"xmlparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xmlparser"
|
||||
version = "0.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "114ba2b24d2167ef6d67d7d04c8cc86522b87f490025f39f0303b7db5bf5e3d8"
|
||||
checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f"
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
[package]
|
||||
name = "plxml"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
default-run = "interpreter"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
roxmltree = "0.14.1"
|
||||
roxmltree = "0.19"
|
||||
@ -1,3 +1,8 @@
|
||||
# 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>
|
||||
<assign variable="arr">
|
||||
<array>
|
||||
<float value="0.1" />
|
||||
<real value="0.1" />
|
||||
</array>
|
||||
</assign>
|
||||
<call function="array-set">
|
||||
<arguments>
|
||||
<value variable="arr" />
|
||||
<integer value="0" />
|
||||
<float value="0.5" />
|
||||
<real value="0.5" />
|
||||
</arguments>
|
||||
</call>
|
||||
<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 {
|
||||
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 {}
|
||||
|
||||
#[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 {
|
||||
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 {}
|
||||
|
||||
#[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::{
|
||||
BadChildCount, IncompatibleValues, InvalidValue, MissingAttribute, MissingChild,
|
||||
UnknownVariable,
|
||||
};
|
||||
use super::{util, Context, Value};
|
||||
use super::error::{BadChildCount, MissingAttribute, MissingChild};
|
||||
use crate::util;
|
||||
use roxmltree::Node;
|
||||
use std::cell::RefCell;
|
||||
use std::error::Error;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Instruction {
|
||||
@ -14,8 +9,8 @@ pub enum Instruction {
|
||||
Assign(String, Box<Instruction>),
|
||||
Integer(String),
|
||||
IntegerCast(Box<Instruction>),
|
||||
Float(String),
|
||||
FloatCast(Box<Instruction>),
|
||||
Real(String),
|
||||
RealCast(Box<Instruction>),
|
||||
String(String),
|
||||
StringCast(Box<Instruction>),
|
||||
Array(Vec<Instruction>),
|
||||
@ -43,6 +38,7 @@ pub enum Instruction {
|
||||
},
|
||||
Each(String, Box<Instruction>, Vec<Instruction>),
|
||||
While(Box<Instruction>, Vec<Instruction>),
|
||||
Handle(Vec<Instruction>, Vec<Instruction>, String),
|
||||
}
|
||||
|
||||
impl Instruction {
|
||||
@ -72,13 +68,13 @@ impl Instruction {
|
||||
Err(MissingAttribute("integer", "value"))?
|
||||
}
|
||||
}
|
||||
"float" => {
|
||||
"real" => {
|
||||
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() {
|
||||
Instruction::FloatCast(Box::new(Instruction::new(n)?))
|
||||
Instruction::RealCast(Box::new(Instruction::new(n)?))
|
||||
} else {
|
||||
Err(MissingAttribute("float", "value"))?
|
||||
Err(MissingAttribute("real", "value"))?
|
||||
}
|
||||
}
|
||||
"string" => {
|
||||
@ -230,6 +226,19 @@ impl Instruction {
|
||||
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))?,
|
||||
})
|
||||
}
|
||||
@ -240,467 +249,4 @@ impl Instruction {
|
||||
.map(Instruction::new)
|
||||
.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;
|
||||
|
||||
#[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 super::{Context, Value};
|
||||
use crate::error::{BadArgumentCount, InaccessibleFile, InvalidArgument};
|
||||
use crate::interpreter::context::Context;
|
||||
use crate::interpreter::value::Value;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::error::Error;
|
||||
use std::io;
|
||||
use std::fs::{self, OpenOptions};
|
||||
use std::io::{stdin, stdout, Write};
|
||||
use std::rc::Rc;
|
||||
|
||||
pub fn inject_all(ctx: &mut Context) {
|
||||
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("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("from-ascii"), Value::StdFunction(from_ascii));
|
||||
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>> {
|
||||
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 {
|
||||
match &vals[0] {
|
||||
Value::Integer(i) => println!("{}", i),
|
||||
Value::Float(f) => println!("{}", f),
|
||||
Value::Real(f) => println!("{}", f),
|
||||
Value::String(s) => println!("{}", s),
|
||||
v => println!("{:?}", v), // _ => Err("unprintable value")?,
|
||||
};
|
||||
let _ = stdout().flush();
|
||||
Ok(Some(vals[0].clone()))
|
||||
} 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>> {
|
||||
if vals.len() == 0 {
|
||||
let mut line = String::new();
|
||||
io::stdin().read_line(&mut line)?;
|
||||
stdin().read_line(&mut line)?;
|
||||
line.pop();
|
||||
Ok(Some(Value::String(line)))
|
||||
} 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())
|
||||
}
|
||||
} 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())
|
||||
}
|
||||
} 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())
|
||||
}
|
||||
} 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())
|
||||
}
|
||||
} 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())
|
||||
}
|
||||
} 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())
|
||||
}
|
||||
} 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())
|
||||
}
|
||||
} 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())
|
||||
}
|
||||
} 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(),
|
||||
)))))
|
||||
} 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 std::cell::RefCell;
|
||||
use std::error::Error;
|
||||
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)]
|
||||
pub struct Function {
|
||||
pub args: Vec<String>,
|
||||
@ -19,7 +23,7 @@ impl Function {
|
||||
globals: &Context,
|
||||
) -> Result<Option<Value>, Box<dyn Error>> {
|
||||
if args.len() != self.args.len() {
|
||||
Err(BadArgumentCount("function", args.len()))?
|
||||
Err(BadArgumentCount("function", args.len(), self.args.len()))?
|
||||
}
|
||||
self.args
|
||||
.iter()
|
||||
@ -50,7 +54,7 @@ impl Function {
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Value {
|
||||
Integer(i64),
|
||||
Float(f64),
|
||||
Real(f64),
|
||||
String(String),
|
||||
Array(Rc<RefCell<Vec<Value>>>),
|
||||
Function(Function),
|
||||
@ -61,7 +65,7 @@ impl Value {
|
||||
pub fn to_bool(&self) -> bool {
|
||||
match self {
|
||||
Value::Integer(i) => *i != 0,
|
||||
Value::Float(f) => *f != 0.0,
|
||||
Value::Real(f) => *f != 0.0,
|
||||
Value::String(s) => s.len() != 0,
|
||||
Value::Array(v) => v.borrow().len() != 0,
|
||||
_ => 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 instruction;
|
||||
mod stl;
|
||||
mod util;
|
||||
mod value;
|
||||
|
||||
use context::Context;
|
||||
use error::{InvalidProgram, MissingChild, Unnamed};
|
||||
use instruction::Instruction;
|
||||
use value::{Function, Value};
|
||||
mod compiler;
|
||||
mod interpreter;
|
||||
|
||||
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(())
|
||||
}
|
||||
pub use compiler::compile::{compile, compile_file};
|
||||
pub use interpreter::run::{run, run_file};
|
||||
|
||||
@ -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