5 Commits

5 changed files with 84 additions and 24 deletions

View File

@@ -1,48 +1,66 @@
package main
import "os"
import "fmt"
import "strings"
import "path/filepath"
import "git.tebibyte.media/sashakoshka/go-cli"
import "git.tebibyte.media/sashakoshka/goparse"
import "git.tebibyte.media/sashakoshka/hopp/generate"
func main() {
name := os.Args[0]
if len(os.Args) != 3 {
fmt.Fprintf(os.Stderr, "Usage: %s SOURCE DESTINATION\n", name)
flagOutput := cli.NewInputFlag('o', "output", "The output file", "", cli.ValString)
flagPackageName := cli.NewInputFlag('p', "package-name", "The package name of the file", "", cli.ValString)
command := cli.New("Compile PDL files to program source code",
flagOutput,
flagPackageName)
command.Syntax = "FILE [OPTIONS]..."
command.ParseOrExit(os.Args)
if len(command.Args) != 1 {
command.Usage()
os.Exit(2)
}
source := os.Args[1]
destination := os.Args[2]
source := command.Args[0]
destination := flagOutput.Value
if destination == "" {
destination = "protocol.go"
}
input, err := os.Open(source)
handleErr(1, err)
handleErr(command, 1, err)
defer input.Close()
protocol, err := generate.ParseReader(source, input)
handleErr(1, err)
handleErr(command, 1, err)
packageName := flagPackageName.Value
if packageName == "" {
absDestination, err := filepath.Abs(destination)
handleErr(1, err)
packageName := cleanPackageName(strings.ReplaceAll(
strings.ToLower(filepath.Base(absDestination)),
" ", "_"))
destination = filepath.Join(os.Args[2], "generated.go")
handleErr(command, 1, err)
base := filepath.Base(absDestination)
if scrounged, ok := scroungeForPackageName(base); ok {
packageName = scrounged
} else {
packageName = strings.ReplaceAll(
strings.ToLower(base),
" ", "_")
}
}
packageName = cleanPackageName(packageName)
output, err := os.Create(destination)
handleErr(1, err)
handleErr(command, 1, err)
generator := generate.Generator {
Output: output,
PackageName: packageName,
}
_, err = generator.Generate(protocol)
handleErr(1, err)
fmt.Fprintf(os.Stderr, "%s: OK\n", name)
handleErr(command, 1, err)
command.Println(output, "OK")
}
func handleErr(code int, err error) {
func handleErr(command *cli.Cli, code int, err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", os.Args[0], parse.Format(err))
command.Errorln(parse.Format(err))
os.Exit(code)
}
}
@@ -61,3 +79,32 @@ func cleanPackageName(str string) string {
}
return string(buffer[:j])
}
func scroungeForPackageName(dir string) (string, bool) {
entries, err := os.ReadDir(dir)
if err != nil { return "", false}
for _, entry := range entries {
if !entry.Type().IsRegular() { continue }
file, err := os.Open(filepath.Join(dir, entry.Name()))
if err != nil { continue }
defer file.Close()
// FIXME: it is entirely possible that the only file will have
// a shitload of doc comments preceeding the package name, and
// those comments are usually huge so this is bad
buffer := [512]byte { }
n, _ := file.Read(buffer[:])
text := string(buffer[:n])
packageIndex := strings.Index(text, "package")
if packageIndex < 0 { continue }
text = text[packageIndex:]
newlineIndex := strings.Index(text, "\n")
if packageIndex > 0 { text = text[:newlineIndex] }
fields := strings.Fields(text)
if len(fields) < 2 { continue }
return fields[1], true
}
return "", false
}

View File

@@ -52,6 +52,7 @@ structures. They are separated by whitespace.
| RBrace | `}` | A right curly brace.
| LBracket | `[` | A left square bracket.
| RBracket | `]` | A right square bracket.
| Comment | `\/\/.*$` | A doc comment starting with a double-slash.
## Syntax
@@ -68,18 +69,27 @@ an Ident token respectively. A message consists of the method code (Method), the
message name (Ident), and the message's root type. This is usually a table, but
can be anything.
Messages, types, and table fields can all have doc comments preceding them,
which are used to generate documentation for the protocol. The syntax is the
same as Go's (for now). Comments aren't allowed anywhere else.
Here is an example of all that:
```
// Connect is sent from the client to the server as the first message of an
// authenticated transaction.
M0000 Connect {
0000 Name String,
0001 Password String,
}
// UserList is sent from the server to the client in response to a Connect
// message.
M0001 UserList {
0000 Users []User,
}
// User holds profile information about a single user.
User {
0000 Name String,
0001 Bio String,
@@ -99,7 +109,7 @@ Below is an EBNF description of the language.
<field> -> <key> <ident> <type>
<type> -> <ident>
| "[" "]" <type>
| "{" (<field> ",")* [<field>] "}"
<message> -> <method> <ident> <type>
<typedef> -> <ident> <type>
| "{" (<comment>* <field> ",")* [<comment>* <field>] "}"
<message> -> <comment>* <method> <ident> <type>
<typedef> -> <comment>* <ident> <type>
```

1
go.mod
View File

@@ -3,6 +3,7 @@ module git.tebibyte.media/sashakoshka/hopp
go 1.23.0
require (
git.tebibyte.media/sashakoshka/go-cli v0.1.3
git.tebibyte.media/sashakoshka/go-util v0.9.1
git.tebibyte.media/sashakoshka/goparse v0.2.0
)

2
go.sum
View File

@@ -1,3 +1,5 @@
git.tebibyte.media/sashakoshka/go-cli v0.1.3 h1:tSkWjyx2JrGu6KotbXWSTKSYGGS1D4O3qwCrRoZuwbs=
git.tebibyte.media/sashakoshka/go-cli v0.1.3/go.mod h1:JFA3wSdRkXxa4iQJWHfe3DokiG7Dh2XUJBzPmuVlbuY=
git.tebibyte.media/sashakoshka/go-util v0.9.1 h1:eGAbLwYhOlh4aq/0w+YnJcxT83yPhXtxnYMzz6K7xGo=
git.tebibyte.media/sashakoshka/go-util v0.9.1/go.mod h1:0Q1t+PePdx6tFYkRuJNcpM1Mru7wE6X+it1kwuOH+6Y=
git.tebibyte.media/sashakoshka/goparse v0.2.0 h1:uQmKvOCV2AOlCHEDjg9uclZCXQZzq2PxaXfZ1aIMiQI=

View File

@@ -252,8 +252,8 @@ func decodeAnyOrError(decoder *Decoder, destination reflect.Value, tag Tag) (n i
// not we receive an empty any value) actually makes it fucking
// work. go figure().
//
// (the map allocation functionality in skeletonPointer has been
// removed)
// (the map allocation functionality in skeletonPointer was
// removed after this comment was written)
value := reflect.MakeMapWithSize(reflect.TypeOf(dummyMap), lengthCast)
destination.Set(value)
destination = value