aboutsummaryrefslogtreecommitdiff
path: root/calculator-java/src/main/java/ch/bfh/CalculatorLexer.java
blob: 48d009e7fdef85f33aa33ca69133a9c69ffc0558 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package ch.bfh;
import ch.bfh.exceptions.LexerException;

// Lexer for classical arithmetic expressions with identifiers and assignments.
// Scans a source string char by char.

public class CalculatorLexer {

  private String src;  // source string for lexical analysis
  private int idx;     // current index in source
  private int len;     // length of source

  public CalculatorLexer() { }

  public void initLexer(String source) {
    this.src = source;
    idx = 0;
    len = src.length();
  }

  // Consumes letters only and builds an identifier
  private String identifier() {
    StringBuffer s = new StringBuffer();
  
    do {
      s.append(src.charAt(idx));
      idx++;
    } while (idx < len && Character.isLetter(src.charAt(idx)));
    return s.toString();
  }

  // Consumes digits and convert integer part and decimal part
  // Convert characters using the formula
  // "3456.253" = [(((3+0)*10+4)*10+5)*10+6]+[0.1*2+0.01*5+0.001*3]
  private double number() throws LexerException {
    double v = 0;        // accumulates the result
    double factor = 0.1; // factor for decimal part

    do { // integer part
      v = v * 10 + Character.digit(src.charAt(idx),30);
      idx++;
    } while (idx < len && Character.isDigit(src.charAt(idx)));
    if (idx < len && src.charAt(idx) == '.') { // decimal point
      idx++; 
      if (idx < len && Character.isDigit(src.charAt(idx))) { // decimal part
        while (idx < len && Character.isDigit(src.charAt(idx))) {
          v = v + (factor * Character.digit(src.charAt(idx),30));
          factor = factor * 0.1;
          idx++;
        }
      }
      else throw new LexerException("Illegal number: decimal part missing");
    }
    return v;
  }

  // Skips blanks, tabs, newlines
  private void skip() {
    char c;
    while (idx < len) {
      c = src.charAt(idx);
      if (c==' ' || c=='\t' || c=='\n') idx++;
      else break;
    }
  }

  // returns next token 
  public Token nextToken() throws LexerException {
    Token tok = new Token();

    skip();
    if (idx>=len) {
	tok.str="EOL";
	tok.type=Token.EOL;
    }
    else
      // is it a positive number?
      if (Character.isDigit(src.charAt(idx))) {
        tok.value = number();
        tok.type  = Token.NUM;
        tok.str   = Double.toString(tok.value);
      }
      else
        if (Character.isLetter(src.charAt(idx))) {
          tok.value = 0;
          tok.type  = Token.ID;
          tok.str   = identifier();
          if (tok.str.compareTo("let")==0)  tok.type = Token.LET;
          if (tok.str.compareTo("exit")==0) tok.type = Token.END;
        }
        else {
          switch (src.charAt(idx)) {
            case '+': tok.type = Token.ADD; tok.str = "+"; break;
            case '-': tok.type = Token.SUB; tok.str = "-"; break;
            case '*': tok.type = Token.MUL; tok.str = "*"; break;
            case '/': tok.type = Token.DIV; tok.str = "/"; break;
            case '(': tok.type = Token.PAL; tok.str = "("; break;
            case ')': tok.type = Token.PAR; tok.str = ")"; break;
            case '=': tok.type = Token.EQU; tok.str = "="; break;
            default : throw new LexerException("Illegal Token: '" + src.charAt(idx) + "'");
          }
          idx++;
        }
    return tok;
  }
}