Compare commits
5 Commits
unify-byte
...
fbc55534f6
| Author | SHA1 | Date | |
|---|---|---|---|
| fbc55534f6 | |||
| b6e180f466 | |||
| 8f5f25780e | |||
| f08213cd49 | |||
| 2194198693 |
@@ -1,48 +1,66 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "os"
|
import "os"
|
||||||
import "fmt"
|
|
||||||
import "strings"
|
import "strings"
|
||||||
import "path/filepath"
|
import "path/filepath"
|
||||||
|
import "git.tebibyte.media/sashakoshka/go-cli"
|
||||||
import "git.tebibyte.media/sashakoshka/goparse"
|
import "git.tebibyte.media/sashakoshka/goparse"
|
||||||
import "git.tebibyte.media/sashakoshka/hopp/generate"
|
import "git.tebibyte.media/sashakoshka/hopp/generate"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
name := os.Args[0]
|
flagOutput := cli.NewInputFlag('o', "output", "The output file", "", cli.ValString)
|
||||||
if len(os.Args) != 3 {
|
flagPackageName := cli.NewInputFlag('p', "package-name", "The package name of the file", "", cli.ValString)
|
||||||
fmt.Fprintf(os.Stderr, "Usage: %s SOURCE DESTINATION\n", name)
|
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)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
source := os.Args[1]
|
source := command.Args[0]
|
||||||
destination := os.Args[2]
|
destination := flagOutput.Value
|
||||||
|
if destination == "" {
|
||||||
|
destination = "protocol.go"
|
||||||
|
}
|
||||||
|
|
||||||
input, err := os.Open(source)
|
input, err := os.Open(source)
|
||||||
handleErr(1, err)
|
handleErr(command, 1, err)
|
||||||
defer input.Close()
|
defer input.Close()
|
||||||
protocol, err := generate.ParseReader(source, input)
|
protocol, err := generate.ParseReader(source, input)
|
||||||
handleErr(1, err)
|
handleErr(command, 1, err)
|
||||||
|
|
||||||
absDestination, err := filepath.Abs(destination)
|
packageName := flagPackageName.Value
|
||||||
handleErr(1, err)
|
if packageName == "" {
|
||||||
packageName := cleanPackageName(strings.ReplaceAll(
|
absDestination, err := filepath.Abs(destination)
|
||||||
strings.ToLower(filepath.Base(absDestination)),
|
handleErr(command, 1, err)
|
||||||
" ", "_"))
|
base := filepath.Base(absDestination)
|
||||||
destination = filepath.Join(os.Args[2], "generated.go")
|
if scrounged, ok := scroungeForPackageName(base); ok {
|
||||||
|
packageName = scrounged
|
||||||
|
} else {
|
||||||
|
packageName = strings.ReplaceAll(
|
||||||
|
strings.ToLower(base),
|
||||||
|
" ", "_")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
packageName = cleanPackageName(packageName)
|
||||||
|
|
||||||
output, err := os.Create(destination)
|
output, err := os.Create(destination)
|
||||||
handleErr(1, err)
|
handleErr(command, 1, err)
|
||||||
generator := generate.Generator {
|
generator := generate.Generator {
|
||||||
Output: output,
|
Output: output,
|
||||||
PackageName: packageName,
|
PackageName: packageName,
|
||||||
}
|
}
|
||||||
_, err = generator.Generate(protocol)
|
_, err = generator.Generate(protocol)
|
||||||
handleErr(1, err)
|
handleErr(command, 1, err)
|
||||||
fmt.Fprintf(os.Stderr, "%s: OK\n", name)
|
command.Println(output, "OK")
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleErr(code int, err error) {
|
func handleErr(command *cli.Cli, code int, err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "%s: %v\n", os.Args[0], parse.Format(err))
|
command.Errorln(parse.Format(err))
|
||||||
os.Exit(code)
|
os.Exit(code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,3 +79,32 @@ func cleanPackageName(str string) string {
|
|||||||
}
|
}
|
||||||
return string(buffer[:j])
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ structures. They are separated by whitespace.
|
|||||||
| RBrace | `}` | A right curly brace.
|
| RBrace | `}` | A right curly brace.
|
||||||
| LBracket | `[` | A left square bracket.
|
| LBracket | `[` | A left square bracket.
|
||||||
| RBracket | `]` | A right square bracket.
|
| RBracket | `]` | A right square bracket.
|
||||||
|
| Comment | `\/\/.*$` | A doc comment starting with a double-slash.
|
||||||
|
|
||||||
## Syntax
|
## 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
|
message name (Ident), and the message's root type. This is usually a table, but
|
||||||
can be anything.
|
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:
|
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 {
|
M0000 Connect {
|
||||||
0000 Name String,
|
0000 Name String,
|
||||||
0001 Password String,
|
0001 Password String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserList is sent from the server to the client in response to a Connect
|
||||||
|
// message.
|
||||||
M0001 UserList {
|
M0001 UserList {
|
||||||
0000 Users []User,
|
0000 Users []User,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// User holds profile information about a single user.
|
||||||
User {
|
User {
|
||||||
0000 Name String,
|
0000 Name String,
|
||||||
0001 Bio String,
|
0001 Bio String,
|
||||||
@@ -99,7 +109,7 @@ Below is an EBNF description of the language.
|
|||||||
<field> -> <key> <ident> <type>
|
<field> -> <key> <ident> <type>
|
||||||
<type> -> <ident>
|
<type> -> <ident>
|
||||||
| "[" "]" <type>
|
| "[" "]" <type>
|
||||||
| "{" (<field> ",")* [<field>] "}"
|
| "{" (<comment>* <field> ",")* [<comment>* <field>] "}"
|
||||||
<message> -> <method> <ident> <type>
|
<message> -> <comment>* <method> <ident> <type>
|
||||||
<typedef> -> <ident> <type>
|
<typedef> -> <comment>* <ident> <type>
|
||||||
```
|
```
|
||||||
|
|||||||
1
go.mod
1
go.mod
@@ -3,6 +3,7 @@ module git.tebibyte.media/sashakoshka/hopp
|
|||||||
go 1.23.0
|
go 1.23.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
git.tebibyte.media/sashakoshka/go-cli v0.1.3
|
||||||
git.tebibyte.media/sashakoshka/go-util v0.9.1
|
git.tebibyte.media/sashakoshka/go-util v0.9.1
|
||||||
git.tebibyte.media/sashakoshka/goparse v0.2.0
|
git.tebibyte.media/sashakoshka/goparse v0.2.0
|
||||||
)
|
)
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -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 h1:eGAbLwYhOlh4aq/0w+YnJcxT83yPhXtxnYMzz6K7xGo=
|
||||||
git.tebibyte.media/sashakoshka/go-util v0.9.1/go.mod h1:0Q1t+PePdx6tFYkRuJNcpM1Mru7wE6X+it1kwuOH+6Y=
|
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=
|
git.tebibyte.media/sashakoshka/goparse v0.2.0 h1:uQmKvOCV2AOlCHEDjg9uclZCXQZzq2PxaXfZ1aIMiQI=
|
||||||
|
|||||||
@@ -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
|
// not we receive an empty any value) actually makes it fucking
|
||||||
// work. go figure().
|
// work. go figure().
|
||||||
//
|
//
|
||||||
// (the map allocation functionality in skeletonPointer has been
|
// (the map allocation functionality in skeletonPointer was
|
||||||
// removed)
|
// removed after this comment was written)
|
||||||
value := reflect.MakeMapWithSize(reflect.TypeOf(dummyMap), lengthCast)
|
value := reflect.MakeMapWithSize(reflect.TypeOf(dummyMap), lengthCast)
|
||||||
destination.Set(value)
|
destination.Set(value)
|
||||||
destination = value
|
destination = value
|
||||||
|
|||||||
Reference in New Issue
Block a user