From 8ba4c9f138a5f3f2703463be09eeed4909df45de Mon Sep 17 00:00:00 2001 From: Dimitri Sokolyuk Date: Wed, 7 Jan 2015 23:50:04 +0100 Subject: initial commit --- .gitignore | 2 + calc.y | 155 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ generate.go | 2 + lexer.go | 162 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 25 ++++++++++ register.go | 9 ++++ 6 files changed, 355 insertions(+) create mode 100644 .gitignore create mode 100644 calc.y create mode 100644 generate.go create mode 100644 lexer.go create mode 100644 main.go create mode 100644 register.go 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 DREG VREG +%token NUMBER +%token STRING + +%type dexp +%type 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) } -- cgit v1.2.3