// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build ignore

// Generate system call table for DragonFly, NetBSD,
// FreeBSD or OpenBSD from master list (for example,
// /usr/src/sys/kern/syscalls.master or sys/syscall.h).
package main

import (
	"bufio"
	"fmt"
	"io"
	"net/http"
	"os"
	"regexp"
	"strings"
)

var (
	goos, goarch string
)

// cmdLine returns this programs's commandline arguments
func cmdLine() string {
	return "go run mksysnum.go " + strings.Join(os.Args[1:], " ")
}

// goBuildTags returns build tags in the go:build format.
func goBuildTags() string {
	return fmt.Sprintf("%s && %s", goarch, goos)
}

func checkErr(err error) {
	if err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		os.Exit(1)
	}
}

// source string and substring slice for regexp
type re struct {
	str string   // source string
	sub []string // matched sub-string
}

// Match performs regular expression match
func (r *re) Match(exp string) bool {
	r.sub = regexp.MustCompile(exp).FindStringSubmatch(r.str)
	if r.sub != nil {
		return true
	}
	return false
}

// fetchFile fetches a text file from URL
func fetchFile(URL string) io.Reader {
	resp, err := http.Get(URL)
	checkErr(err)
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	checkErr(err)
	return strings.NewReader(string(body))
}

// readFile reads a text file from path
func readFile(path string) io.Reader {
	file, err := os.Open(os.Args[1])
	checkErr(err)
	return file
}

func format(name, num, proto string) string {
	name = strings.ToUpper(name)
	// There are multiple entries for enosys and nosys, so comment them out.
	nm := re{str: name}
	if nm.Match(`^SYS_E?NOSYS$`) {
		name = fmt.Sprintf("// %s", name)
	}
	if name == `SYS_SYS_EXIT` {
		name = `SYS_EXIT`
	}
	return fmt.Sprintf("	%s = %s;  // %s\n", name, num, proto)
}

func main() {
	// Get the OS (using GOOS_TARGET if it exist)
	goos = os.Getenv("GOOS_TARGET")
	if goos == "" {
		goos = os.Getenv("GOOS")
	}
	// Get the architecture (using GOARCH_TARGET if it exists)
	goarch = os.Getenv("GOARCH_TARGET")
	if goarch == "" {
		goarch = os.Getenv("GOARCH")
	}
	// Check if GOOS and GOARCH environment variables are defined
	if goarch == "" || goos == "" {
		fmt.Fprintf(os.Stderr, "GOARCH or GOOS not defined in environment\n")
		os.Exit(1)
	}

	file := strings.TrimSpace(os.Args[1])
	var syscalls io.Reader
	if strings.HasPrefix(file, "https://") || strings.HasPrefix(file, "http://") {
		// Download syscalls.master file
		syscalls = fetchFile(file)
	} else {
		syscalls = readFile(file)
	}

	var text, line string
	s := bufio.NewScanner(syscalls)
	for s.Scan() {
		t := re{str: line}
		if t.Match(`^(.*)\\$`) {
			// Handle continuation
			line = t.sub[1]
			line += strings.TrimLeft(s.Text(), " \t")
		} else {
			// New line
			line = s.Text()
		}
		t = re{str: line}
		if t.Match(`\\$`) {
			continue
		}
		t = re{str: line}

		switch goos {
		case "dragonfly":
			if t.Match(`^([0-9]+)\s+STD\s+({ \S+\s+(\w+).*)$`) {
				num, proto := t.sub[1], t.sub[2]
				name := fmt.Sprintf("SYS_%s", t.sub[3])
				text += format(name, num, proto)
			}
		case "freebsd":
			if t.Match(`^([0-9]+)\s+\S+\s+(?:(?:NO)?STD)\s+({ \S+\s+(\w+).*)$`) {
				num, proto := t.sub[1], t.sub[2]
				name := fmt.Sprintf("SYS_%s", t.sub[3])
				// remove whitespace around parens
				proto = regexp.MustCompile(`\( `).ReplaceAllString(proto, "(")
				proto = regexp.MustCompile(` \)`).ReplaceAllString(proto, ")")
				// remove SAL 2.0 annotations
				proto = regexp.MustCompile(`_In[^ ]*[_)] `).ReplaceAllString(proto, "")
				proto = regexp.MustCompile(`_Out[^ ]*[_)] `).ReplaceAllString(proto, "")
				// remove double spaces at the source
				proto = regexp.MustCompile(`\s{2}`).ReplaceAllString(proto, " ")
				text += format(name, num, proto)
			}
		case "openbsd":
			if t.Match(`^([0-9]+)\s+STD\s+(NOLOCK\s+)?({ \S+\s+\*?(\w+).*)$`) {
				num, proto, name := t.sub[1], t.sub[3], t.sub[4]
				text += format(name, num, proto)
			}
		case "netbsd":
			if t.Match(`^([0-9]+)\s+((STD)|(NOERR))\s+(RUMP\s+)?({\s+\S+\s*\*?\s*\|(\S+)\|(\S*)\|(\w+).*\s+})(\s+(\S+))?$`) {
				num, proto, compat := t.sub[1], t.sub[6], t.sub[8]
				name := t.sub[7] + "_" + t.sub[9]
				if t.sub[11] != "" {
					name = t.sub[7] + "_" + t.sub[11]
				}
				name = strings.ToUpper(name)
				if compat == "" || compat == "13" || compat == "30" || compat == "50" {
					text += fmt.Sprintf("	%s = %s;  // %s\n", name, num, proto)
				}
			}
		default:
			fmt.Fprintf(os.Stderr, "unrecognized GOOS=%s\n", goos)
			os.Exit(1)

		}
	}
	err := s.Err()
	checkErr(err)

	fmt.Printf(template, cmdLine(), goBuildTags(), text)
}

const template = `// %s
// Code generated by the command above; see README.md. DO NOT EDIT.

//go:build %s

package unix

const(
%s)`
