init
This commit is contained in:
commit
8f4f858d86
30 changed files with 9765 additions and 0 deletions
247
cmd/gotlinc/main.go
Normal file
247
cmd/gotlinc/main.go
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"gotlin/internal/lang"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
usage()
|
||||
}
|
||||
|
||||
switch os.Args[1] {
|
||||
case "build":
|
||||
runBuild(os.Args[2:])
|
||||
case "run":
|
||||
runRun(os.Args[2:])
|
||||
default:
|
||||
runBuild(os.Args[1:])
|
||||
}
|
||||
}
|
||||
|
||||
func runBuild(args []string) {
|
||||
fs := flag.NewFlagSet("build", flag.ExitOnError)
|
||||
srcOnly := fs.Bool("src", false, "emit Go source instead of building an executable")
|
||||
outPath := fs.String("o", "", "output file path")
|
||||
fs.Usage = func() {
|
||||
fmt.Fprintln(os.Stderr, "usage: gotlinc build [-src] [-o output] <input.gt>")
|
||||
}
|
||||
normalizedArgs, err := normalizeBuildArgs(args)
|
||||
if err != nil {
|
||||
fail(err)
|
||||
}
|
||||
if err := fs.Parse(normalizedArgs); err != nil {
|
||||
fail(err)
|
||||
}
|
||||
if fs.NArg() != 1 {
|
||||
fs.Usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
inputPath := fs.Arg(0)
|
||||
goSrc := compileFile(inputPath, !*srcOnly)
|
||||
|
||||
if *srcOnly {
|
||||
if *outPath == "" {
|
||||
_, _ = os.Stdout.WriteString(goSrc)
|
||||
return
|
||||
}
|
||||
if err := os.WriteFile(*outPath, []byte(goSrc), 0o644); err != nil {
|
||||
fail(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
outputPath := *outPath
|
||||
if outputPath == "" {
|
||||
outputPath = defaultExecutablePath(inputPath)
|
||||
}
|
||||
|
||||
if err := buildExecutable(goSrc, outputPath); err != nil {
|
||||
fail(err)
|
||||
}
|
||||
}
|
||||
|
||||
func runRun(args []string) {
|
||||
fs := flag.NewFlagSet("run", flag.ExitOnError)
|
||||
fs.Usage = func() {
|
||||
fmt.Fprintln(os.Stderr, "usage: gotlinc run <input.gt> [program args...]")
|
||||
}
|
||||
if err := fs.Parse(args); err != nil {
|
||||
fail(err)
|
||||
}
|
||||
if fs.NArg() < 1 {
|
||||
fs.Usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
inputPath := fs.Arg(0)
|
||||
goSrc := compileFile(inputPath, true)
|
||||
|
||||
tmpFile, err := os.CreateTemp("", "gotlinc-*.go")
|
||||
if err != nil {
|
||||
fail(err)
|
||||
}
|
||||
tmpPath := tmpFile.Name()
|
||||
defer os.Remove(tmpPath)
|
||||
|
||||
if _, err := tmpFile.WriteString(goSrc); err != nil {
|
||||
_ = tmpFile.Close()
|
||||
fail(err)
|
||||
}
|
||||
if err := tmpFile.Close(); err != nil {
|
||||
fail(err)
|
||||
}
|
||||
|
||||
cmdArgs := append([]string{"run", tmpPath}, fs.Args()[1:]...)
|
||||
cmd := exec.Command("go", cmdArgs...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdin = os.Stdin
|
||||
if err := cmd.Run(); err != nil {
|
||||
fail(err)
|
||||
}
|
||||
}
|
||||
|
||||
func compileFile(inputPath string, forceMain bool) string {
|
||||
src, err := os.ReadFile(inputPath)
|
||||
if err != nil {
|
||||
fail(err)
|
||||
}
|
||||
|
||||
program, err := lang.Parse(string(src))
|
||||
if err != nil {
|
||||
fail(err)
|
||||
}
|
||||
|
||||
var goSrc []byte
|
||||
if forceMain {
|
||||
goSrc, err = lang.GenerateGoMain(program)
|
||||
} else {
|
||||
goSrc, err = lang.GenerateGo(program)
|
||||
}
|
||||
if err != nil {
|
||||
fail(err)
|
||||
}
|
||||
if forceMain {
|
||||
return addBestEffortLineDirectives(string(goSrc), inputPath, string(src))
|
||||
}
|
||||
return string(goSrc)
|
||||
}
|
||||
|
||||
func buildExecutable(goSrc string, outputPath string) error {
|
||||
tmpFile, err := os.CreateTemp("", "gotlinc-build-*.go")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tmpPath := tmpFile.Name()
|
||||
defer os.Remove(tmpPath)
|
||||
|
||||
if _, err := tmpFile.WriteString(goSrc); err != nil {
|
||||
_ = tmpFile.Close()
|
||||
return err
|
||||
}
|
||||
if err := tmpFile.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := exec.Command("go", "build", "-o", outputPath, tmpPath)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdin = os.Stdin
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func defaultExecutablePath(inputPath string) string {
|
||||
base := strings.TrimSuffix(filepath.Base(inputPath), filepath.Ext(inputPath))
|
||||
if runtime.GOOS == "windows" && filepath.Ext(base) != ".exe" {
|
||||
base += ".exe"
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
func normalizeBuildArgs(args []string) ([]string, error) {
|
||||
var flags []string
|
||||
var positional []string
|
||||
|
||||
for i := 0; i < len(args); i++ {
|
||||
arg := args[i]
|
||||
switch {
|
||||
case arg == "-src":
|
||||
flags = append(flags, arg)
|
||||
case arg == "-o":
|
||||
if i+1 >= len(args) {
|
||||
return nil, fmt.Errorf("missing value for -o")
|
||||
}
|
||||
flags = append(flags, arg, args[i+1])
|
||||
i++
|
||||
case strings.HasPrefix(arg, "-o="):
|
||||
flags = append(flags, arg)
|
||||
case strings.HasPrefix(arg, "-"):
|
||||
flags = append(flags, arg)
|
||||
default:
|
||||
positional = append(positional, arg)
|
||||
}
|
||||
}
|
||||
|
||||
return append(flags, positional...), nil
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintln(os.Stderr, "usage:")
|
||||
fmt.Fprintln(os.Stderr, " gotlinc build [-src] [-o output] <input.gt>")
|
||||
fmt.Fprintln(os.Stderr, " gotlinc run <input.gt> [program args...]")
|
||||
fmt.Fprintln(os.Stderr, "")
|
||||
fmt.Fprintln(os.Stderr, "default command: build")
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
func fail(err error) {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func addBestEffortLineDirectives(goSrc string, inputPath string, gtSrc string) string {
|
||||
fnLines := sourceFunctionLines(gtSrc)
|
||||
if len(fnLines) == 0 {
|
||||
return goSrc
|
||||
}
|
||||
|
||||
pattern := regexp.MustCompile(`^\s*func\s+(?:\(\s*[^)]*\)\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*\(`)
|
||||
lines := strings.Split(goSrc, "\n")
|
||||
out := make([]string, 0, len(lines)+len(fnLines))
|
||||
for _, line := range lines {
|
||||
m := pattern.FindStringSubmatch(line)
|
||||
if len(m) == 2 {
|
||||
if gtLine, ok := fnLines[m[1]]; ok {
|
||||
out = append(out, fmt.Sprintf("//line %s:%d", inputPath, gtLine))
|
||||
}
|
||||
}
|
||||
out = append(out, line)
|
||||
}
|
||||
return strings.Join(out, "\n")
|
||||
}
|
||||
|
||||
func sourceFunctionLines(src string) map[string]int {
|
||||
out := map[string]int{}
|
||||
pattern := regexp.MustCompile(`^\s*fun\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(`)
|
||||
lines := strings.Split(src, "\n")
|
||||
for i, line := range lines {
|
||||
m := pattern.FindStringSubmatch(line)
|
||||
if len(m) == 2 {
|
||||
if _, exists := out[m[1]]; !exists {
|
||||
out[m[1]] = i + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue