Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9370f9c3ee |
+101
-23
@@ -28,7 +28,7 @@ import (
|
||||
|
||||
// version is the release version, stamped at build time via
|
||||
// -ldflags "-X main.version=…" (defaulting to the current release).
|
||||
var version = "0.7.0"
|
||||
var version = "0.8.0"
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
@@ -49,31 +49,71 @@ func main() {
|
||||
case "lsp":
|
||||
os.Exit(cmdLSP(os.Args[2:]))
|
||||
case "version", "--version", "-V":
|
||||
fmt.Printf("gasm %s\n", version)
|
||||
case "help", "-h", "--help":
|
||||
os.Exit(cmdVersion())
|
||||
case "help", "--help", "-h":
|
||||
usage(os.Stdout)
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "gasm: unknown command %q\n\n", os.Args[1])
|
||||
usage(os.Stderr)
|
||||
fmt.Fprintf(os.Stderr, "gasm: unknown command %q — run \"gasm --help\" for usage\n", os.Args[1])
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
||||
|
||||
// cmdVersion prints the release version.
|
||||
func cmdVersion() int {
|
||||
fmt.Printf("gasm %s\n", version)
|
||||
return 0
|
||||
}
|
||||
|
||||
func usage(w io.Writer) {
|
||||
fmt.Fprintf(w, `gasm %s — developer tooling for Go's Plan 9 assembler
|
||||
fmt.Fprintf(w, `gasm %s — developer tooling for Go's Plan 9 assembler (GAsm)
|
||||
|
||||
gasm bundles a lexer, parser, formatter, linter, standalone assembler and
|
||||
language server for Plan 9 assembly into one self-contained binary.
|
||||
|
||||
Usage:
|
||||
gasm tokens <file> print the lexical token stream
|
||||
gasm parse <file> parse and report syntax errors
|
||||
gasm fmt [-w] [path...] canonicalise formatting (no path or a directory:
|
||||
reformat every .s below it in place, like go fmt)
|
||||
gasm lint <file...> run static checks
|
||||
gasm asm [-o out.bin] <file> assemble to machine code (amd64, Phase 2)
|
||||
gasm lsp run the language server over stdio
|
||||
gasm version print the version
|
||||
gasm <command> [arguments]
|
||||
gasm [flags]
|
||||
|
||||
Commands:
|
||||
tokens print the lexical token stream
|
||||
parse parse and report syntax errors
|
||||
fmt canonicalise formatting (gofmt for assembly)
|
||||
lint run static checks
|
||||
asm assemble .s files to machine code (amd64)
|
||||
lsp run the language server over stdio
|
||||
version print the version (same as --version)
|
||||
|
||||
Flags:
|
||||
-h, --help show this help
|
||||
-V, --version print the version
|
||||
|
||||
Run "gasm <command> -h" for a command's usage and flags.
|
||||
|
||||
Examples:
|
||||
gasm fmt reformat every .s below the current directory
|
||||
gasm lint go-flac/*.s run static checks over the kernels
|
||||
gasm asm -o k.bin kern_amd64.s
|
||||
`, version)
|
||||
}
|
||||
|
||||
// newCommand returns the FlagSet of a subcommand whose -h/--help prints a
|
||||
// proper usage block: the one-line usage, the long description and the flag
|
||||
// defaults. The flag package routes -h/--help to fs.Usage and exits 0.
|
||||
func newCommand(name, usageLine, long string) *flag.FlagSet {
|
||||
fs := flag.NewFlagSet(name, flag.ExitOnError)
|
||||
fs.Usage = func() {
|
||||
w := fs.Output()
|
||||
fmt.Fprintf(w, "Usage: %s\n\n%s\n", usageLine, strings.TrimSpace(long))
|
||||
hasFlags := false
|
||||
fs.VisitAll(func(*flag.Flag) { hasFlags = true })
|
||||
if hasFlags {
|
||||
fmt.Fprintln(w, "\nFlags:")
|
||||
fs.PrintDefaults()
|
||||
}
|
||||
}
|
||||
return fs
|
||||
}
|
||||
|
||||
// readSource returns the contents of path, or stdin when path is "-".
|
||||
func readSource(path string) (string, error) {
|
||||
if path == "-" {
|
||||
@@ -85,7 +125,10 @@ func readSource(path string) (string, error) {
|
||||
}
|
||||
|
||||
func cmdTokens(args []string) int {
|
||||
fs := flag.NewFlagSet("tokens", flag.ExitOnError)
|
||||
fs := newCommand("tokens", "gasm tokens <file>", `
|
||||
Print the lexical token stream of FILE: position, token kind and text, one
|
||||
token per line. FILE may be "-" to read standard input.
|
||||
`)
|
||||
fs.Parse(args)
|
||||
if fs.NArg() != 1 {
|
||||
fmt.Fprintln(os.Stderr, "usage: gasm tokens <file>")
|
||||
@@ -103,7 +146,11 @@ func cmdTokens(args []string) int {
|
||||
}
|
||||
|
||||
func cmdParse(args []string) int {
|
||||
fs := flag.NewFlagSet("parse", flag.ExitOnError)
|
||||
fs := newCommand("parse", "gasm parse <file>", `
|
||||
Parse FILE and report syntax errors on stderr. On success, print how many
|
||||
declarations and TEXT functions the file contains. FILE may be "-" to read
|
||||
standard input.
|
||||
`)
|
||||
fs.Parse(args)
|
||||
if fs.NArg() != 1 {
|
||||
fmt.Fprintln(os.Stderr, "usage: gasm parse <file>")
|
||||
@@ -133,14 +180,24 @@ func cmdParse(args []string) int {
|
||||
}
|
||||
|
||||
func cmdFmt(args []string) int {
|
||||
fs_ := flag.NewFlagSet("fmt", flag.ExitOnError)
|
||||
write := fs_.Bool("w", false, "write result to the source file")
|
||||
fs_.Parse(args)
|
||||
fs := newCommand("fmt", "gasm fmt [-w] [path...]", `
|
||||
Canonicalise the formatting of Plan 9 assembly sources: indentation, operand
|
||||
spacing, per-function mnemonic alignment and blank-line layout (exactly one
|
||||
blank line before each label, TEXT and GLOBL block). Formatting is
|
||||
idempotent and preserves every line, comments included.
|
||||
|
||||
With no paths — or a directory path — every .s file below it is reformatted
|
||||
in place and the changed files are listed, the way go fmt does; "." and "_"
|
||||
directories are skipped. Explicit file paths print to stdout unless -w is
|
||||
given.
|
||||
`)
|
||||
write := fs.Bool("w", false, "write result to the source file")
|
||||
fs.Parse(args)
|
||||
// Like go fmt: with no arguments, or with a directory argument, every .s
|
||||
// file below the directory is formatted in place and the names of the
|
||||
// changed files are listed; explicit file arguments keep the -w / stdout
|
||||
// behaviour.
|
||||
paths := fs_.Args()
|
||||
paths := fs.Args()
|
||||
dirMode := len(paths) == 0
|
||||
if dirMode {
|
||||
paths = []string{"."}
|
||||
@@ -215,7 +272,16 @@ func asmFiles(dir string) ([]string, error) {
|
||||
}
|
||||
|
||||
func cmdLint(args []string) int {
|
||||
fs := flag.NewFlagSet("lint", flag.ExitOnError)
|
||||
fs := newCommand("lint", "gasm lint <file...>", `
|
||||
Run the static checks over the given files and print diagnostics as
|
||||
"file:line:col: severity: message [code]". The exit status is non-zero when
|
||||
an error-severity diagnostic is found; warnings (e.g. the register-clobber
|
||||
audit) do not affect it.
|
||||
|
||||
Rules include unknown-instruction, operand-count, undefined-label,
|
||||
duplicate-label, missing-ret, missing-textflag-include, abi-argsize,
|
||||
unreachable-code, register-clobber and funcdata-pcdata.
|
||||
`)
|
||||
disable := fs.String("disable", "", "comma-separated rule codes to disable")
|
||||
fs.Parse(args)
|
||||
if fs.NArg() == 0 {
|
||||
@@ -256,7 +322,13 @@ func cmdLint(args []string) int {
|
||||
}
|
||||
|
||||
func cmdLSP(args []string) int {
|
||||
fs := flag.NewFlagSet("lsp", flag.ExitOnError)
|
||||
fs := newCommand("lsp", "gasm lsp", `
|
||||
Run the language server over standard input/output: JSON-RPC 2.0 with
|
||||
Content-Length framing. Point an LSP-capable editor at the binary and
|
||||
associate it with .s files; the target architecture is inferred from the file
|
||||
suffix (_amd64.s, _arm64.s, _riscv64.s, _loong64.s). Provides completion,
|
||||
hover, document symbols, diagnostics and semantic-token highlighting.
|
||||
`)
|
||||
fs.Parse(args)
|
||||
srv := lsp.New(os.Stdin, os.Stdout)
|
||||
if err := srv.Run(); err != nil {
|
||||
@@ -267,7 +339,13 @@ func cmdLSP(args []string) int {
|
||||
}
|
||||
|
||||
func cmdAsm(args []string) int {
|
||||
fs := flag.NewFlagSet("asm", flag.ExitOnError)
|
||||
fs := newCommand("asm", "gasm asm [-o out.bin] <file>", `
|
||||
Assemble FILE (amd64) without the Go toolchain: every TEXT function is
|
||||
encoded to machine code — scalar, VEX/AVX2 and EVEX/AVX-512 instructions,
|
||||
FP/SP frame mapping, local labels and file-local static symbols (GLOBL/DATA)
|
||||
resolved RIP-relative — and printed as a hex dump. With -o the concatenated
|
||||
image (functions followed by the data section) is written to a file instead.
|
||||
`)
|
||||
out := fs.String("o", "", "write the concatenated machine code to this file")
|
||||
fs.Parse(args)
|
||||
if fs.NArg() != 1 {
|
||||
|
||||
+18
-2
@@ -200,8 +200,24 @@ func TestCmdFmtWrite(t *testing.T) {
|
||||
func TestUsage(t *testing.T) {
|
||||
var b bytes.Buffer
|
||||
usage(&b)
|
||||
if !strings.Contains(b.String(), "gasm") {
|
||||
t.Errorf("usage text unexpected:\n%s", b.String())
|
||||
out := b.String()
|
||||
for _, want := range []string{
|
||||
"gasm", "Commands:", "Flags:", "--help", "--version",
|
||||
"tokens", "parse", "fmt", "lint", "asm", "lsp", "version",
|
||||
} {
|
||||
if !strings.Contains(out, want) {
|
||||
t.Errorf("usage text missing %q:\n%s", want, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCmdVersion(t *testing.T) {
|
||||
out, _, code := capture(func() int { return cmdVersion() })
|
||||
if code != 0 {
|
||||
t.Fatalf("code = %d", code)
|
||||
}
|
||||
if !strings.Contains(out, version) {
|
||||
t.Errorf("version output %q does not mention %q", out, version)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user