// Inferno utils/6a/a.h and lex.c.
// http://code.google.com/p/inferno-os/source/browse/utils/6a/a.h
// http://code.google.com/p/inferno-os/source/browse/utils/6a/lex.c
//
//	Copyright © 1994-1999 Lucent Technologies Inc.	All rights reserved.
//	Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net)
//	Portions Copyright © 1997-1999 Vita Nuova Limited
//	Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com)
//	Portions Copyright © 2004,2006 Bruce Ellis
//	Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net)
//	Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others
//	Portions Copyright © 2009 The Go Authors.  All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

// Package asm holds code shared among the assemblers.
package asm

import (
	"flag"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"cmd/internal/obj"
)

// Initialized by client.
var (
	LSCONST int
	LCONST  int
	LFCONST int
	LNAME   int
	LVAR    int
	LLAB    int

	Thechar     rune
	Thestring   string
	Thelinkarch *obj.LinkArch

	Arches map[string]*obj.LinkArch

	Cclean  func()
	Yyparse func()
	Syminit func(*Sym)

	Lexinit []Lextab
)

type Lextab struct {
	Name  string
	Type  int
	Value int64
}

const (
	MAXALIGN = 7
	FPCHIP   = 1
	NSYMB    = 500
	BUFSIZ   = 8192
	HISTSZ   = 20
	EOF      = -1
	IGN      = -2
	NHASH    = 503
	NMACRO   = 10
)

const (
	CLAST = iota
	CMACARG
	CMACRO
	CPREPROC
)

type Macro struct {
	Text string
	Narg int
	Dots bool
}

type Sym struct {
	Link      *Sym
	Ref       *Ref
	Macro     *Macro
	Value     int64
	Type      int
	Name      string
	Labelname string
	Sym       int8
}

type Ref struct {
	Class int
}

type Io struct {
	Link *Io
	P    []byte
	F    *os.File
	B    [1024]byte
}

var fi struct {
	P []byte
}

var (
	debug    [256]int
	hash     = map[string]*Sym{}
	Dlist    []string
	newflag  int
	hunk     string
	include  []string
	iofree   *Io
	ionext   *Io
	iostack  *Io
	Lineno   int32
	nerrors  int
	nhunk    int32
	ninclude int
	nsymb    int32
	nullgen  obj.Addr
	outfile  string
	Pass     int
	PC       int32
	peekc    int = IGN
	sym      int
	symb     string
	thunk    int32
	obuf     obj.Biobuf
	Ctxt     *obj.Link
	bstdout  obj.Biobuf
)

func dodef(p string) {
	Dlist = append(Dlist, p)
}

func usage() {
	fmt.Printf("usage: %ca [options] file.c...\n", Thechar)
	flag.PrintDefaults()
	errorexit()
}

func Main() {
	// Allow GOARCH=Thestring or GOARCH=Thestringsuffix,
	// but not other values.
	p := obj.Getgoarch()

	if !strings.HasPrefix(p, Thestring) {
		log.Fatalf("cannot use %cc with GOARCH=%s", Thechar, p)
	}
	if p != Thestring {
		Thelinkarch = Arches[p]
		if Thelinkarch == nil {
			log.Fatalf("unknown arch %s", p)
		}
	}

	Ctxt = obj.Linknew(Thelinkarch)
	Ctxt.Diag = Yyerror
	Ctxt.Bso = &bstdout
	Ctxt.Enforce_data_order = 1
	bstdout = *obj.Binitw(os.Stdout)

	debug = [256]int{}
	cinit()
	outfile = ""
	setinclude(".")

	flag.Var(flagFn(dodef), "D", "name[=value]: add #define")
	flag.Var(flagFn(setinclude), "I", "dir: add dir to include path")
	flag.Var((*count)(&debug['S']), "S", "print assembly and machine code")
	flag.Var((*count)(&debug['m']), "m", "debug preprocessor macros")
	flag.StringVar(&outfile, "o", "", "file: set output file")
	flag.StringVar(&Ctxt.LineHist.TrimPathPrefix, "trimpath", "", "prefix: remove prefix from recorded source file paths")

	flag.Parse()

	Ctxt.Debugasm = int32(debug['S'])

	if flag.NArg() < 1 {
		usage()
	}
	if flag.NArg() > 1 {
		fmt.Printf("can't assemble multiple files\n")
		errorexit()
	}

	if assemble(flag.Arg(0)) != 0 {
		errorexit()
	}
	bstdout.Flush()
	if nerrors > 0 {
		errorexit()
	}
}

func assemble(file string) int {
	if outfile == "" {
		outfile = strings.TrimSuffix(filepath.Base(file), ".s") + "." + string(Thechar)
	}

	of, err := os.Create(outfile)
	if err != nil {
		Yyerror("%ca: cannot create %s", Thechar, outfile)
		errorexit()
	}

	obuf = *obj.Binitw(of)
	fmt.Fprintf(&obuf, "go object %s %s %s\n", obj.Getgoos(), obj.Getgoarch(), obj.Getgoversion())
	fmt.Fprintf(&obuf, "!\n")

	var i int
	for Pass = 1; Pass <= 2; Pass++ {
		pinit(file)
		for i = 0; i < len(Dlist); i++ {
			dodefine(Dlist[i])
		}
		Yyparse()
		Cclean()
		if nerrors != 0 {
			return nerrors
		}
	}

	obj.Writeobjdirect(Ctxt, &obuf)
	obuf.Flush()
	return 0
}

func cinit() {
	for i := 0; i < len(Lexinit); i++ {
		s := Lookup(Lexinit[i].Name)
		if s.Type != LNAME {
			Yyerror("double initialization %s", Lexinit[i].Name)
		}
		s.Type = Lexinit[i].Type
		s.Value = Lexinit[i].Value
	}
}

func syminit(s *Sym) {
	s.Type = LNAME
	s.Value = 0
}

type flagFn func(string)

func (flagFn) String() string {
	return "<arg>"
}

func (f flagFn) Set(s string) error {
	f(s)
	return nil
}

type yyImpl struct{}

// count is a flag.Value that is like a flag.Bool and a flag.Int.
// If used as -name, it increments the count, but -name=x sets the count.
// Used for verbose flag -v.
type count int

func (c *count) String() string {
	return fmt.Sprint(int(*c))
}

func (c *count) Set(s string) error {
	switch s {
	case "true":
		*c++
	case "false":
		*c = 0
	default:
		n, err := strconv.Atoi(s)
		if err != nil {
			return fmt.Errorf("invalid count %q", s)
		}
		*c = count(n)
	}
	return nil
}

func (c *count) IsBoolFlag() bool {
	return true
}
