|
|
|
|
|
|
|
|
var parseCommon = require('./abc_common'); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var Tokenizer = function(lines, multilineVars) { |
|
|
this.lineIndex = 0 |
|
|
this.lines = lines |
|
|
this.multilineVars = multilineVars; |
|
|
|
|
|
this.skipWhiteSpace = function(str) { |
|
|
for (var i = 0; i < str.length; i++) { |
|
|
if (!this.isWhiteSpace(str[i])) |
|
|
return i; |
|
|
} |
|
|
return str.length; |
|
|
}; |
|
|
var finished = function(str, i) { |
|
|
return i >= str.length; |
|
|
}; |
|
|
this.eatWhiteSpace = function(line, index) { |
|
|
for (var i = index; i < line.length; i++) { |
|
|
if (!this.isWhiteSpace(line[i])) |
|
|
return i-index; |
|
|
} |
|
|
return i-index; |
|
|
}; |
|
|
|
|
|
|
|
|
this.getKeyPitch = function(str) { |
|
|
var i = this.skipWhiteSpace(str); |
|
|
if (finished(str, i)) |
|
|
return {len: 0}; |
|
|
switch (str[i]) { |
|
|
case 'A':return {len: i+1, token: 'A'}; |
|
|
case 'B':return {len: i+1, token: 'B'}; |
|
|
case 'C':return {len: i+1, token: 'C'}; |
|
|
case 'D':return {len: i+1, token: 'D'}; |
|
|
case 'E':return {len: i+1, token: 'E'}; |
|
|
case 'F':return {len: i+1, token: 'F'}; |
|
|
case 'G':return {len: i+1, token: 'G'}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
return {len: 0}; |
|
|
}; |
|
|
|
|
|
|
|
|
this.getSharpFlat = function(str) { |
|
|
if (str === 'bass') |
|
|
return {len: 0}; |
|
|
switch (str[0]) { |
|
|
case '#':return {len: 1, token: '#'}; |
|
|
case 'b':return {len: 1, token: 'b'}; |
|
|
} |
|
|
return {len: 0}; |
|
|
}; |
|
|
|
|
|
this.getMode = function(str) { |
|
|
var skipAlpha = function(str, start) { |
|
|
|
|
|
while (start < str.length && ((str[start] >= 'a' && str[start] <= 'z') || (str[start] >= 'A' && str[start] <= 'Z'))) |
|
|
start++; |
|
|
return start; |
|
|
}; |
|
|
|
|
|
var i = this.skipWhiteSpace(str); |
|
|
if (finished(str, i)) |
|
|
return {len: 0}; |
|
|
var firstThree = str.substring(i,i+3).toLowerCase(); |
|
|
if (firstThree.length > 1 && firstThree[1] === ' ' || firstThree[1] === '^' || firstThree[1] === '_' || firstThree[1] === '=') firstThree = firstThree[0]; |
|
|
switch (firstThree) { |
|
|
case 'mix':return {len: skipAlpha(str, i), token: 'Mix'}; |
|
|
case 'dor':return {len: skipAlpha(str, i), token: 'Dor'}; |
|
|
case 'phr':return {len: skipAlpha(str, i), token: 'Phr'}; |
|
|
case 'lyd':return {len: skipAlpha(str, i), token: 'Lyd'}; |
|
|
case 'loc':return {len: skipAlpha(str, i), token: 'Loc'}; |
|
|
case 'aeo':return {len: skipAlpha(str, i), token: 'm'}; |
|
|
case 'maj':return {len: skipAlpha(str, i), token: ''}; |
|
|
case 'ion':return {len: skipAlpha(str, i), token: ''}; |
|
|
case 'min':return {len: skipAlpha(str, i), token: 'm'}; |
|
|
case 'm':return {len: skipAlpha(str, i), token: 'm'}; |
|
|
} |
|
|
return {len: 0}; |
|
|
}; |
|
|
|
|
|
this.getClef = function(str, bExplicitOnly) { |
|
|
var strOrig = str; |
|
|
var i = this.skipWhiteSpace(str); |
|
|
if (finished(str, i)) |
|
|
return {len: 0}; |
|
|
|
|
|
var needsClef = false; |
|
|
var strClef = str.substring(i); |
|
|
if (parseCommon.startsWith(strClef, 'clef=')) { |
|
|
needsClef = true; |
|
|
strClef = strClef.substring(5); |
|
|
i += 5; |
|
|
} |
|
|
if (strClef.length === 0 && needsClef) |
|
|
return {len: i+5, warn: "No clef specified: " + strOrig}; |
|
|
|
|
|
var j = this.skipWhiteSpace(strClef); |
|
|
if (finished(strClef, j)) |
|
|
return {len: 0}; |
|
|
if (j > 0) { |
|
|
i += j; |
|
|
strClef = strClef.substring(j); |
|
|
} |
|
|
var name = null; |
|
|
if (parseCommon.startsWith(strClef, 'treble')) |
|
|
name = 'treble'; |
|
|
else if (parseCommon.startsWith(strClef, 'bass3')) |
|
|
name = 'bass3'; |
|
|
else if (parseCommon.startsWith(strClef, 'bass')) |
|
|
name = 'bass'; |
|
|
else if (parseCommon.startsWith(strClef, 'tenor')) |
|
|
name = 'tenor'; |
|
|
else if (parseCommon.startsWith(strClef, 'alto2')) |
|
|
name = 'alto2'; |
|
|
else if (parseCommon.startsWith(strClef, 'alto1')) |
|
|
name = 'alto1'; |
|
|
else if (parseCommon.startsWith(strClef, 'alto')) |
|
|
name = 'alto'; |
|
|
else if (!bExplicitOnly && (needsClef && parseCommon.startsWith(strClef, 'none'))) |
|
|
name = 'none'; |
|
|
else if (parseCommon.startsWith(strClef, 'perc')) |
|
|
name = 'perc'; |
|
|
else if (!bExplicitOnly && (needsClef && parseCommon.startsWith(strClef, 'C'))) |
|
|
name = 'tenor'; |
|
|
else if (!bExplicitOnly && (needsClef && parseCommon.startsWith(strClef, 'F'))) |
|
|
name = 'bass'; |
|
|
else if (!bExplicitOnly && (needsClef && parseCommon.startsWith(strClef, 'G'))) |
|
|
name = 'treble'; |
|
|
else |
|
|
return {len: i+5, warn: "Unknown clef specified: " + strOrig}; |
|
|
|
|
|
strClef = strClef.substring(name.length); |
|
|
j = this.isMatch(strClef, '+8'); |
|
|
if (j > 0) |
|
|
name += "+8"; |
|
|
else { |
|
|
j = this.isMatch(strClef, '-8'); |
|
|
if (j > 0) |
|
|
name += "-8"; |
|
|
} |
|
|
return {len: i+name.length, token: name, explicit: needsClef}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
this.getBarLine = function(line, i) { |
|
|
switch (line[i]) { |
|
|
case ']': |
|
|
++i; |
|
|
switch (line[i]) { |
|
|
case '|': return {len: 2, token: "bar_thick_thin"}; |
|
|
case '[': |
|
|
++i; |
|
|
if ((line[i] >= '1' && line[i] <= '9') || line[i] === '"') |
|
|
return {len: 2, token: "bar_invisible"}; |
|
|
return {len: 1, warn: "Unknown bar symbol"}; |
|
|
default: |
|
|
return {len: 1, token: "bar_invisible"}; |
|
|
} |
|
|
break; |
|
|
case ':': |
|
|
++i; |
|
|
switch (line[i]) { |
|
|
case ':': return {len: 2, token: "bar_dbl_repeat"}; |
|
|
case '|': |
|
|
++i; |
|
|
switch (line[i]) { |
|
|
case ']': |
|
|
++i; |
|
|
switch (line[i]) { |
|
|
case '|': |
|
|
++i; |
|
|
if (line[i] === ':') return {len: 5, token: "bar_dbl_repeat"}; |
|
|
return {len: 3, token: "bar_right_repeat"}; |
|
|
default: |
|
|
return {len: 3, token: "bar_right_repeat"}; |
|
|
} |
|
|
break; |
|
|
case '|': |
|
|
++i; |
|
|
if (line[i] === ':') return {len: 4, token: "bar_dbl_repeat"}; |
|
|
return {len: 3, token: "bar_right_repeat"}; |
|
|
default: |
|
|
return {len: 2, token: "bar_right_repeat"}; |
|
|
} |
|
|
break; |
|
|
default: |
|
|
return {len: 1, warn: "Unknown bar symbol"}; |
|
|
} |
|
|
break; |
|
|
case '[': |
|
|
++i; |
|
|
if (line[i] === '|') { |
|
|
++i; |
|
|
switch (line[i]) { |
|
|
case ':': return {len: 3, token: "bar_left_repeat"}; |
|
|
case ']': return {len: 3, token: "bar_invisible"}; |
|
|
default: return {len: 2, token: "bar_thick_thin"}; |
|
|
} |
|
|
} else { |
|
|
if ((line[i] >= '1' && line[i] <= '9') || line[i] === '"') |
|
|
return {len: 1, token: "bar_invisible"}; |
|
|
return {len: 0}; |
|
|
} |
|
|
break; |
|
|
case '|': |
|
|
++i; |
|
|
switch (line[i]) { |
|
|
case ']': return {len: 2, token: "bar_thin_thick"}; |
|
|
case '|': |
|
|
++i; |
|
|
if (line[i] === ':') return {len: 3, token: "bar_left_repeat"}; |
|
|
return {len: 2, token: "bar_thin_thin"}; |
|
|
case ':': |
|
|
var colons = 0; |
|
|
while (line[i+colons] === ':') colons++; |
|
|
return { len: 1+colons, token: "bar_left_repeat"}; |
|
|
default: return {len: 1, token: "bar_thin"}; |
|
|
} |
|
|
break; |
|
|
} |
|
|
return {len: 0}; |
|
|
}; |
|
|
|
|
|
|
|
|
this.getTokenOf = function(str, legalChars) { |
|
|
for (var i = 0; i < str.length; i++) { |
|
|
if (legalChars.indexOf(str[i]) < 0) |
|
|
return {len: i, token: str.substring(0, i)}; |
|
|
} |
|
|
return {len: i, token: str}; |
|
|
}; |
|
|
|
|
|
this.getToken = function(str, start, end) { |
|
|
|
|
|
var i = start; |
|
|
while (i < end && !this.isWhiteSpace(str[i])) |
|
|
i++; |
|
|
return str.substring(start, i); |
|
|
}; |
|
|
|
|
|
|
|
|
this.isMatch = function(str, match) { |
|
|
var i = this.skipWhiteSpace(str); |
|
|
if (finished(str, i)) |
|
|
return 0; |
|
|
if (parseCommon.startsWith(str.substring(i), match)) |
|
|
return i+match.length; |
|
|
return 0; |
|
|
}; |
|
|
|
|
|
this.getPitchFromTokens = function(tokens) { |
|
|
var ret = { }; |
|
|
var pitches = {A: 5, B: 6, C: 0, D: 1, E: 2, F: 3, G: 4, a: 12, b: 13, c: 7, d: 8, e: 9, f: 10, g: 11}; |
|
|
ret.position = pitches[tokens[0].token]; |
|
|
if (ret.position === undefined) |
|
|
return { warn: "Pitch expected. Found: " + tokens[0].token }; |
|
|
tokens.shift(); |
|
|
while (tokens.length) { |
|
|
switch (tokens[0].token) { |
|
|
case ',': ret.position -= 7; tokens.shift(); break; |
|
|
case '\'': ret.position += 7; tokens.shift(); break; |
|
|
default: return ret; |
|
|
} |
|
|
} |
|
|
return ret; |
|
|
}; |
|
|
|
|
|
this.getKeyAccidentals2 = function(tokens) { |
|
|
var accs; |
|
|
|
|
|
while (tokens.length > 0) { |
|
|
var acc; |
|
|
if (tokens[0].token === '^') { |
|
|
acc = 'sharp'; |
|
|
tokens.shift(); |
|
|
if (tokens.length === 0) return {accs: accs, warn: 'Expected note name after ' + acc}; |
|
|
switch (tokens[0].token) { |
|
|
case '^': acc = 'dblsharp'; tokens.shift(); break; |
|
|
case '/': acc = 'quartersharp'; tokens.shift(); break; |
|
|
} |
|
|
} else if (tokens[0].token === '=') { |
|
|
acc = 'natural'; |
|
|
tokens.shift(); |
|
|
} else if (tokens[0].token === '_') { |
|
|
acc = 'flat'; |
|
|
tokens.shift(); |
|
|
if (tokens.length === 0) return {accs: accs, warn: 'Expected note name after ' + acc}; |
|
|
switch (tokens[0].token) { |
|
|
case '_': acc = 'dblflat'; tokens.shift(); break; |
|
|
case '/': acc = 'quarterflat'; tokens.shift(); break; |
|
|
} |
|
|
} else { |
|
|
|
|
|
return { accs: accs }; |
|
|
} |
|
|
if (tokens.length === 0) return {accs: accs, warn: 'Expected note name after ' + acc}; |
|
|
switch (tokens[0].token[0]) |
|
|
{ |
|
|
case 'a': |
|
|
case 'b': |
|
|
case 'c': |
|
|
case 'd': |
|
|
case 'e': |
|
|
case 'f': |
|
|
case 'g': |
|
|
case 'A': |
|
|
case 'B': |
|
|
case 'C': |
|
|
case 'D': |
|
|
case 'E': |
|
|
case 'F': |
|
|
case 'G': |
|
|
if (accs === undefined) |
|
|
accs = []; |
|
|
accs.push({ acc: acc, note: tokens[0].token[0] }); |
|
|
if (tokens[0].token.length === 1) |
|
|
tokens.shift(); |
|
|
else |
|
|
tokens[0].token = tokens[0].token.substring(1); |
|
|
break; |
|
|
default: |
|
|
return {accs: accs, warn: 'Expected note name after ' + acc + ' Found: ' + tokens[0].token }; |
|
|
} |
|
|
} |
|
|
return { accs: accs }; |
|
|
}; |
|
|
|
|
|
|
|
|
this.getKeyAccidental = function(str) { |
|
|
var accTranslation = { |
|
|
'^': 'sharp', |
|
|
'^^': 'dblsharp', |
|
|
'=': 'natural', |
|
|
'_': 'flat', |
|
|
'__': 'dblflat', |
|
|
'_/': 'quarterflat', |
|
|
'^/': 'quartersharp' |
|
|
}; |
|
|
var i = this.skipWhiteSpace(str); |
|
|
if (finished(str, i)) |
|
|
return {len: 0}; |
|
|
var acc = null; |
|
|
switch (str[i]) |
|
|
{ |
|
|
case '^': |
|
|
case '_': |
|
|
case '=': |
|
|
acc = str[i]; |
|
|
break; |
|
|
default:return {len: 0}; |
|
|
} |
|
|
i++; |
|
|
if (finished(str, i)) |
|
|
return {len: 1, warn: 'Expected note name after accidental'}; |
|
|
switch (str[i]) |
|
|
{ |
|
|
case 'a': |
|
|
case 'b': |
|
|
case 'c': |
|
|
case 'd': |
|
|
case 'e': |
|
|
case 'f': |
|
|
case 'g': |
|
|
case 'A': |
|
|
case 'B': |
|
|
case 'C': |
|
|
case 'D': |
|
|
case 'E': |
|
|
case 'F': |
|
|
case 'G': |
|
|
return {len: i+1, token: {acc: accTranslation[acc], note: str[i]}}; |
|
|
case '^': |
|
|
case '_': |
|
|
case '/': |
|
|
acc += str[i]; |
|
|
i++; |
|
|
if (finished(str, i)) |
|
|
return {len: 2, warn: 'Expected note name after accidental'}; |
|
|
switch (str[i]) |
|
|
{ |
|
|
case 'a': |
|
|
case 'b': |
|
|
case 'c': |
|
|
case 'd': |
|
|
case 'e': |
|
|
case 'f': |
|
|
case 'g': |
|
|
case 'A': |
|
|
case 'B': |
|
|
case 'C': |
|
|
case 'D': |
|
|
case 'E': |
|
|
case 'F': |
|
|
case 'G': |
|
|
return {len: i+1, token: {acc: accTranslation[acc], note: str[i]}}; |
|
|
default: |
|
|
return {len: 2, warn: 'Expected note name after accidental'}; |
|
|
} |
|
|
break; |
|
|
default: |
|
|
return {len: 1, warn: 'Expected note name after accidental'}; |
|
|
} |
|
|
}; |
|
|
|
|
|
this.isWhiteSpace = function(ch) { |
|
|
return ch === ' ' || ch === '\t' || ch === '\x12'; |
|
|
}; |
|
|
|
|
|
this.getMeat = function(line, start, end) { |
|
|
|
|
|
|
|
|
var comment = line.indexOf('%', start); |
|
|
if (comment >= 0 && comment < end) |
|
|
end = comment; |
|
|
while (start < end && (line[start] === ' ' || line[start] === '\t' || line[start] === '\x12')) |
|
|
start++; |
|
|
while (start < end && (line[end-1] === ' ' || line[end-1] === '\t' || line[end-1] === '\x12')) |
|
|
end--; |
|
|
return {start: start, end: end}; |
|
|
}; |
|
|
|
|
|
var isLetter = function(ch) { |
|
|
return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'); |
|
|
}; |
|
|
|
|
|
var isNumber = function(ch) { |
|
|
return (ch >= '0' && ch <= '9'); |
|
|
}; |
|
|
|
|
|
this.tokenize = function(line, start, end, alphaUntilWhiteSpace) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var ret = this.getMeat(line, start, end); |
|
|
start = ret.start; |
|
|
end = ret.end; |
|
|
var tokens = []; |
|
|
var i; |
|
|
while (start < end) { |
|
|
if (line[start] === '"') { |
|
|
i = start+1; |
|
|
while (i < end && line[i] !== '"') i++; |
|
|
tokens.push({ type: 'quote', token: line.substring(start+1, i), start: start+1, end: i}); |
|
|
i++; |
|
|
} else if (isLetter(line[start])) { |
|
|
i = start+1; |
|
|
if (alphaUntilWhiteSpace) |
|
|
while (i < end && !this.isWhiteSpace(line[i])) i++; |
|
|
else |
|
|
while (i < end && isLetter(line[i])) i++; |
|
|
tokens.push({ type: 'alpha', token: line.substring(start, i), continueId: isNumber(line[i]), start: start, end: i}); |
|
|
start = i + 1; |
|
|
} else if (line[start] === '.' && isNumber(line[i+1])) { |
|
|
i = start+1; |
|
|
var int2 = null; |
|
|
var float2 = null; |
|
|
while (i < end && isNumber(line[i])) i++; |
|
|
|
|
|
float2 = parseFloat(line.substring(start, i)); |
|
|
tokens.push({ type: 'number', token: line.substring(start, i), intt: int2, floatt: float2, continueId: isLetter(line[i]), start: start, end: i}); |
|
|
start = i + 1; |
|
|
} else if (isNumber(line[start]) || (line[start] === '-' && isNumber(line[i+1]))) { |
|
|
i = start+1; |
|
|
var intt = null; |
|
|
var floatt = null; |
|
|
while (i < end && isNumber(line[i])) i++; |
|
|
if (line[i] === '.' && isNumber(line[i+1])) { |
|
|
i++; |
|
|
while (i < end && isNumber(line[i])) i++; |
|
|
} else |
|
|
intt = parseInt(line.substring(start, i)); |
|
|
|
|
|
floatt = parseFloat(line.substring(start, i)); |
|
|
tokens.push({ type: 'number', token: line.substring(start, i), intt: intt, floatt: floatt, continueId: isLetter(line[i]), start: start, end: i}); |
|
|
start = i + 1; |
|
|
} else if (line[start] === ' ' || line[start] === '\t') { |
|
|
i = start+1; |
|
|
} else { |
|
|
tokens.push({ type: 'punct', token: line[start], start: start, end: start+1}); |
|
|
i = start+1; |
|
|
} |
|
|
start = i; |
|
|
} |
|
|
return tokens; |
|
|
}; |
|
|
|
|
|
this.getVoiceToken = function(line, start, end) { |
|
|
|
|
|
var i = start; |
|
|
while (i < end && this.isWhiteSpace(line[i]) || line[i] === '=') |
|
|
i++; |
|
|
|
|
|
if (line[i] === '"') { |
|
|
var close = line.indexOf('"', i+1); |
|
|
if (close === -1 || close >= end) |
|
|
return {len: 1, err: "Missing close quote"}; |
|
|
return {len: close-start+1, token: this.translateString(line.substring(i+1, close))}; |
|
|
} else { |
|
|
var ii = i; |
|
|
while (ii < end && !this.isWhiteSpace(line[ii]) && line[ii] !== '=') |
|
|
ii++; |
|
|
return {len: ii-start+1, token: line.substring(i, ii)}; |
|
|
} |
|
|
}; |
|
|
|
|
|
var charMap = { |
|
|
"`a": 'à', "'a": "á", "^a": "â", "~a": "ã", "\"a": "ä", "oa": "å", "aa": "å", "=a": "ā", "ua": "ă", ";a": "ą", |
|
|
"`e": 'è', "'e": "é", "^e": "ê", "\"e": "ë", "=e": "ē", "ue": "ĕ", ";e": "ę", ".e": "ė", |
|
|
"`i": 'ì', "'i": "í", "^i": "î", "\"i": "ï", "=i": "ī", "ui": "ĭ", ";i": "į", |
|
|
"`o": 'ò', "'o": "ó", "^o": "ô", "~o": "õ", "\"o": "ö", "=o": "ō", "uo": "ŏ", "/o": "ø", |
|
|
"`u": 'ù', "'u": "ú", "^u": "û", "~u": "ũ", "\"u": "ü", "ou": "ů", "=u": "ū", "uu": "ŭ", ";u": "ų", |
|
|
"`A": 'À', "'A": "Á", "^A": "Â", "~A": "Ã", "\"A": "Ä", "oA": "Å", "AA": "Å", "=A": "Ā", "uA": "Ă", ";A": "Ą", |
|
|
"`E": 'È', "'E": "É", "^E": "Ê", "\"E": "Ë", "=E": "Ē", "uE": "Ĕ", ";E": "Ę", ".E": "Ė", |
|
|
"`I": 'Ì', "'I": "Í", "^I": "Î", "~I": "Ĩ", "\"I": "Ï", "=I": "Ī", "uI": "Ĭ", ";I": "Į", ".I": "İ", |
|
|
"`O": 'Ò', "'O": "Ó", "^O": "Ô", "~O": "Õ", "\"O": "Ö", "=O": "Ō", "uO": "Ŏ", "/O": "Ø", |
|
|
"`U": 'Ù', "'U": "Ú", "^U": "Û", "~U": "Ũ", "\"U": "Ü", "oU": "Ů", "=U": "Ū", "uU": "Ŭ", ";U": "Ų", |
|
|
"ae": "æ", "AE": "Æ", "oe": "œ", "OE": "Œ", "ss": "ß", |
|
|
"'c": "ć", "^c": "ĉ", "uc": "č", "cc": "ç", ".c": "ċ", "cC": "Ç", "'C": "Ć", "^C": "Ĉ", "uC": "Č", ".C": "Ċ", |
|
|
"~N": "Ñ", "~n": "ñ", |
|
|
"=s": "š", "vs": "š", |
|
|
"DH": "Ð", "dh": "ð", |
|
|
"HO": "Ő", "Ho": "ő", "HU": "Ű", "Hu": "ű", |
|
|
"'Y": "Ý", "'y": "ý", "^Y": "Ŷ", "^y": "ŷ", "\"Y": "Ÿ", "\"y": "ÿ", |
|
|
"vS": "Š", "vZ": "Ž", "vz": 'ž' |
|
|
|
|
|
|
|
|
}; |
|
|
var charMap1 = { |
|
|
"#": "♯", |
|
|
"b": "♭", |
|
|
"=": "♮" |
|
|
}; |
|
|
var charMap2 = { |
|
|
"201": "♯", |
|
|
"202": "♭", |
|
|
"203": "♮", |
|
|
"241": "¡", |
|
|
"242": "¢", "252": "a", "262": "2", "272": "o", "302": "Â", "312": "Ê", "322": "Ò", "332": "Ú", "342": "â", "352": "ê", "362": "ò", "372": "ú", |
|
|
"243": "£", "253": "«", "263": "3", "273": "»", "303": "Ã", "313": "Ë", "323": "Ó", "333": "Û", "343": "ã", "353": "ë", "363": "ó", "373": "û", |
|
|
"244": "¤", "254": "¬", "264": " ́", "274": "1⁄4", "304": "Ä", "314": "Ì", "324": "Ô", "334": "Ü", "344": "ä", "354": "ì", "364": "ô", "374": "ü", |
|
|
"245": "¥", "255": "-", "265": "μ", "275": "1⁄2", "305": "Å", "315": "Í", "325": "Õ", "335": "Ý", "345": "å", "355": "í", "365": "õ", "375": "ý", |
|
|
"246": "¦", "256": "®", "266": "¶", "276": "3⁄4", "306": "Æ", "316": "Î", "326": "Ö", "336": "Þ", "346": "æ", "356": "î", "366": "ö", "376": "þ", |
|
|
"247": "§", "257": " ̄", "267": "·", "277": "¿", "307": "Ç", "317": "Ï", "327": "×", "337": "ß", "347": "ç", "357": "ï", "367": "÷", "377": "ÿ", |
|
|
"250": " ̈", "260": "°", "270": " ̧", "300": "À", "310": "È", "320": "Ð", "330": "Ø", "340": "à", "350": "è", "360": "ð", "370": "ø", |
|
|
"251": "©", "261": "±", "271": "1", "301": "Á", "311": "É", "321": "Ñ", "331": "Ù", "341": "á", "351": "é", "361": "ñ", "371": "ù" }; |
|
|
this.translateString = function(str) { |
|
|
var arr = str.split('\\'); |
|
|
if (arr.length === 1) return str; |
|
|
var out = null; |
|
|
arr.forEach(function(s) { |
|
|
if (out === null) |
|
|
out = s; |
|
|
else { |
|
|
var c = charMap[s.substring(0, 2)]; |
|
|
if (c !== undefined) |
|
|
out += c + s.substring(2); |
|
|
else { |
|
|
c = charMap2[s.substring(0, 3)]; |
|
|
if (c !== undefined) |
|
|
out += c + s.substring(3); |
|
|
else { |
|
|
c = charMap1[s.substring(0, 1)]; |
|
|
if (c !== undefined) |
|
|
out += c + s.substring(1); |
|
|
else |
|
|
out += "\\" + s; |
|
|
} |
|
|
} |
|
|
} |
|
|
}); |
|
|
return out; |
|
|
}; |
|
|
this.getNumber = function(line, index) { |
|
|
var num = 0; |
|
|
while (index < line.length) { |
|
|
switch (line[index]) { |
|
|
case '0':num = num*10;index++;break; |
|
|
case '1':num = num*10+1;index++;break; |
|
|
case '2':num = num*10+2;index++;break; |
|
|
case '3':num = num*10+3;index++;break; |
|
|
case '4':num = num*10+4;index++;break; |
|
|
case '5':num = num*10+5;index++;break; |
|
|
case '6':num = num*10+6;index++;break; |
|
|
case '7':num = num*10+7;index++;break; |
|
|
case '8':num = num*10+8;index++;break; |
|
|
case '9':num = num*10+9;index++;break; |
|
|
default: |
|
|
return {num: num, index: index}; |
|
|
} |
|
|
} |
|
|
return {num: num, index: index}; |
|
|
}; |
|
|
|
|
|
this.getFraction = function(line, index) { |
|
|
var num = 1; |
|
|
var den = 1; |
|
|
if (line[index] !== '/') { |
|
|
var ret = this.getNumber(line, index); |
|
|
num = ret.num; |
|
|
index = ret.index; |
|
|
} |
|
|
if (line[index] === '/') { |
|
|
index++; |
|
|
if (line[index] === '/') { |
|
|
var div = 0.5; |
|
|
while (line[index++] === '/') |
|
|
div = div /2; |
|
|
return {value: num * div, index: index-1}; |
|
|
} else { |
|
|
var iSave = index; |
|
|
var ret2 = this.getNumber(line, index); |
|
|
if (ret2.num === 0 && iSave === index) |
|
|
ret2.num = 2; |
|
|
if (ret2.num !== 0) |
|
|
den = ret2.num; |
|
|
index = ret2.index; |
|
|
} |
|
|
} |
|
|
|
|
|
return {value: num/den, index: index}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function getTitleNumber(str){ |
|
|
|
|
|
const regex = /^(\d+)\./; |
|
|
|
|
|
|
|
|
const match = regex.exec(str); |
|
|
|
|
|
|
|
|
if (match) { |
|
|
|
|
|
|
|
|
const foundNumber = match[1]; |
|
|
return foundNumber; |
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
|
return null; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
var thePatterns = [ |
|
|
{ match: /,\s*[Tt]he$/, replace: "The " }, |
|
|
{ match: /,\s*[Aa]$/, replace: "A " }, |
|
|
{ match: /,\s*[Aa]n$/, replace: "An " }, |
|
|
] |
|
|
|
|
|
this.theReverser = function (str) { |
|
|
|
|
|
for (var i = 0; i < thePatterns.length; i++) { |
|
|
var thisPattern = thePatterns[i] |
|
|
var match = str.match(thisPattern.match) |
|
|
if (match) { |
|
|
var theTitleNumber = getTitleNumber(str); |
|
|
if (theTitleNumber){ |
|
|
|
|
|
|
|
|
|
|
|
str = str.replace(theTitleNumber+".",""); |
|
|
str = str.trim(); |
|
|
} |
|
|
var len = match[0].length |
|
|
var result = thisPattern.replace + str.substring(0, str.length - len); |
|
|
|
|
|
if (theTitleNumber){ |
|
|
result = theTitleNumber+". "+result; |
|
|
} |
|
|
|
|
|
return result; |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
return str; |
|
|
|
|
|
}; |
|
|
|
|
|
this.stripComment = function(str) { |
|
|
var i = str.indexOf('%'); |
|
|
if (i >= 0) |
|
|
return parseCommon.strip(str.substring(0, i)); |
|
|
return parseCommon.strip(str); |
|
|
}; |
|
|
|
|
|
this.getInt = function(str) { |
|
|
|
|
|
|
|
|
var x = parseInt(str); |
|
|
if (isNaN(x)) |
|
|
return {digits: 0}; |
|
|
var s = "" + x; |
|
|
var i = str.indexOf(s); |
|
|
return {value: x, digits: i+s.length}; |
|
|
}; |
|
|
|
|
|
this.getFloat = function(str) { |
|
|
|
|
|
|
|
|
var x = parseFloat(str); |
|
|
if (isNaN(x)) |
|
|
return {digits: 0}; |
|
|
var s = "" + x; |
|
|
var i = str.indexOf(s); |
|
|
return {value: x, digits: i+s.length}; |
|
|
}; |
|
|
|
|
|
this.getMeasurement = function(tokens) { |
|
|
if (tokens.length === 0) return { used: 0 }; |
|
|
var used = 1; |
|
|
var num = ''; |
|
|
if (tokens[0].token === '-') { |
|
|
tokens.shift(); |
|
|
num = '-'; |
|
|
used++; |
|
|
} |
|
|
else if (tokens[0].type !== 'number') return { used: 0 }; |
|
|
num += tokens.shift().token; |
|
|
if (tokens.length === 0) return { used: 1, value: parseInt(num) }; |
|
|
var x = tokens.shift(); |
|
|
if (x.token === '.') { |
|
|
used++; |
|
|
if (tokens.length === 0) return { used: used, value: parseInt(num) }; |
|
|
if (tokens[0].type === 'number') { |
|
|
x = tokens.shift(); |
|
|
num = num + '.' + x.token; |
|
|
used++; |
|
|
if (tokens.length === 0) return { used: used, value: parseFloat(num) }; |
|
|
} |
|
|
x = tokens.shift(); |
|
|
} |
|
|
switch (x.token) { |
|
|
case 'pt': return { used: used+1, value: parseFloat(num) }; |
|
|
case 'px': return { used: used+1, value: parseFloat(num) }; |
|
|
case 'cm': return { used: used+1, value: parseFloat(num)/2.54*72 }; |
|
|
case 'in': return { used: used+1, value: parseFloat(num)*72 }; |
|
|
default: tokens.unshift(x); return { used: used, value: parseFloat(num) }; |
|
|
} |
|
|
}; |
|
|
var substInChord = function(str) { |
|
|
str = str.replace(/\\n/g, "\n"); |
|
|
str = str.replace(/\\"/g, '"'); |
|
|
return str; |
|
|
}; |
|
|
this.getBrackettedSubstring = function(line, i, maxErrorChars, _matchChar) |
|
|
{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var matchChar = _matchChar || line[i]; |
|
|
var pos = i+1; |
|
|
var esc = false; |
|
|
while ((pos < line.length) && (esc || line[pos] !== matchChar)) { |
|
|
esc = line[pos] === '\\'; |
|
|
++pos; |
|
|
} |
|
|
if (line[pos] === matchChar) |
|
|
return [pos-i+1,substInChord(line.substring(i+1, pos)), true]; |
|
|
else |
|
|
{ |
|
|
pos = i+maxErrorChars; |
|
|
if (pos > line.length-1) |
|
|
pos = line.length-1; |
|
|
return [pos-i+1, substInChord(line.substring(i+1, pos)), false]; |
|
|
} |
|
|
}; |
|
|
}; |
|
|
|
|
|
Tokenizer.prototype.peekLine = function() { |
|
|
return this.lines[this.lineIndex] |
|
|
} |
|
|
|
|
|
Tokenizer.prototype.nextLine = function() { |
|
|
if (this.lineIndex > 0) { |
|
|
this.multilineVars.iChar += this.lines[this.lineIndex-1].length + 1; |
|
|
} |
|
|
if (this.lineIndex < this.lines.length) { |
|
|
var result = this.lines[this.lineIndex] |
|
|
this.lineIndex++ |
|
|
return result |
|
|
} |
|
|
return null |
|
|
} |
|
|
|
|
|
module.exports = Tokenizer; |
|
|
|