Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ There is a [book 📒](https://uberfoo.github.io/assets/docs/dwarf/introduction.
Check it out!

*Nota Bene*: This is a work in progress.
The parser is pretty good, but not perfect, at recognizing the language.
However, the errors that it reports aren't always useful.

I appreciate feedback.
Let me know if you love it, or hate it.
Expand Down
2 changes: 1 addition & 1 deletion doc/book.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,4 @@ command = "mdbook-mermaid"

[preprocessor.admonish]
command = "mdbook-admonish"
assets_version = "2.0.2" # do not edit: managed by `mdbook-admonish install`
assets_version = "3.0.1" # do not edit: managed by `mdbook-admonish install`
166 changes: 79 additions & 87 deletions doc/mdbook-admonish.css

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion doc/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@

- [Dwarf Language Reference]()
- [Items]()
- [Enumerations](./reference/enums.md)
- [Functions](./reference/functions.md)
- [Imports](./reference/imports.md)
- [Patterns](./reference/patterns.md)
- [Structs](./reference/structs.md)
- [Statements](./reference/statements.md)
- [Expressions](./reference/expressions.md)
- [Expressions]()
- [Literal Expressions](./reference/expressions/literal.md)
- [Unary Expressions](./reference/expressions/unary.md)
- [Binary Expressions](./reference/expressions/binary.md)
Expand Down
130 changes: 102 additions & 28 deletions doc/src/hacker/dwarf/generics.md
Original file line number Diff line number Diff line change
@@ -1,83 +1,115 @@
# Generic Types

For those not in the know, generics are a type of polymorphism.
They allow one to write code that works with many distinct types.
This is facilitated by supplying a generic type in your generic code, e.g., a function or a struct.
A concreted type is supplied at the usage site.
During compilation, the generic type is replaced with the concrete type.
Today we're going to talk about how generics are implemented in dwarf.
Generics are a type of polymorphism, which is a way to have one chunk of code work with multiple types.
In the generic code, the abstract type is specified by a single capital letter.
When the code is compiled the abstract type is replaced with a concrete type.
The concrete type is inferred from the call site.

Here's a simple example:
Below are examples of a generic function and a generic type, as well as a usage of each.

```dwarf
// This is a generic function.
// It takes a type T and returns a value of type T.
fn id<T>(x: T) -> T {
x
x + 42
}

// This is a generic type.
// It takes a type T and stores a value of type T.
struct Box<T> {
value: T,
struct Box<U> {
value: U,
}

impl Box<T> {
impl Box<U> {
fn display(self) {
print("Box<{0}>\n".format(self.value));
}
}

fn main() {
// Here we call the generic function with an int.
let x = id(42);
let x = id(54);
print("{0}\n".format(x));
chacha::assert(x == 96);

// And here with a float.
let y = id(9.6);
let y = id("Hello World, the answer is ");
print("{0}\n".format(y));
chacha::assert(y == "Hello World, the answer is 42");

// Here we create a Box that stores an int.
let z = Box{value: "Hello, World!"};
print("{0}\n".format(z));
// Here we create a Box that stores a float.
let z = Box{ value: 0.42 };
z.display();
chacha::assert(chacha::typeof(z.value) == "float");

// Let's box a list.
let α = Box{value: [1, 2, 3]};
print("{0}\n".format(α));
// Let's box a list now.
let α = Box{ value: [1, 2, 3] };
α.display();
chacha::assert(chacha::typeof(α.value) == "[int]");

// Let's try something interesting...
let β = Box{ value: id("") };
β.display();
chacha::assert(chacha::typeof(β.value) == "string");
chacha::assert(β.value == "42");
}
```

In the examples above notice that the generic labels, `T` and `U`, are used in the function and type definitions.
The code in the `main` function uses the generic function and type with no label to be seen.
That is because concrete types are used in the non-generic, non-definition code.

## Requirements

So what does it take to make this happen in dwarf?
Well, like everything else, there's a parser piece, an extruder piece, and an interpreter piece.

The {{i: parser}} reads the input and looks for generic annotations where appropriate.
The {{i: extruder}} takes the output of the parser and builds the AST, substituting the generic placeholder with a concrete type.
The {{i: interpreter}} then takes the AST and evaluates it.

### Parser

### Extruder
We won't discuss how the parser works, which is covered in the [parser](../arch/parser.md) section.
Instead we'll look at the parser output.
But first!

#### Where are generics allowed?

### Interpreter

## {{i: Grace}} {{i: AST}} Model



### Extruder

#### {{i: Grace}} {{i: AST}} Model

Below is an approximation of (a part of) the model that is used to generate (a part of) the dwarf abstract syntax tree ([AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree)).
The points worth reflecting upon are that `Type` is a generalization over all of the dwarf types.
Also, `Struct` and `Field` both have relationships to `Type`. separate from `R1`.
The diagram is UML-ish, and says that **Type** is a supertype, and everything connected by an **R1** label is it's subtype.
**Field** has two relationships, **R3** and **R4**, to **Struct** and **Type** respectively.
These relationships are backed by "referential attributes" whose purpose is to store the relationship data.
In the case of **Field**, **struct** formalizes **R3** and **type** formalizes **R4**.

The points worth reflecting upon are that **Type** is a generalization over all of the dwarf types.
Also, **Field** and **Generic** both have relationships to **Type**. separate from **R1**.

```mermaid
classDiagram
Type <|-- Generic : R1
Type <|-- Integer : R1
Type <|-- Struct : R1
Type <|-- Etc : R1
Type <|-- Etcetera : R1
Generic --> "0..1" Type : R2
Struct "1" <-- "0..*" Field : R3
Field --> Type : R4

class Type
<<Enumeration>> Type

class Generic {
place_holder: String
type: Option~R2_Type~
label: String
*type: Option~R2_Type~
}

class Struct {
Expand All @@ -86,11 +118,51 @@ classDiagram

class Field {
name: String
struct: R3_Struct
type: R4_Type
*struct: R3_Struct
*type: R4_Type
}
```

There is a real model that is much more extensive that is used to generate the AST code.
Below is the generated code for the actual **Type**, called `ValueType` in the code.

```rust
pub enum ValueType {
Char(Uuid),
Empty(Uuid),
Enumeration(Uuid),
Function(Uuid),
Future(Uuid),
Generic(Uuid),
Import(Uuid),
Lambda(Uuid),
List(Uuid),
ObjectStore(Uuid),
Plugin(Uuid),
Range(Uuid),
Struct(Uuid),
Task(Uuid),
Ty(Uuid),
Unknown(Uuid),
}
```

The `Ty` variant is actually imported from yet another, fundamental, model.
It's definition is below, ond contains what one might expect, given what is missing from above.
These are the fundamental modeling types, whereas above are the dwarf types.

```rust
pub enum Ty {
Boolean(Uuid),
External(Uuid),
Float(Uuid),
Integer(Uuid),
Object(Uuid),
String(Uuid),
Uuid(Uuid),
}
```

```dwarf
let definition = "
struct Box<T> {
Expand All @@ -104,3 +176,5 @@ print(ast);
```

Mention something about assuming the type of a field expression, and then having to use that to check type.

### Interpreter
1 change: 1 addition & 0 deletions doc/src/reference/enums.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Enumerations
1 change: 1 addition & 0 deletions doc/src/reference/functions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Functions
1 change: 1 addition & 0 deletions doc/src/reference/imports.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Imports
1 change: 1 addition & 0 deletions doc/src/reference/patterns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Patterns
1 change: 1 addition & 0 deletions doc/src/reference/structs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Structs
8 changes: 7 additions & 1 deletion doc/src/tutorials/mandelbrot/complex.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ One element is the real part of the number, and the other is the imaginary part.
The real part is plotted along the x-axis, and the imaginary part is plotted along the y-axis.

Addition and subtraction are defined as you would expect: perform the operation on the real and imaginary parts independently, e.g.: \\((a, m) + (b, n) = (a + b, m + n)\\).
To multiply two complex numbers, one must refer back to multiplying two binomials: \\((a + bi)(c + di) = ac + adi + bci + bdi^2\\).
To multiply two complex numbers, one must refer back to multiplying two binomials: \\((a + bi)(c + di) = bdi^2 + bci + adi + ac = bci + adi + ac - bd\\).
But don't get caught up in the math — it's not on the test.

## Defining the Type
Expand Down Expand Up @@ -54,6 +54,8 @@ The last two lines are functions provided by the runtime {{i: ChaCha}}.
Having a type is a good start.
We can now create Complex numbers

### Addition

Addition is fairly straightforward:

```dwarf
Expand All @@ -73,6 +75,7 @@ impl Complex {
This is an *impl* block.
Functions that belong to the *struct* go into the *impl* block.

### Squared

Similarly, the square function is not too bad:

Expand All @@ -90,6 +93,7 @@ impl Complex {
}
```

### A Shortcut
Earlier I said that you know if you are in the set if you don't go to infinity and beyond.
We don't have that much time, and there's a shortcut.
While we are iterating, we can just check the absolute value of the complex number.
Expand Down Expand Up @@ -117,6 +121,8 @@ There is something worth noting in the last function.
We are returning a *float*, but there is no *return* statement.
Just like in Rust, the last expression in a *block* is the the value of the block.

### Zero

We'll need to be able to create the Complex number "0".
We can do that with a *{{i: static method}}*.
Static methods are functions that belong to the type, rather than an instance of the type.
Expand Down
2 changes: 1 addition & 1 deletion src/chacha/interpreter/pvt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ impl<'a> PrintableValueType<'a> {
let list = s_read!(lu_dog).exhume_list(list).unwrap();
let list = s_read!(list);
let ty = list.r36_value_type(&s_read!(lu_dog))[0].clone();
write!(f, "{}", PrintableValueType(false, ty, context))
write!(f, "[{}]", PrintableValueType(false, ty, context))
}
ValueTypeEnum::XPlugin(ref plugin) => {
let plugin = s_read!(lu_dog).exhume_x_plugin(plugin).unwrap();
Expand Down
22 changes: 13 additions & 9 deletions src/chacha/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,14 +453,16 @@ impl Value {
unreachable!()
}
Value::Vector { ty, inner: _ } => {
let ty = match &s_read!(ty).subtype {
ValueTypeEnum::XFuture(id) => {
let ty = lu_dog.exhume_x_future(id).unwrap();
let ty = s_read!(ty);
ty.r2_value_type(lu_dog)[0].clone()
}
_ => ty.clone(),
};
// 🚧 This code was here and I don't know why. I think it must
// have been a copy/paste error. I commented it out on 12/11/2023.
// let ty = match &s_read!(ty).subtype {
// ValueTypeEnum::XFuture(id) => {
// let ty = lu_dog.exhume_x_future(id).unwrap();
// let ty = s_read!(ty);
// ty.r2_value_type(lu_dog)[0].clone()
// }
// _ => ty.clone(),
// };
for vt in lu_dog.iter_value_type() {
if let ValueTypeEnum::List(id) = s_read!(vt).subtype {
let list = lu_dog.exhume_list(&id).unwrap();
Expand Down Expand Up @@ -1142,7 +1144,9 @@ impl std::ops::Add for Value {
(Value::String(a), Value::Float(b)) => Value::String(a.to_owned() + &b.to_string()),
(Value::Integer(a), Value::Integer(b)) => Value::Integer(a + b),
// (Value::Integer(a), Value::String(b)) => Value::String(a.to_string() + &b),
// (Value::String(a), Value::Integer(b)) => Value::String(a + &b.to_string()),
(Value::String(a), Value::Integer(b)) => {
Value::String(a.to_owned() + b.to_string().as_str())
}
(Value::String(a), Value::String(b)) => Value::String(a.to_owned() + b),
(Value::Char(a), Value::Char(b)) => Value::String(a.to_string() + &b.to_string()),
(Value::Char(a), Value::String(b)) => Value::String(a.to_string() + &b),
Expand Down
44 changes: 0 additions & 44 deletions src/dwarf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,50 +305,6 @@ impl Type {
}
}

// impl From<(&Type, &mut LuDogStore, &SarzakStore)> for ValueType {
// fn from((type_, store, model): (&Type, &mut LuDogStore, &SarzakStore)) -> Self {
// match type_ {
// Type::Boolean => {
// let ty = Ty::new_boolean();
// ValueType::new_ty(&ty, store)
// }
// Type::Empty => ValueType::new_empty(),
// Type::Float => {
// let ty = Ty::new_float();
// ValueType::new_ty(&ty, store)
// }
// Type::Integer => {
// let ty = Ty::new_integer();
// ValueType::new_ty(&ty, store)
// }
// Type::Option(type_) => {
// let ty = (&**type_, &store, &model).into();
// let option = WoogOption::new_none(&ty, store);
// ValueType::new_woog_option(&option, store)
// }
// Type::Self_(type_) => panic!("Self is deprecated."),
// Type::String => {
// let ty = Ty::new_s_string();
// ValueType::new_ty(&ty, store)
// }
// Type::UserType(type_) => {
// let name = if let Token::Object(name) = &**type_ {
// name
// } else {
// panic!("Expected UserType to be Token::Object.")
// };
// let obj_id = model.exhume_object_id_by_name(&name).unwrap();
// let ty = model.exhume_ty(obj_id).unwrap();
// ValueType::new_ty(&ty, store)
// }
// Type::Uuid => {
// let ty = Ty::new_s_uuid();
// ValueType::new_ty(&ty, store)
// }
// }
// }
// }

#[derive(Clone, Debug, PartialEq)]
pub enum Statement {
Empty,
Expand Down
Loading