aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDimitri Sokolyuk <demon@dim13.org>2015-01-07 23:50:04 +0100
committerDimitri Sokolyuk <demon@dim13.org>2015-01-07 23:50:04 +0100
commit8ba4c9f138a5f3f2703463be09eeed4909df45de (patch)
tree353cb7fd5d563fc1b76eed4fd4a529bf99e7f72d
initial commit
-rw-r--r--.gitignore2
-rw-r--r--calc.y155
-rw-r--r--generate.go2
-rw-r--r--lexer.go162
-rw-r--r--main.go25
-rw-r--r--register.go9
6 files changed, 355 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5632e99
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+calc.go
+calc
diff --git a/calc.y b/calc.y
new file mode 100644
index 0000000..202d4ca
--- /dev/null
+++ b/calc.y
@@ -0,0 +1,155 @@
+// XXX http://play.golang.org/p/V_dX9jzYzD
+%{
+
+package main
+
+import (
+ "fmt"
+ "math"
+)
+
+type Number float64
+
+type Interval struct {
+ lo Number
+ hi Number
+}
+
+var nreg = make(map[rune]Number)
+var ireg = make(map[rune]Interval)
+
+%}
+
+%union{
+ dval Number
+ vval Interval
+ rval rune
+ sval string
+}
+
+%token <rval> DREG VREG
+%token <dval> NUMBER
+%token <sval> STRING
+
+%type <dval> dexp
+%type <vval> vexp
+
+%left '+' '-'
+%left '*' '/'
+%left UMINUS
+
+%%
+
+line
+ : dexp { fmt.Println($1) }
+ | vexp { fmt.Println($1) }
+ | DREG '=' dexp { $3.Set($1) }
+ | VREG '=' vexp { $3.Set($1) }
+ | STRING { fmt.Println($1) }
+ ;
+
+dexp
+ : NUMBER
+ | DREG { $$.Get($1) }
+ | dexp '+' dexp { $$ = $1 + $3 }
+ | dexp '-' dexp { $$ = $1 - $3 }
+ | dexp '*' dexp { $$ = $1 * $3 }
+ | dexp '/' dexp { $$ = $1 / $3 }
+ | '-' dexp %prec UMINUS { $$ = -$2 }
+ | '(' dexp ')' { $$ = $2 }
+ | error { $$ = Number(math.NaN()) }
+ ;
+
+vexp
+ : '(' dexp ',' dexp ')' {
+ if $2 > $4 {
+ $$ = Interval{$4, $2}
+ } else {
+ $$ = Interval{$2, $4}
+ }
+ }
+ | VREG { $$.Get($1) }
+ | vexp '+' vexp { $$ = $3.vadd($1) }
+ | dexp '+' vexp { $$ = $3.vadd(Interval{$1, $1}) }
+ | vexp '+' dexp { $$ = Interval{$3, $3}.vadd($1) }
+ | vexp '-' vexp { $$ = $3.vsub($1) }
+ | dexp '-' vexp { $$ = $3.vsub(Interval{$1, $1}) }
+ | vexp '-' dexp { $$ = Interval{$3, $3}.vsub($1) }
+ | vexp '*' vexp { $$ = $3.vmul($1) }
+ | dexp '*' vexp { $$ = $3.vmul(Interval{$1, $1}) }
+ | vexp '*' dexp { $$ = Interval{$3, $3}.vmul($1) }
+ | vexp '/' vexp {
+ if $3.dcheck() {
+ yylex.Error("divisor interval contains 0")
+ }
+ $$ = $3.vdiv($1)
+ }
+ | dexp '/' vexp {
+ if $3.dcheck() {
+ yylex.Error("divisor interval contains 0")
+ }
+ $$ = $3.vdiv(Interval{$1, $1})
+ }
+ | vexp '/' dexp {
+ if $3 == 0 {
+ yylex.Error("divisor interval contains 0")
+ }
+ $$ = Interval{$3, $3}.vdiv($1)
+ }
+ | '-' vexp %prec UMINUS { $$ = $2.vneg() }
+ | '(' vexp ')' { $$ = $2 }
+ ;
+
+%%
+
+func (i Interval) String() string {
+ return fmt.Sprint("(", i.lo, ",", i.hi, ")")
+}
+
+func hilo(a, b, c, d Number) (v Interval) {
+ if a > b {
+ v.hi = a
+ v.lo = b
+ } else {
+ v.hi = b
+ v.lo = a
+ }
+ if c > d {
+ if c > v.hi { v.hi = c }
+ if d < v.lo { v.lo = d }
+ } else {
+ if d > v.hi { v.hi = d }
+ if c < v.lo { v.lo = c }
+ }
+ return
+}
+
+func (v Interval) vmul(a Interval) Interval {
+ return hilo(a.lo*v.hi, a.lo*v.lo, a.hi*v.hi, a.hi*v.lo)
+}
+
+func (v Interval) dcheck() bool {
+ return v.hi >= 0 && v.lo <= 0
+}
+
+func (v Interval) vdiv(a Interval) Interval {
+ return hilo(a.lo/v.hi, a.lo/v.lo, a.hi/v.hi, a.hi/v.lo)
+}
+
+func (v Interval) vadd(a Interval) Interval {
+ return Interval{a.lo + v.lo, a.hi + v.hi}
+}
+
+func (v Interval) vsub(a Interval) Interval {
+ return Interval{a.lo - v.lo, a.hi - v.hi}
+}
+
+func (v Interval) vneg() Interval {
+ return Interval{lo: -v.hi, hi: -v.lo}
+}
+
+func (n *Number) Set(key rune) { nreg[key] = *n }
+func (n *Number) Get(key rune) { *n = nreg[key] }
+
+func (n *Interval) Set(key rune) { ireg[key] = *n }
+func (n *Interval) Get(key rune) { *n = ireg[key] }
diff --git a/generate.go b/generate.go
new file mode 100644
index 0000000..8c00036
--- /dev/null
+++ b/generate.go
@@ -0,0 +1,2 @@
+package main
+//go:generate go tool yacc -o calc.go calc.y
diff --git a/lexer.go b/lexer.go
new file mode 100644
index 0000000..76ffc8b
--- /dev/null
+++ b/lexer.go
@@ -0,0 +1,162 @@
+package main
+
+import (
+ "log"
+ "strconv"
+ "strings"
+ "unicode"
+ "unicode/utf8"
+)
+
+const eof = 0
+
+type item struct {
+ typ int
+ val string
+}
+
+type yyLex struct {
+ input string
+ start int
+ pos int
+ width int
+ items chan item
+}
+
+func (y *yyLex) Error(s string) {
+ log.Println(s)
+}
+
+func (y *yyLex) Lex(lval *yySymType) int {
+ item := <-y.items
+ switch item.typ {
+ case NUMBER:
+ n, err := strconv.ParseFloat(item.val, 64)
+ if err != nil {
+ log.Println(err)
+ }
+ lval.dval = Number(n)
+ case VREG, DREG:
+ lval.rval = rune(item.val[0])
+ case STRING:
+ lval.sval = item.val[1:len(item.val)-1]
+ }
+ return int(item.typ)
+}
+
+func lex(input string) *yyLex {
+ l := &yyLex{
+ input: input,
+ items: make(chan item),
+ }
+ go l.run()
+ return l
+}
+
+func (y *yyLex) run() {
+ defer close(y.items)
+ for {
+ switch c := y.next(); {
+ case unicode.IsDigit(c):
+ y.lexNumber()
+ y.emit(NUMBER)
+ case unicode.IsUpper(c):
+ y.emit(VREG)
+ case unicode.IsLower(c):
+ y.emit(DREG)
+ case unicode.IsSpace(c):
+ y.ignore()
+ case c == '\'':
+ if y.lexQuoted() {
+ y.emit(STRING)
+ } else {
+ y.emit(int(c))
+ }
+ case c == eof:
+ return
+ default:
+ y.emit(int(c))
+ }
+ }
+}
+
+func (y *yyLex) lexNumber() {
+ y.acceptDigits()
+ if y.acceptRune('.') {
+ y.acceptDigits()
+ }
+ if y.acceptRune('e', 'E') {
+ y.acceptRune('-')
+ y.acceptDigits()
+ }
+}
+
+func (y *yyLex) lexQuoted() bool {
+ if n := strings.IndexRune(y.input[y.pos:], '\''); n >= 0 {
+ y.pos += n
+ y.next()
+ return true
+ }
+ return false
+}
+
+func (y *yyLex) emit(t int) {
+ y.items <- item{
+ typ: t,
+ val: y.input[y.start:y.pos],
+ }
+ y.start = y.pos
+}
+
+func (y *yyLex) next() (r rune) {
+ if y.pos >= len(y.input) {
+ y.width = 0
+ return eof
+ }
+ r, y.width = utf8.DecodeRuneInString(y.input[y.pos:])
+ y.pos += y.width
+ return r
+}
+
+func (y *yyLex) ignore() {
+ y.start = y.pos
+}
+
+func (y *yyLex) backup() {
+ y.pos -= y.width
+}
+
+func (y *yyLex) peek() rune {
+ defer y.backup()
+ return y.next()
+}
+
+func (y *yyLex) acceptDigits() {
+ defer y.backup()
+ for unicode.IsDigit(y.next()) {
+ }
+}
+
+func (y *yyLex) acceptRune(valid ...rune) bool {
+ for _, r := range valid {
+ if y.next() == r {
+ return true
+ }
+ y.backup()
+ }
+ return false
+}
+
+func (y *yyLex) accept(valid string) bool {
+ if strings.IndexRune(valid, y.next()) >= 0 {
+ return true
+ }
+ y.backup()
+ return false
+}
+
+func (y *yyLex) acceptRun(valid string) {
+ for strings.IndexRune(valid, y.next()) >= 0 {
+ }
+ y.backup()
+}
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..b0460d7
--- /dev/null
+++ b/main.go
@@ -0,0 +1,25 @@
+package main
+
+//go:generate go tool yacc -o calc.go calc.y
+
+import (
+ "bufio"
+ "io"
+ "os"
+)
+
+func main() {
+ in := bufio.NewReader(os.Stdin)
+ yyDebug = 1
+
+ for {
+ os.Stdout.WriteString("\t")
+ //line, err := in.ReadBytes('\n')
+ line, err := in.ReadString('\n')
+ if err == io.EOF {
+ return
+ }
+ //yyParse(&yyLex{input: line})
+ yyParse(lex(line))
+ }
+}
diff --git a/register.go b/register.go
new file mode 100644
index 0000000..2647660
--- /dev/null
+++ b/register.go
@@ -0,0 +1,9 @@
+package main
+
+type Register interface {
+ Set(rune)
+ Get(rune)
+}
+
+func Set(key rune, v Register) { v.Set(key) }
+func Get(key rune, v Register) { v.Get(key) }