Skip to content

Instantly share code, notes, and snippets.

@danking
Created January 15, 2014 15:09
Show Gist options
  • Save danking/8437957 to your computer and use it in GitHub Desktop.
Save danking/8437957 to your computer and use it in GitHub Desktop.
///////////////////////////////////////////////////////////////////////////////
// data
function throw2 (arg) {
throw arg;
}
function numberDatum() { }
function makeDefaultNumber() {
var n = new numberDatum()
n.isExact = false
n.base = 10
n.value = NaN
return n
}
///////////////////////////////////////////////////////////////////////////////
// parser
// we only do inexact reals. no exacts, no complex
// we also don't allow '#' to represent zero's whose value is unknown.
function read_number(str, i) {
var p = str.charAt(i);
var datum = makeDefaultNumber()
if(p === '#') {
i = read_number_exactness_and_radix(str, i, datum)
}
p = str.charAt(i)
// signed inexact or inf.0, nan.0
if(/[+-]/.test(p)) {
i = read_signed_number_or_special(str, i, datum)
} else {
i = read_unsigned_number(str, i, datum)
}
var sexp = datum
sexp.location = i
// TODO: actually get location right
// sexp.location = new Location(sCol, sLine, iStart, i-iStart)
return sexp
}
function read_number_exactness_and_radix(str, i, datum) {
var p = str.charAt(i)
// read exactness and raidx
if(p === '#') {
if(/[ei]/.test(str.charAt(i+1))) {
// exactness first
i = read_number_exactness(str, i+1, datum)
if (str.charAt(i) === '#') {
i = read_number_radix(str, i+1, datum)
}
} else {
// radix first
i = read_number_radix(str, i+1, datum)
if (str.charAt(i) === '#') {
i = read_number_exactness(str, i+1, datum)
}
}
}
return i
}
function read_number_exactness(str, i, datum) {
datum.isExact = (str.charAt(i) === 'e') ? true : false
return i+1
}
function read_number_radix(str, i, datum) {
if (i >= str.length) {
throw "read: end of string while reading a radix: string \"" +
str + "\", position " + i
}
datum.base =
(str.charAt(i) === 'b') ? 2 :
(str.charAt(i) === 'o') ? 8 :
(str.charAt(i) === 'd') ? 10 :
(str.charAt(i) === 'x') ? 16 :
(throw2("read_number_radix: invalid radix: " + str.charAt(i)))
return i+1
}
// special refers to nan and infinity
function read_signed_number_or_special(str, i, datum) {
var sign = str.charAt(i) === '+' ? 1 : -1
var digits = digits_for_radix(datum.base)
i = i + 1
if(digits.test(str.charAt(i))) {
// a number
i = read_unsigned_number(str, i, datum)
datum.value = sign * datum.value
} else {
// nan or inf
var nanOrInf = str.substr(i, 5)
i = i+5
if(nanOrInf === "inf.0" || nanOrInf === "inf.f") {
datum.value = Infinity
} else if (nanOrInf === "nan.0" || nanOrInf === "nan.f") {
datum.value = NaN
} else {
throw2("read: error, expected nan or inf: str \"" +
str + "\", position " + i)
}
}
return i
}
function read_unsigned_number(str, i, datum) {
var digits = digits_for_radix(datum.base)
i = read_digits(str, i, datum, digits)
i = maybe_read_slash_and_denominator(str, i, datum, digits)
i = maybe_read_exponent(str, i, datum, digits)
return i
}
function read_digits(str, i, datum, digits) {
var valueAndI = read_digits_to_js_num(str, i, datum.base, digits)
datum.value = valueAndI[0]
i = valueAndI[1]
if (isNaN(datum.value)) {
throw2("read: expected a base " + datum.base + " number")
}
return i
}
// in this function, datum contains the value of the numerator
// we'll read the denominator and then divide by it
function maybe_read_slash_and_denominator(str, i, datum, digits) {
if (str.charAt(i) === '/') {
i = i + 1
var denominator = makeDefaultNumber()
denominator.base = datum.base
denominator.isExact = datum.base
i = read_digits(str, i, denominator, digits)
datum.value = datum.value / denominator.value
}
return i
}
function maybe_read_exponent(str, i, datum, digits) {
if(exp_mark_for_radix(datum.base).test(str.charAt(i))) {
// we ignore the single versus double precision distinction
i = i + 1
var sign = 1
if(/[-+]/.test(str.charAt(i))) {
sign = str.charAt(i) === '+' ? 1 : -1
i = i + 1
}
var exponentAndI = read_digits_to_js_num(str, i, datum.base, digits)
if (isNaN(exponentAndI[0])) {
throw2("read: expected an exponent in base " + datum.base)
}
var exponent = sign * exponentAndI[0]
i = exponentAndI[1]
datum.value = datum.value * Math.pow(10, exponent)
}
return i
}
function read_digits_to_js_num(str, i, base, digits) {
var len = 0
while(i < str.length &&
digits.test(str.charAt(i+len))) {
len++
}
var digitString = str.substr(i, len)
// var intParser = Number.parseInt
var intParser = parseInt
return [intParser(digitString, base), i+len]
}
function digits_for_radix(radix) {
return radix === 2 ? /[01]/ :
radix === 8 ? /[0123]/ :
radix === 10 ? /[0123456789]/ :
radix === 16 ? /[0123456789abcdef]/ :
(throw2("digits_for_radix: invalid radix: " + radix))
}
function exp_mark_for_radix(radix) {
return (radix === 2 || radix === 8 || radix === 10) ? /[sldef]/ :
radix === 16 ? /[sl]/ :
(throw2("digits_for_radix: invalid radix: " + radix))
}
///////////////////////////////////////////////////////////////////////////////
// tests
function testThis(str) {
return read_number(str, 0)
}
console.log(testThis("-1").value == -1)
console.log(testThis("1/2").value == 0.5)
console.log(testThis("1.0").value == 1.0)
// console.log(parse(lex("1+2i")).real == 1 ; parse(lex("1+2i")).imag == 2)
// console.log(parse(lex("1/2+3/4i")).real == 0.5; parse(lex("1/2+3/4i")).imag == 0.75)
console.log(testThis("2e5").value == 200000)
console.log(testThis("#i5").value == 5)
console.log(testThis("#e2e5").value == 200000)
console.log(testThis("#x2e5").value == 741)
console.log(testThis("#b101").value == 5)
// -1 reads equal to -1
// 1/2 reads equal to (/ 1 2)
// 1.0 reads equal to (exact->inexact 1)
// 1+2i reads equal to (make-complex 1 2)
// 1/2+3/4i reads equal to (make-complex (/ 1 2) (/ 3 4))
// 1.0+3.0e7i reads equal to (exact->inexact (make-complex 1 30000000))
// 2e5 reads equal to (exact->inexact 200000)
// #i5 reads equal to (exact->inexact 5)
// #e2e5 reads equal to 200000
// #x2e5 reads equal to 741
// #b101 reads equal to 5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment