Skip to content

Commit

Permalink
Merge 76ca3c9 into 0c5e950
Browse files Browse the repository at this point in the history
  • Loading branch information
odino committed Aug 17, 2019
2 parents 0c5e950 + 76ca3c9 commit 146328b
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 15 deletions.
15 changes: 15 additions & 0 deletions evaluator/evaluator_test.go
Expand Up @@ -47,6 +47,21 @@ func TestEvalFloatExpression(t *testing.T) {
}
}

func TestEvalNumberAbbreviations(t *testing.T) {
tests := []struct {
input string
expected float64
}{
{"5k", 5000},
{"1m / 1M", 1},
}

for _, tt := range tests {
evaluated := testEval(tt.input)
testNumberObject(t, evaluated, tt.expected)
}
}

func TestEvalNumberExpression(t *testing.T) {
tests := []struct {
input string
Expand Down
29 changes: 25 additions & 4 deletions lexer/lexer.go
Expand Up @@ -347,19 +347,32 @@ func (l *Lexer) readIdentifier() string {
return string(l.input[position:l.position])
}

// List of character that can appear in a "number"
//
// * digits
// * dot for floats
// * scientific notation characters (12e-1)
// * separators (1_000_000)
// * abbreviations (12M)
func isCharAllowedInNumber(c rune) bool {
lowchar := unicode.ToLower(c)
_, isAbbr := token.NumberAbbreviations[string(lowchar)]

return isDigit(lowchar) || lowchar == '.' || lowchar == '+' || lowchar == '-' || c == 'e' || lowchar == token.NumberSeparator || isAbbr
}

// 12
// 12.2
// 12e-1
// 12e+1
// 12e1
func (l *Lexer) readNumber() (number string, kind token.TokenType) {
position := l.position
hasDot := false
kind = token.NUMBER
hasExponent := false
var hasDot bool
var hasExponent bool

// List of character that can appear in a "number"
for isDigit(l.ch) || l.ch == '.' || l.ch == '+' || l.ch == '-' || l.ch == 'e' || l.ch == '_' {
for isCharAllowedInNumber(l.ch) {
// If we have a plus / minus but there was no exponent
// in this number, it means we're at the end of the
// number and we're at an addition / subtraction.
Expand All @@ -373,6 +386,14 @@ func (l *Lexer) readNumber() (number string, kind token.TokenType) {
hasExponent = true
}

// If this character is a number abbreviation
// (eg. the K in 12K), let's read it and complete
// the number
if _, isAbbr := token.NumberAbbreviations[string(l.ch)]; isAbbr {
l.readChar()
return string(l.input[position:l.position]), kind
}

// If we have a dot, let's check whether this is a range
// or maybe a method call (122.string())
if l.ch == '.' && (l.peekChar() == '.' || !isDigit(l.peekChar())) {
Expand Down
16 changes: 16 additions & 0 deletions lexer/lexer_test.go
Expand Up @@ -85,6 +85,14 @@ $111
10_000
10_00.00
1_2e1
12k
12K
12m
12M
12t
12T
12b
12B
小明
hello_w0rld
Expand Down Expand Up @@ -316,6 +324,14 @@ a[1:3]
{token.NUMBER, "10000"},
{token.NUMBER, "1000.00"},
{token.NUMBER, "12e1"},
{token.NUMBER, "12k"},
{token.NUMBER, "12K"},
{token.NUMBER, "12m"},
{token.NUMBER, "12M"},
{token.NUMBER, "12t"},
{token.NUMBER, "12T"},
{token.NUMBER, "12b"},
{token.NUMBER, "12B"},
{token.IDENT, "小明"},
{token.ILLEGAL, "❤"},
{token.IDENT, "hello_w0rld"},
Expand Down
19 changes: 16 additions & 3 deletions parser/parser.go
Expand Up @@ -3,6 +3,7 @@ package parser
import (
"fmt"
"strconv"
"strings"

"github.com/abs-lang/abs/ast"
"github.com/abs-lang/abs/lexer"
Expand Down Expand Up @@ -406,17 +407,29 @@ func (p *Parser) parseIdentifier() ast.Expression {
return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
}

// 1 or 1.1
// 1 or 1.1 or 1k
func (p *Parser) ParseNumberLiteral() ast.Expression {
lit := &ast.NumberLiteral{Token: p.curToken}
var abbr float64
var ok bool
number := p.curToken.Literal

value, err := strconv.ParseFloat(p.curToken.Literal, 64)
// Check if the last character of this number is an abbreviation
if abbr, ok = token.NumberAbbreviations[strings.ToLower(string(number[len(number)-1]))]; ok {
number = p.curToken.Literal[:len(p.curToken.Literal)-1]
}

value, err := strconv.ParseFloat(number, 64)
if err != nil {
msg := fmt.Sprintf("could not parse %q as number", p.curToken.Literal)
msg := fmt.Sprintf("could not parse %q as number", number)
p.reportError(msg, p.curToken)
return nil
}

if abbr != 0 {
value *= abbr
}

lit.Value = value

return lit
Expand Down
19 changes: 11 additions & 8 deletions parser/parser_test.go
Expand Up @@ -123,13 +123,16 @@ func TestIdentifierExpression(t *testing.T) {

func TestNumberLiteralExpression(t *testing.T) {
prefixTests := []struct {
input string
value float64
input string
value float64
literal string
}{
{"5;", 5},
{"5.5", 5.5},
{"5.5555555", 5.5555555},
{"5_000", 5000},
{"5;", 5, "5"},
{"5.5", 5.5, "5.5"},
{"5.5555555", 5.5555555, "5.5555555"},
{"5_000", 5000, "5000"},
{"1k", 1000, "1k"},
{"1M", 1000000, "1M"},
}

for _, tt := range prefixTests {
Expand All @@ -154,8 +157,8 @@ func TestNumberLiteralExpression(t *testing.T) {
t.Errorf("literal.Value not %v. got=%v", tt.value, literal.Value)
}

if literal.TokenLiteral() != fmt.Sprintf("%v", tt.value) {
t.Errorf("number.TokenLiteral not %v. got=%s", tt.value, literal.TokenLiteral())
if literal.TokenLiteral() != fmt.Sprintf("%v", tt.literal) {
t.Errorf("number.TokenLiteral not %v. got=%s", tt.literal, literal.TokenLiteral())
}
}
}
Expand Down
11 changes: 11 additions & 0 deletions token/token.go
Expand Up @@ -103,6 +103,17 @@ var keywords = map[string]TokenType{
"continue": CONTINUE,
}

// NumberAbbreviations is a list of abbreviations that can be used in numbers eg. 1k, 20B
var NumberAbbreviations = map[string]float64{
"k": 1000,
"m": 1000000,
"b": 1000000000,
"t": 1000000000000,
}

// NumberSeparator is a separator for numbers eg. 1_000_000
var NumberSeparator = '_'

func LookupIdent(ident string) TokenType {
if tok, ok := keywords[ident]; ok {
return tok
Expand Down

0 comments on commit 146328b

Please sign in to comment.