>>20;Qa.Ti=a.H.Wc+Qa.Jk;Qa.lb=of.aa[(Qa.Ti&of.C)>>>of.A];Qa.Aj=Qa.lb.Nd(Qa.Jk);Qa.Kk=(Ng&4190208)>>>10;Qa.Ui=(Qa.Aj&-4096)+Qa.Kk;Qa.mb=of.aa[(Qa.Ui&of.C)>>>of.A];Qa.Bj=Qa.mb.Nd(Qa.Kk);Qa.ul=(Qa.Bj&-4096)+(Ng&4095)}if(nf=Qa){a.P("linear PDE addr PDE PTE addr PTE physical");a.P("--------- ---------- -------- ---------- -------- ----------");var ve="%"+q(Ng),ve=ve+(" %%"+Sy(nf.Ti,nf.Aj)),ve=
-ve+(" %%"+Sy(nf.Ui,nf.Bj,!0)),ve=ve+(" %%"+q(nf.ul));a.P(ve)}else a.P("unsupported operation")}}else a.P("missing address")}else{if("d"==za){if("disk"==cb){ca[0]="l";ca[1]="json";bB(a,ca);break a}for(xb in Sb)if(ca[1]==xb){var tt=a.za[xb];tt?(ca.shift(),ca.shift(),tt(ca)):a.P("no dump registered for "+cb);break a}cb||(za=a.eb||"db")}else a.eb=za;if("dh"==za){var ut=cb,vt=Sa,wt="",xt=0,Mc=a.la,jd=a.N;if(jd.length){var uc=+ut||a.Ma,Og=+vt||10;isNaN(uc)?uc=Og:wt="more ";uc>jd.length&&(a.P("note: only "+
-jd.length+" available"),uc=jd.length);Mc-=uc;0>Mc&&(null==jd[jd.length-1].U?(uc=Mc+uc,Mc=0):Mc+=jd.length);var rm=[];"call"==vt&&(Og=1E5,rm=["CALL"]);for(void 0!==ut&&a.P(uc+" instructions earlier:");0...
tags, and eliminate any HTML entities.
- */
- sText = "";
- var match, re = /([\s\S]*?)<\/pre>/gi;
- while (match = re.exec(sContents)) {
- var s = match[1];
- if (s.indexOf('&') >= 0) s = s.replace(/</gi, '<').replace(/>/gi, '>').replace(/&/gi, '&');
- sText += s;
+ var a = this.dbg.resetVariables();
+ try {
+ var sText = sContents;
+ if (Str.endsWith(sPath, ".html")) {
+ /*
+ * We want to parse ONLY the text between ...
tags, and eliminate any HTML entities.
+ */
+ sText = "";
+ var match, re = /([\s\S]*?)<\/pre>/gi;
+ while (match = re.exec(sContents)) {
+ var s = match[1];
+ if (s.indexOf('&') >= 0) s = s.replace(/</gi, '<').replace(/>/gi, '>').replace(/&/gi, '&');
+ sText += s;
+ }
+ match = sText.match(/&[a-z]+;/i);
+ if (match) this.warning("unrecognized HTML entity: " + match[0]);
}
- match = sText.match(/&[a-z]+;/i);
- if (match) this.warning("unrecognized HTML entity: " + match[0]);
+ var i;
+ var asLines = sText.split(/\r?\n/);
+ for (i = 0; i < asLines.length; i++) {
+ this.nLine++;
+ if (!this.parseLine(asLines[i] + '\r\n')) break;
+ }
+ } catch(err) {
+ this.println(err.message);
+ this.nError = -1;
}
- var i;
- var asLines = sText.split(/\r?\n/);
- for (i = 0; i < asLines.length; i++) {
- if (!this.parseLine(i + 1, asLines[i])) break;
- }
- return 0;
+ this.dbg.restoreVariables(a);
+ return this.nError;
}
/**
- * parseLine(nLine, sLine)
+ * parseLine(sLine)
*
* @this {Macro10}
- * @param {number} nLine (line number)
* @param {string} sLine (line contents)
* @return {boolean}
*/
- parseLine(nLine, sLine)
+ parseLine(sLine)
{
- var reLine = /([A-Z$%.][0-9A-Z$%.]*[:=]|)\s*([^\s;]+|)\s*([^;]+|)\s*(;?.*)/i;
+ if (this.nMacroDef) {
+ if (this.nMacroDef == 1) {
+ var i = sLine.indexOf('<');
+ if (i >= 0) {
+ this.nMacroDef++;
+ sLine = sLine.substr(i+1);
+ } else {
+ this.error("expected " + this.sOperator + " definition: " + sLine);
+ }
+ }
+ if (this.nMacroDef > 1) {
+ sLine = this.appendMacro(sLine);
+ }
+ if (this.nMacroDef) return true;
+ }
+
+ if (this.chASCII != null) {
+ sLine = this.addASCII(sLine);
+ }
+
+ var reLine = /\s*([A-Z$%.][0-9A-Z$%.]*[:=]|)\s*([A-Z$%.][0-9A-Z$%.]*|)\s*([^;]+|)\s*(;?.*)/i;
var match = sLine.match(reLine);
if (!match || match[4] && match[4].slice(0, 1) != ';') {
- this.warning("failed to parse line " + nLine + ": " + sLine);
+ this.error("failed to parse line: " + sLine);
return false;
}
+
var sLabel = match[1];
- var sOperator = match[2];
+ var sOperator = match[2].toUpperCase();
var sOperands = match[3].trim();
var sComment = match[4].slice(1);
if (sLabel) {
@@ -148,14 +225,103 @@ class Macro10 {
if (chSep == ':') {
this.addLabel(sLabel);
} else {
- this.addVariable(sLabel, sOperands = sOperator + sOperands);
+ sOperands = sOperator + sOperands;
sOperator = chSep;
}
}
- if (DEBUG) this.println(Str.toDec(nLine, 5) + ": label(" + sLabel + ") operator(" + sOperator + ") operands(" + sOperands + ") comment(" + sComment + ")");
+
+ this.sOperator = sOperator;
+ sOperands = sOperands.trim();
+
+ switch(sOperator) {
+ case "":
+ break; // eg, a blank line or a line that contains only a label and/or a comment
+
+ case "=":
+ this.addAssign(sLabel, sOperands);
+ break;
+
+ case Macro10.PSEUDO_OP.ASCII:
+ case Macro10.PSEUDO_OP.ASCIZ:
+ case Macro10.PSEUDO_OP.SIXBIT:
+ this.addASCII(sOperands);
+ break;
+
+ case Macro10.PSEUDO_OP.DEFINE:
+ case Macro10.PSEUDO_OP.IFE:
+ case Macro10.PSEUDO_OP.REPEAT:
+ this.addMacro(sOperator, sOperands);
+ break;
+
+ case Macro10.PSEUDO_OP.PAGE: // TODO
+ case Macro10.PSEUDO_OP.SUBTTL: // TODO
+ break;
+
+ default:
+ if (DEBUG) this.println(Str.toDec(this.nLine, 5) + ": label(" + sLabel + ") operator(" + sOperator + ") operands(" + sOperands + ") comment(" + sComment + ")");
+ break;
+ }
+
+ if (this.nLine >= 330) {
+ this.dbg.printVariable();
+ this.error("temporary line limit reached");
+ return false;
+ }
return true;
}
+ /**
+ * parseMacro()
+ *
+ * @this {Macro10}
+ */
+ parseMacro()
+ {
+ if (!this.sMacroDef) return;
+
+ var macro = this.tblMacros[this.sMacroDef];
+ var sOperator = this.sMacroDef[0] == '@'? this.sMacroDef.substr(1) : this.sOperator;
+
+ switch(sOperator) {
+ case Macro10.PSEUDO_OP.IFE:
+ if (!macro.nOperand) {
+ this.parseText(macro.sText);
+ }
+ break;
+
+ case Macro10.PSEUDO_OP.REPEAT:
+ while (macro.nOperand-- > 0) {
+ this.parseText(macro.sText);
+ }
+ break;
+ }
+ }
+
+ /**
+ * parseText(sText)
+ *
+ * @this {Macro10}
+ * @param {string} sText
+ */
+ parseText(sText)
+ {
+ var asLines = sText.split(/\r?\n/);
+ for (var i = 0; i < asLines.length; i++) {
+ if (!this.parseLine(asLines[i] + '\r\n')) break;
+ }
+ }
+
+ /**
+ * error(sError)
+ *
+ * @this {Macro10}
+ * @param {string} sError
+ */
+ error(sError)
+ {
+ throw new Error("error" + (this.nLine? " at line " + Str.toDec(this.nLine) : "") + ": " + sError);
+ }
+
/**
* warning(sWarning)
*
@@ -164,7 +330,90 @@ class Macro10 {
*/
warning(sWarning)
{
- this.println("warning: " + sWarning);
+ this.println("warning" + (this.nLine? " at line " + Str.toDec(this.nLine) : "") + ": " + sWarning);
+ }
+
+ /**
+ * getExpression(sInput, chDelim)
+ *
+ * @this {Macro10}
+ * @param {string} sInput
+ * @param {string} [chDelim]
+ * @return {string|null} (if the input string begins with an expression, return it)
+ */
+ getExpression(sInput, chDelim)
+ {
+ var sExp = null;
+ var cNesting = 0;
+ for (var i = 0; i < sInput.length; i++) {
+ var ch = sInput[i];
+ if (ch == chDelim) {
+ if (!cNesting) {
+ sExp = sInput.substr(0, i);
+ break;
+ }
+ }
+ if (ch == '<') {
+ cNesting++;
+ } else if (ch == '>') {
+ if (--cNesting < 0) {
+ this.error("missing angle bracket(s): " + sInput);
+ break;
+ }
+ }
+ }
+ return sExp;
+ }
+
+ /**
+ * addASCII(sOperands)
+ *
+ * @this {Macro10}
+ * @param {string} sOperands
+ * @return {string} (returns whatever portion of the string was not part of an ASCII pseudo-op)
+ */
+ addASCII(sOperands)
+ {
+ var sRemain = sOperands;
+ if (this.chASCII == null) {
+ this.chASCII = this.sASCII = "";
+ if (sOperands) {
+ this.chASCII = sOperands[0];
+ sRemain = sOperands = sOperands.substr(1);
+ }
+ }
+ if (this.chASCII) {
+ var i = sOperands.indexOf(this.chASCII);
+ if (i < 0) {
+ sRemain = "";
+ } else {
+ sRemain = sOperands.substr(i + 1);
+ sOperands = sOperands.substr(0, i);
+ this.chASCII = null;
+ }
+ this.sASCII += sOperands;
+ }
+ if (this.chASCII == null) {
+ this.genASCII();
+ }
+ return sRemain;
+ }
+
+ /**
+ * addAssign(sName, sExp)
+ *
+ * @this {Macro10}
+ * @param {string} sName
+ * @param {string} sExp
+ */
+ addAssign(sName, sExp)
+ {
+ var value = this.dbg.parseExpression(sExp);
+ if (value === undefined) {
+ this.error("parseExpression(" + sExp + ")");
+ return;
+ }
+ this.addSymbol(sName, value);
}
/**
@@ -175,16 +424,200 @@ class Macro10 {
*/
addLabel(sLabel)
{
+ this.addSymbol(sLabel, this.nAddr, true);
}
/**
- * addVariable(sVar, sExp)
+ * addMacro(sOperator, sOperands)
+ *
+ * If sOperator is DEFINE, then a macro definition is expected. If it's REPEAT, then we're starting a
+ * REPEAT block instead.
+ *
+ * REPEAT blocks piggy-back on this code because they're essentially anonymous immediately-invoked macros;
+ * we use an illegal MACRO-10 symbol ('@REPEAT') to name the anonymous macro while it's being defined, and the
+ * macro's nOperand field will contain the repeat count (-1 for regular macros).
+ *
+ * The piggy-backing continues with other pseudo-ops like IFE, which again contain an anonymous block of text
+ * that is immediately invoked if the criteria associated with the expression stored in the nOperand field is
+ * satisfied. That satisfaction occurs (or doesn't occur) when parseMacro() is called, once the macro has
+ * been fully defined.
*
* @this {Macro10}
- * @param {string} sVar
- * @param {string} sExp
+ * @param {string} sOperator
+ * @param {string} sOperands
*/
- addVariable(sVar, sExp)
+ addMacro(sOperator, sOperands)
{
+ var match, name, aParms, nOperand, iDelim;
+
+ if (sOperator == Macro10.PSEUDO_OP.DEFINE) {
+ match = sOperands.match(/([A-Z$%.][0-9A-Z$%.]*)\s*(\([^)]*\)|)\s*(<|)(.*)/i);
+ if (!match) {
+ this.error("unrecognized " + sOperator + " definition: " + sOperands);
+ return;
+ }
+ /*
+ * TODO: Tighten up this parsing at some point. All this is doing is extracting entire symbols
+ * from within the parentheses, if any; it's NOT ensuring that those symbols are comma-separated
+ * with no other intervening characters.
+ */
+ name = match[1];
+ aParms = match[2].match(/[A-Z$%.][0-9A-Z$%.]*/g);
+ nOperand = -1;
+ iDelim = 3;
+ } else {
+ var sExp = this.getExpression(sOperands, ',');
+ if (!sExp) {
+ this.error("missing " + sOperator + " expression: " + sOperands);
+ return;
+ }
+ sOperands = sOperands.substr(sExp.length + 1);
+ sExp = sExp.trim();
+ match = sOperands.match(/\s*(<|)(.*)/i);
+ name = '@' + sOperator;
+ aParms = [];
+ nOperand = this.dbg.parseExpression(sExp);
+ iDelim = 1;
+ }
+
+ /*
+ * Now we need to set a global parsing state: we are either about to receive a macro definition on
+ * subsequent lines (1), the definition has already started on the current line (2), or the definition
+ * started and ended on the current line (0).
+ */
+ this.nMacroDef = 1;
+ this.sMacroDef = name;
+
+ var sText = "";
+ if (match[iDelim]) { // if there IS an angle bracket...
+ this.nMacroDef = 2; // then the macro definition has begun
+ sText = match[iDelim + 1];
+ if (sText.slice(-1) == '>') { // and if there is ALSO a closing angle bracket...
+ this.nMacroDef = 0; // the macro definition has also ended
+ sText = sText.slice(0, -1);
+ }
+ }
+ this.tblMacros[name] = {
+ name: name,
+ aParms: aParms,
+ nOperand: nOperand,
+ sText: sText
+ };
+ if (!this.nMacroDef) this.parseMacro();
+ }
+
+ /**
+ * appendMacro(sLine)
+ *
+ * @this {Macro10}
+ * @param {string} sLine
+ * @return {string}
+ */
+ appendMacro(sLine)
+ {
+ var sRemain = "";
+ for (var i = 0; i < sLine.length; i++) {
+ if (sLine[i] == '<') {
+ this.nMacroDef++;
+ } else if (sLine[i] == '>') {
+ this.nMacroDef--;
+ if (this.nMacroDef == 1) {
+ this.nMacroDef = 0;
+ sRemain = sLine.substr(i + 1);
+ sLine = sLine.substr(0, i);
+ break;
+ }
+ }
+ }
+ var macro = this.tblMacros[this.sMacroDef];
+ macro.sText += sLine;
+ if (!this.nMacroDef) this.parseMacro();
+ return sRemain;
+ }
+
+ /**
+ * addSymbol(name, value, fLabel, fGlobal, fPrivate)
+ *
+ * @this {Macro10}
+ * @param {string} name
+ * @param {number} value
+ * @param {boolean} [fLabel] (default is false, meaning the symbol is an assignment)
+ * @param {boolean} [fGlobal] (default is false, meaning the symbol is local to the current file)
+ * @param {boolean} [fPrivate] (default is false, meaning the symbol has visibility to the caller)
+ */
+ addSymbol(name, value, fLabel = false, fGlobal = false, fPrivate = false)
+ {
+ name = name.toUpperCase().substr(0, 6);
+ if (fLabel && this.tblSymbols[name] !== undefined) {
+ this.error("label " + name + " redefined");
+ return;
+ }
+ this.tblSymbols[name] = {
+ name: name,
+ value: value,
+ fLabel: fLabel,
+ fGlobal: fGlobal,
+ fPrivate: fPrivate
+ };
+ this.dbg.setVariable(name, value);
+ }
+
+ /**
+ * genASCII()
+ *
+ * Based on the last operator, generate the appropriate ASCII data.
+ *
+ * @this {Macro10}
+ */
+ genASCII()
+ {
+ var n = 0, w = 0; // number of characters in current word, and current word
+ var bits, shift; // bits per character, and bits to left-shift next character
+ var cch = this.sASCII.length;
+ if (this.sOperator == Macro10.PSEUDO_OP.ASCIZ) cch++;
+ for (var i = 0; i < cch; i++) {
+ if (!n) {
+ w = 0; shift = 29; bits = 7;
+ if (this.sOperator == Macro10.PSEUDO_OP.SIXBIT) {
+ bits--; shift++;
+ }
+ }
+ /*
+ * If we're processing an ASCIZ pseudo-op, then yes, we will fetch one character beyond
+ * the end of sASCII, which will return NaN, but when we mask a falsey value like NaN, we
+ * get zero, so it's all good.
+ */
+ var c = this.sASCII.charCodeAt(i) & 0o177;
+ w += c * Math.pow(2, shift);
+ shift -= bits;
+ n++;
+ if (shift < 0) {
+ this.genWord(w);
+ n = 0;
+ }
+ }
+ if (n) this.genWord(w);
+ }
+
+ /**
+ * genWord(w)
+ *
+ * @this {Macro10}
+ * @param {number} w
+ */
+ genWord(w)
+ {
+ this.aWords[this.nAddr++] = w;
}
}
+
+Macro10.PSEUDO_OP = {
+ ASCII: "ASCII",
+ ASCIZ: "ASCIZ",
+ SIXBIT: "SIXBIT",
+ DEFINE: "DEFINE",
+ IFE: "IFE",
+ REPEAT: "REPEAT",
+ PAGE: "PAGE",
+ SUBTTL: "SUBTTL",
+};
diff --git a/modules/pdp11/lib/debugger.js b/modules/pdp11/lib/debugger.js
index a9dc5887f..dcbcfb82b 100644
--- a/modules/pdp11/lib/debugger.js
+++ b/modules/pdp11/lib/debugger.js
@@ -89,7 +89,9 @@ class DebuggerPDP11 extends Debugger {
* Since this Debugger doesn't use replaceRegs(), we can use parentheses instead of braces.
*/
this.fInit = false;
- this.fParens = true;
+
+ this.achGroup = ['(',')'];
+ this.achAddress = [];
/*
* Most commands that require an address call parseAddr(), which defaults to dbgAddrNextCode
diff --git a/modules/shared/lib/debugger.js b/modules/shared/lib/debugger.js
index 5d36aab83..74c4ad782 100644
--- a/modules/shared/lib/debugger.js
+++ b/modules/shared/lib/debugger.js
@@ -100,7 +100,9 @@ class Debugger extends Component {
* Default base used to display all values; modified with the "s base" command.
*/
this.nBase = +parmsDbg['base'] || 16;
- this.fParens = false;
+
+ this.achGroup = ['{','}'];
+ this.achAddress = ['[',']'];
/*
* These keep track of instruction activity, but only when tracing or when Debugger checks
@@ -409,19 +411,19 @@ class Debugger extends Component {
* ...
*
* We pop 1 "binop" from aOps and 2 values from aVals whenever a "binop" of lower priority than its
- * predecessor is encountered, evaluate, and push the result back onto aVals.
+ * predecessor is encountered, evaluate, and push the result back onto aVals. Unary operators like
+ * '~' and ternary operators like '?:' are not supported.
*
- * Unary operators like '~' and ternary operators like '?:' are not supported; neither are parentheses.
+ * parseReference() makes it possible to write parenthetical-style sub-expressions by using whatever
+ * characters achGroup contains (default is braces}. Address references are resolved using the characters
+ * in achAddress (default is brackets).
*
- * However, parseReference() now makes it possible to write parenthetical-style sub-expressions by using
- * {...} (braces), as well as address references by using [...] (brackets).
+ * Why not always use parentheses for sub-expressions? Because parseReference() serves multiple purposes,
+ * the other being reference replacement in message strings passing through replaceRegs(), and some
+ * Debuggers don't want parentheses taking on a new meaning in message strings.
*
- * Why am I using braces instead of parentheses for sub-expressions? Because parseReference() serves
- * multiple purposes, the other being reference replacement in message strings passing through replaceRegs(),
- * and I didn't want parentheses taking on a new meaning in message strings.
- *
- * However, a Debugger can override this choice by setting fParens to true, if there's no conflict in its
- * replaceRegs() implementation.
+ * However, a Debugger can override these choices by modifying achGroup and/or achAddress, if there's no
+ * conflict in its replaceRegs() implementation.
*
* @this {Debugger}
* @param {string|undefined} sExp
@@ -494,8 +496,7 @@ class Debugger extends Component {
*
* Returns the given string with any "{expression}" sequences replaced with the value of the expression,
* and any "[address]" references replaced with the contents of the address. Expressions are parsed BEFORE
- * addresses. Owing to this function's simplistic parsing, nested braces/brackets are not supported
- * (define intermediate variables if needed).
+ * addresses.
*
* @this {Debugger}
* @param {string} s
@@ -503,17 +504,36 @@ class Debugger extends Component {
*/
parseReference(s) {
var a;
- var chOpen = this.fParens? '(' : '{';
- var chClose = this.fParens? ')' : '}';
- var reSubExp = new RegExp(this.fParens? "\\((.*?)\\)" : "\\{(.*?)\\}");
+ var chOpen = this.achGroup[0];
+ var chClose = this.achGroup[1];
+ var chEscape = (chOpen == '(' || chOpen == '{' || chOpen == '[')? '\\' : '';
+ var chInnerEscape = (chOpen == '['? '\\' : '');
+ var reSubExp = new RegExp(chEscape + chOpen + "([^" + chInnerEscape + chOpen + chInnerEscape + chClose + "]+)" + chEscape + chClose);
while (a = s.match(reSubExp)) {
- if (a[1].indexOf(chOpen) >= 0) break; // unsupported nested brace(s)
var value = this.parseExpression(a[1]);
- s = s.replace(chOpen + a[1] + chClose, value != null? this.toStrBase(value) : "undefined");
+ var sSearch = chOpen + a[1] + chClose;
+ var sReplace = value != null? this.toStrBase(value) : "undefined";
+ /*
+ * Note that by default, the String replace() method only replaces the FIRST occurrence,
+ * and there MIGHT be more than one occurrence of the expression we just parsed, so we could
+ * do this instead:
+ *
+ * s = s.split(sSearch).join(sReplace);
+ *
+ * However, that's knd of an expensive (slow) solution, and it's not strictly necessary, since
+ * any additional identical expressions will be picked up on a subsequent iteration through this loop.
+ */
+ s = s.replace(sSearch, sReplace);
}
- while (a = s.match(/\[(.*?)]/)) {
- if (a[1].indexOf('[') >= 0) break; // unsupported nested bracket(s)
- s = this.parseAddrReference(s, a[1]);
+ if (this.achAddress.length) {
+ chOpen = this.achAddress[0];
+ chClose = this.achAddress[1];
+ chEscape = (chOpen == '(' || chOpen == '{' || chOpen == '[')? '\\' : '';
+ chInnerEscape = (chOpen == '['? '\\' : '');
+ reSubExp = new RegExp(chEscape + chOpen + "([^" + chInnerEscape + chOpen + chInnerEscape + chClose + "]+)" + chEscape + chClose);
+ while (a = s.match(reSubExp)) {
+ s = this.parseAddrReference(s, a[1]);
+ }
}
return this.parseSysVars(s);
}
@@ -595,6 +615,28 @@ class Debugger extends Component {
return fDefined;
}
+ /**
+ * resetVariables()
+ *
+ * @this {Debugger}
+ * @return {Object}
+ */
+ resetVariables() {
+ var a = this.aVariables;
+ this.aVariables = {};
+ return a;
+ }
+
+ /**
+ * restoreVariables(a)
+ *
+ * @this {Debugger}
+ * @param {Object} a (from previous resetVariables() call)
+ */
+ restoreVariables(a) {
+ this.aVariables = a;
+ }
+
/**
* printVariable(sVar)
*
diff --git a/modules/shared/lib/strlib.js b/modules/shared/lib/strlib.js
index e8e122965..853d6e778 100644
--- a/modules/shared/lib/strlib.js
+++ b/modules/shared/lib/strlib.js
@@ -65,6 +65,10 @@ class Str {
* to indicate octal (because such a number could also be decimal or hex). Any number of commas are
* allowed; we remove them all before calling the built-in parseInt().
*
+ * More recently, we've added support for "^D", "^O", and "^B" prefixes to accommodate the base overrides
+ * that the PDP-10's MACRO-10 assembly language supports. If this support turns out to adversely affect
+ * other debuggers, then it will have to be "conditionalized".
+ *
* To summarize our non-standard alternatives: a 'y' suffix indicates binary, a '#' prefix indicates
* octal, a '$' prefix indicates hex, and a "0b" prefix indicates binary IF at least one comma is present.
* Commas are useful for grouping binary digits, but if you don't want to use them, then you must use a
@@ -110,6 +114,21 @@ class Str {
chPrefix = null;
}
}
+ else if (chPrefix == '^') {
+ chPrefix = s.charAt(1);
+ if (chPrefix == 'D') {
+ base = 10;
+ chPrefix = null;
+ }
+ else if (chPrefix == 'O') {
+ base = 8;
+ chPrefix = null;
+ }
+ else if (chPrefix == 'B') {
+ base = 2;
+ chPrefix = null;
+ }
+ }
if (chPrefix == null) {
s = s.substr(2);
}