Compare commits

...

6 Commits

4 changed files with 155 additions and 11 deletions

View File

@ -2,6 +2,14 @@ package codec
import "io" import "io"
// Decodable is any type that can decode itself from a decoder.
type Decodable interface {
// Decode reads data from decoder, replacing the data of the object. It
// returns the amount of bytes written, and an error if the write
// stopped early.
Decode(decoder *Decoder) (n int, err error)
}
// Decoder wraps an [io.Reader] and decodes data from it. // Decoder wraps an [io.Reader] and decodes data from it.
type Decoder struct { type Decoder struct {
io.Reader io.Reader

View File

@ -2,6 +2,13 @@ package codec
import "io" import "io"
// Encodable is any type that can write itself to an encoder.
type Encodable interface {
// Encode sends data to encoder. It returns the amount of bytes written,
// and an error if the write stopped early.
Encode(encoder *Encoder) (n int, err error)
}
// Encoder wraps an [io.Writer] and encodes data to it. // Encoder wraps an [io.Writer] and encodes data to it.
type Encoder struct { type Encoder struct {
io.Writer io.Writer

102
design/pdl-compiler.md Normal file
View File

@ -0,0 +1,102 @@
# PDL Compiler Specification
Given one or more PDL files representing a protocol, the compiler shall generate
a Go package named "protocol", which shall contain types for message and type
definitions, as well as encoding and decoding methods.
## Static Section
The compiler shall write a static section alongside the generated code. It
shall contain this text:
```go
// Table is a KTV table with an undefined schema.
type Table map[uint16] any
// Message is any message that can be sent along this protocol.
type Message interface {
codec.Encodable
codec.Decodable
// Method returns the method code of the message.
Method() uint16
}
```
## Preamble
At the start of each file but after the package name, the compiler shall emit
this text:
```go
/* # Do not edit this package by hand!
*
* This file was automatically generated by the Holanet PDL compiler. The
* source file is located at <path>
* Please edit that file instead, and re-compile it to this location.
*
* HOPP, TAPE, METADAPT, PDL/0 (c) 2025 holanet.xyz
*/
```
Where `<path>` is the path of the protocol definition file relative to the
generated file.
## Message Definitions
For each defined message, the compiler shall generate a Go type named
`MessageName`, where `Name` is the name of the message as written in its
definition. The message shall be encodable, and shall have `Encode` and `Decode`
methods as described below.
All messages shall satisfy a `Message` interface, which is defined in the
static section.
## Type Definitions
For each defined type, the compiler shall generate a Go type with the same name
as written in its definition. The Go type shall be encodable, and shall have
`Encode` and `Decode` methods as described below.
## Encoding and Decoding Methods
Each encodable type shall be given an `Encode` method and a `Decode` method,
which will take in a `codec.Encoder` and a `codec.Decoder` respectively. Both
return an `(int, error)` pair describing the amount of bytes written and an
error if the write stopped early. `Encode` will encode the data within the
message to the given encoder, and `Decode` will decode data from the given
decoder and place it in the type's value. The methods shall not retain or close
any encoders or decoders they are given. Both methods shall have pointer
receivers. In effect, these methods will satisfy `codec.Encodable` and
`codec.Decodable`.
## Connection
The compiler shall generate a `Conn` struct which embeds a `hopp.Conn`, which
is the real "porcelain" of the generated code. It shall provide methods to
create and accept transactions. Each transaction shall be a struct which embeds
a `hopp.Trans`, and shall have methods for sending and receiving messages.
### Sending
To send a message along a transaction, the program shall:
1. Obtain the method code from the message
3. Obtain a writer from the connection using the method code
4. Wrap the writer in a `codec.Encoder`
5. Use the encoder to encode the message
6. Close the writer
### Receiving
To receiving a message from a transaction, the program shall:
1. Obtain a method code and reader from the connection
2. Wrap the reader in a `codec.Decoder`
3. Switch on the method code, and decode the correct message using the decoder
4. Return the message to the caller as a value
The recieve function must return the message as a value instead of a pointer in
order to avoid making an allocation. Because of this, the return value must be
`any` instead of `Message`. The caller must then use a type switch to figure out
what message was sent.

View File

@ -28,7 +28,8 @@ PDL allows defining a protocol using HOPP and TAPE.
| String | SBA/LBA | * | UTF-8 string | String | SBA/LBA | * | UTF-8 string
| Buffer | SBA/LBA | * | Byte array | Buffer | SBA/LBA | * | Byte array
| []\<TYPE\> | OTA | * | Array of any type[^1] | []\<TYPE\> | OTA | * | Array of any type[^1]
| Table | KTV | * | | Table | KTV | * | Table with undefined schema
| {...} | KTV | * | Table with defined schema
[^1]: Excluding SI and SBA. I5 and U5 cannot be used in an array, but String and [^1]: Excluding SI and SBA. I5 and U5 cannot be used in an array, but String and
Buffer are simply forced to use their "long" variant. Buffer are simply forced to use their "long" variant.
@ -37,19 +38,25 @@ Buffer are simply forced to use their "long" variant.
## Tokens ## Tokens
| Name | Syntax | PDL files are divided into tokens, which assemble together into larger language
| -------- | --------------------- | structures. They are separated by whitespace.
| Method | `M[0-9A-Fa-f]{4}` |
| Key | `[0-9A-Fa-f]{4}` | | Name | Syntax | Description
| Ident | `[A-Za-z][A-Za-z0-9]` | | -------- | ------------------ | -----------
| Comma | `,` | | Magic | `PDL/0` | Must appear at the very start of the file.
| LBrace | `{` | | Method | `M[0-9A-Fa-f]{4}` | A 16-bit hexadecimal method code.
| RBrace | `}` | | Key | `[0-9A-Fa-f]{4}` | A 16-bit hexadecimal table key.
| LBracket | `[` | | Ident | `[A-Z][A-Za-z0-9]` | An identifier.
| RBracket | `]` | | Comma | `,` | A comma separator.
| LBrace | `{` | A left curly brace.
| RBrace | `}` | A right curly brace.
| LBracket | `[` | A left square bracket.
| RBracket | `]` | A right square bracket.
## Syntax ## Syntax
All files must begin with a Magic token.
Types are expressed with an Ident. A table can be used by either writing the Types are expressed with an Ident. A table can be used by either writing the
name of the type (Table), or by defining a schema with curly braces. Arrays must name of the type (Table), or by defining a schema with curly braces. Arrays must
be expressed using two matching square brackets before their element type. be expressed using two matching square brackets before their element type.
@ -66,6 +73,8 @@ can be anything.
Here is an example of all that: Here is an example of all that:
``` ```
PDL/0
M0000 Connect { M0000 Connect {
0000 Name String, 0000 Name String,
0001 Password String, 0001 Password String,
@ -81,3 +90,21 @@ User {
0002 Followers U32, 0002 Followers U32,
} }
``` ```
## EBNF Description
Below is an EBNF description of the language.
```
<file> -> <magic> (<message> | <typedef)*
<magic> -> "PDL/0"
<method> -> /M[0-9A-Fa-f]{4}/
<key> -> /[0-9A-Fa-f]{4}/
<ident> -> /[A-Z][A-Za-z0-9]/
<field> -> <key> <ident> <type>
<type> -> <ident>
| "[" "]" <type>
| "{" (<field> ",")* [<field>] "}"
<message> -> <method> <ident> <type>
<typedef> -> <ident> <type>
```