// run

// Copyright 2009 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.

// Test general operation using s-list.
// First Go program ever run (although not in this exact form).

package main

import "fmt"

const nilchar = 0

type Atom struct {
	str     string
	integer int
	next    *Slist /* in hash bucket */
}

type List struct {
	car *Slist
	cdr *Slist
}

type Slist struct {
	isatom   bool
	isstring bool
	//union {
	atom Atom
	list List
	//} u;

}

func (this *Slist) Car() *Slist {
	return this.list.car
}

func (this *Slist) Cdr() *Slist {
	return this.list.cdr
}

func (this *Slist) String() string {
	return this.atom.str
}

func (this *Slist) Integer() int {
	return this.atom.integer
}

func (slist *Slist) Free() {
	if slist == nil {
		return
	}
	if slist.isatom {
		//		free(slist.String());
	} else {
		slist.Car().Free()
		slist.Cdr().Free()
	}
	//	free(slist);
}

//Slist* atom(byte *s, int i);

var token int
var peekc int = -1
var lineno int32 = 1

var input string
var inputindex int = 0
var tokenbuf [100]byte
var tokenlen int = 0

const EOF int = -1

func main() {
	var list *Slist

	OpenFile()
	for {
		list = Parse()
		if list == nil {
			break
		}
		r := list.Print()
		list.Free()
		if r != "(defn foo (add 12 34))" {
			panic(r)
		}
		break
	}
}

func (slist *Slist) PrintOne(doparen bool) string {
	if slist == nil {
		return ""
	}
	var r string
	if slist.isatom {
		if slist.isstring {
			r = slist.String()
		} else {
			r = fmt.Sprintf("%v", slist.Integer())
		}
	} else {
		if doparen {
			r += "("
		}
		r += slist.Car().PrintOne(true)
		if slist.Cdr() != nil {
			r += " "
			r += slist.Cdr().PrintOne(false)
		}
		if doparen {
			r += ")"
		}
	}
	return r
}

func (slist *Slist) Print() string {
	return slist.PrintOne(true)
}

func Get() int {
	var c int

	if peekc >= 0 {
		c = peekc
		peekc = -1
	} else {
		c = int(input[inputindex])
		inputindex++
		if c == '\n' {
			lineno = lineno + 1
		}
		if c == nilchar {
			inputindex = inputindex - 1
			c = EOF
		}
	}
	return c
}

func WhiteSpace(c int) bool {
	return c == ' ' || c == '\t' || c == '\r' || c == '\n'
}

func NextToken() {
	var i, c int

	tokenbuf[0] = nilchar // clear previous token
	c = Get()
	for WhiteSpace(c) {
		c = Get()
	}
	switch c {
	case EOF:
		token = EOF
	case '(', ')':
		token = c
		break
	default:
		for i = 0; i < 100-1; { // sizeof tokenbuf - 1
			tokenbuf[i] = byte(c)
			i = i + 1
			c = Get()
			if c == EOF {
				break
			}
			if WhiteSpace(c) || c == ')' {
				peekc = c
				break
			}
		}
		if i >= 100-1 { // sizeof tokenbuf - 1
			panic("atom too long\n")
		}
		tokenlen = i
		tokenbuf[i] = nilchar
		if '0' <= tokenbuf[0] && tokenbuf[0] <= '9' {
			token = '0'
		} else {
			token = 'A'
		}
	}
}

func Expect(c int) {
	if token != c {
		print("parse error: expected ", c, "\n")
		panic("parse")
	}
	NextToken()
}

// Parse a non-parenthesized list up to a closing paren or EOF
func ParseList() *Slist {
	var slist, retval *Slist

	slist = new(Slist)
	slist.list.car = nil
	slist.list.cdr = nil
	slist.isatom = false
	slist.isstring = false

	retval = slist
	for {
		slist.list.car = Parse()
		if token == ')' || token == EOF { // empty cdr
			break
		}
		slist.list.cdr = new(Slist)
		slist = slist.list.cdr
	}
	return retval
}

func atom(i int) *Slist { // BUG: uses tokenbuf; should take argument)
	var slist *Slist

	slist = new(Slist)
	if token == '0' {
		slist.atom.integer = i
		slist.isstring = false
	} else {
		slist.atom.str = string(tokenbuf[0:tokenlen])
		slist.isstring = true
	}
	slist.isatom = true
	return slist
}

func atoi() int { // BUG: uses tokenbuf; should take argument)
	var v int = 0
	for i := 0; i < tokenlen && '0' <= tokenbuf[i] && tokenbuf[i] <= '9'; i = i + 1 {
		v = 10*v + int(tokenbuf[i]-'0')
	}
	return v
}

func Parse() *Slist {
	var slist *Slist

	if token == EOF || token == ')' {
		return nil
	}
	if token == '(' {
		NextToken()
		slist = ParseList()
		Expect(')')
		return slist
	} else {
		// Atom
		switch token {
		case EOF:
			return nil
		case '0':
			slist = atom(atoi())
		case '"', 'A':
			slist = atom(0)
		default:
			slist = nil
			print("unknown token: ", token, "\n")
		}
		NextToken()
		return slist
	}
	return nil
}

func OpenFile() {
	input = "(defn foo (add 12 34))\n\x00"
	inputindex = 0
	peekc = -1 // BUG
	NextToken()
}
