summaryrefslogtreecommitdiff
path: root/conflang.go
diff options
context:
space:
mode:
Diffstat (limited to 'conflang.go')
-rw-r--r--conflang.go160
1 files changed, 160 insertions, 0 deletions
diff --git a/conflang.go b/conflang.go
new file mode 100644
index 0000000..0b4a856
--- /dev/null
+++ b/conflang.go
@@ -0,0 +1,160 @@
+package main
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+)
+
+type toktype int
+
+const (
+ tText toktype = iota
+ tNextCmd
+)
+
+type token struct {
+ Type toktype
+ Data string
+ Line int
+}
+
+func scan(r io.Reader, tokens chan<- token, errch chan<- error) {
+ emit := func(t toktype, d []byte, line int) {
+ if t == tText && len(d) == 0 {
+ return
+ }
+ tokens <- token{t, string(d), line}
+ }
+
+ err := func() error {
+ br := bufio.NewReader(r)
+
+ escaped := false
+ data := []byte{}
+ line := 1
+
+ for {
+ b, err := br.ReadByte()
+
+ switch err {
+ case nil:
+ case io.EOF:
+ return nil
+ default:
+ return ErrorAtLine{line, err}
+ }
+
+ if b == '\n' {
+ line++
+ }
+
+ if escaped {
+ data = append(data, b)
+ escaped = false
+ continue
+ }
+
+ switch b {
+ case '\\':
+ escaped = true
+ case ' ', '\t':
+ emit(tText, data, line)
+ data = data[:0]
+ case '\n':
+ emit(tText, data, line)
+ data = data[:0]
+ emit(tNextCmd, nil, line)
+ default:
+ data = append(data, b)
+ }
+ }
+
+ emit(tText, data, line)
+ emit(tNextCmd, nil, line)
+ return nil
+ }()
+
+ close(tokens)
+ errch <- err
+}
+
+type command struct {
+ Name string
+ Params []string
+ Line int
+}
+
+func parse(tokens <-chan token, cmds chan<- command) {
+ defer close(cmds)
+
+ startcmd := true
+ cmd := command{"", make([]string, 0), 0}
+
+ for tok := range tokens {
+ switch tok.Type {
+ case tText:
+ if startcmd {
+ cmd.Name = tok.Data
+ cmd.Line = tok.Line
+ startcmd = false
+ } else {
+ cmd.Params = append(cmd.Params, tok.Data)
+ }
+ case tNextCmd:
+ if !startcmd {
+ cmds <- cmd
+ cmd.Name = ""
+ cmd.Params = make([]string, 0)
+ startcmd = true
+ }
+ }
+ }
+}
+
+type cmdfunc func(params []string) error
+
+var commands = map[string]cmdfunc{
+ "nop": func(_ []string) error { return nil },
+}
+
+func RegisterCommand(name string, f cmdfunc) {
+ commands[name] = f
+}
+
+type ErrorAtLine struct {
+ Line int
+ Err error
+}
+
+func (err ErrorAtLine) Error() string {
+ return fmt.Sprintf("%s (at line %d)", err.Err, err.Line)
+}
+
+type CommandNotFound string
+
+func (c CommandNotFound) Error() string {
+ return fmt.Sprintf("Command \"%s\" not found", c)
+}
+
+func RunCommands(r io.Reader) error {
+ errch := make(chan error)
+ tokens := make(chan token)
+ cmds := make(chan command)
+
+ go scan(r, tokens, errch)
+ go parse(tokens, cmds)
+
+ for cmd := range cmds {
+ f, ok := commands[cmd.Name]
+ if !ok {
+ return ErrorAtLine{cmd.Line, CommandNotFound(cmd.Name)}
+ }
+
+ if err := f(cmd.Params); err != nil {
+ return ErrorAtLine{cmd.Line, err}
+ }
+ }
+
+ return <-errch
+}