Compare commits

...

10 Commits

Author SHA1 Message Date
0e5a9acccf 0.1.1: structural reworks + dep version bump 2023-11-28 18:01:09 +01:00
2e2712704e good ol' handbook 2022-06-05 16:33:39 +02:00
ed233c17de float is boring, let's call it real 2022-06-05 01:19:54 +02:00
b377d4e69f stl documentation 2022-06-05 00:26:52 +02:00
fcd7c31ea7 syntax for handle 2022-06-03 12:43:45 +02:00
9e6f0c454a error handling 2022-06-03 12:39:14 +02:00
d8166a9ff6 file manipulation basics 2022-06-03 11:40:06 +02:00
004788bbfb syntax fixups 2022-05-19 14:39:18 +02:00
15d65269aa doc syntax cleanup 2022-03-11 12:43:31 +01:00
83965b82e8 doc syntax 2022-03-11 12:39:52 +01:00
22 changed files with 1807 additions and 580 deletions

15
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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
View 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
View 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!&#xD;&#xA;" />
</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
View 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
View 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
View 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>

View File

@ -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
View 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
View 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
View 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
View File

@ -0,0 +1 @@
pub mod compile;

View File

@ -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 {}

View File

@ -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
})
}
}

View File

@ -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
View File

@ -0,0 +1,4 @@
mod context;
pub mod run;
mod stl;
mod value;

538
src/interpreter/run.rs Normal file
View 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(())
}

View File

@ -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())
}
}

View File

@ -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,

View File

@ -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};

View 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);
}
}