@garyp/common-object-x
Version:
754 lines (620 loc) • 16 kB
JavaScript
const { Objectx } = require('./Objectx');
//---------------------------------------------------------------------
//---------------------------------------------------------------------
"use strict";
class Streamx extends Objectx {
constructor() {
super();
this.fcharsBuffer = this.fcharsBuffer ?? null;
this.fintPos = this.fintPos ?? null;
}
/* -- static methods --
dq
isWhitespace
coerce
conformCopyToLastIndex
sq
from
on
coerceBracketPair
*/
asLines() {
var lines;
this.reset();
lines = this.newColl();
while(this.hasNext())
lines.add(this.nl());
return lines;
}
asNonBlankLines() {
var lines;
this.reset();
lines = this.newColl();
while(this.hasNext())
{var line;
line = this.nl();
if(!(isBlank(line.zzwrap())))
lines.add(line);}
return lines;
}
asString() {
return this.toString();
}
at(aPos) {
return this.prAt(this.conformForBuffer(aPos));
}
atDq() {
return this.hasNext() && this.objectEquals(this.peek(),this.constructor.dq());
}
atEnd() {
return !this.hasNext();
}
atQuote() {
return this.atSq() || this.atDq();
}
atSafely(aPos) {
//ans char at aPos, or null character
if(this.isEmpty() || aPos < 0 || aPos > this.lastPos())
return null;
return this.at(aPos);
}
atSq() {
return this.hasNext() && this.objectEquals(this.peek(),this.constructor.sq());
}
atWhitespace() {
return this.hasNext() ? this.constructor.isWhitespace(this.peek()) : false;
}
basicParseArgs(modifierFct) {
/* NOTE WELL --
The modifierFct is used only for unquoted values.
Quoted values are assumed to be strings
multiply(a, b)
=> [a, b]
Assume our contents is after the opening paren
e.g.
(10, 'bobby kindro', true, 8)
=> [10, 'bobby kindro', true, 8] */
var closer, delims, args;
(closer = ")");
delims = this.tools().pairWith(",",closer);
args = this.newColl();
this.sw();
while(this.hasNext())
{var next;
next = null;
if(this.atQuote())
{next = this.nsq();
this.upToAny(delims);}
else
{next = this.upToAny(delims);
if(modifierFct != null)
next = this.tools().evaluateWith(modifierFct,next);}
args.add(next);
if(this.objectEquals(this.peekAtOffset(- 1).toString(),closer))
return args;
this.sw();}
return args;
}
basicShow(obj) {
this.buffer(this.buffer().withAppended(obj));
this.setToEnd();
}
buffer(acharsBuffer) {
if(acharsBuffer !== undefined)
{this.fcharsBuffer = acharsBuffer}
else
{return this.fcharsBuffer}
}
collectForWith(evaluable, arg) {
//NOTE -- we do not collect nil values
var coll;
coll = this.newColl();
while(this.hasNext())
{var result;
result = this.tools().evaluateWithWith(evaluable,this,arg);
if(result != null)
coll.add(result);}
return coll;
}
conformForBuffer(streamIndex) {
/* streamIndex is 0-based
js buffer is 0-based
st buffer is 1-based,
So adjust st */
return this.tools().isSmalltalk() ? streamIndex + 1 : streamIndex;
}
contents() {
return this.copyFromTo(0,this.size() - 1);
}
copyFromTo(i1, i2) {
//index is pre-conformed
return this.buffer().slice(this.conformForBuffer(i1), this.conformForBuffer(i2) + 1);
}
cr() {
this.show(this.tools().endLineString());
}
extractBracketedBy(arg) {
//arg may be string or pair
var pair, newStrm, prev, left, right;
pair = this.constructor.coerceBracketPair(arg);
newStrm = this.newStream("");
prev = 0;
left = pair.at(1);
right = pair.at(2);
while(!(this.atEnd()))
{var matchPos;
this.moveTo(left);
newStrm.show(this.copyFromTo(prev,this.pos() - 1));
if(this.atEnd())
return newStrm;
matchPos = this.pos();
this.skip(left.size());
this.moveTo(right);
//If no right found
if(this.atEnd())
{newStrm.show(this.copyFromTo(matchPos,this.pos() - 1));
return newStrm;}
//Right is found
this.skip(right.size());
prev = this.pos();}
return newStrm.zzwrap();
}
extractLineComments(delim) {
//arg may be string or pair
var newLines;
newLines = this.newColl();
for (let line of this.asLines().zzunwrap()) {
newLines.add(line.zzwrap().upTo(delim))
}
return this.constructor.on(newLines.join(this.tools().endLineString()));
}
hasNext() {
return this.pos() < this.size();
}
initialize() {
super.initialize();
this.buffer("");
this.pos(0);
}
isEmpty() {
return this.objectEquals(this.size(),0);
}
lastPos() {
//zero indexed, the last position we have a char (will be -1 for empty case)
return this.size() - 1;
}
matchPosOf(strToken) {
//If not found, we answer end (lastPos+1)
var hold, pos;
hold = this.pos();
this.moveTo(strToken);
pos = this.pos();
this.pos(hold);
return pos;
}
moveTo(chOrString) {
//move to char, ans true if found, false if not found and pos set to end
var substring;
substring = chOrString.toString();
while(this.hasNext())
{if(this.nextEquals(substring))
return true;
this.skip(1);}
return false;
}
moveToAny(collDelims) {
/* If found, pos is at match and return matching delim
else return nil */
while(this.hasNext())
{
for (let delim of collDelims.zzunwrap()) {
if(this.nextEquals(delim.toString()))
return delim
}
this.skip(1);}
return null;
}
moveToWhitespaceOrAny(collDelims) {
/* If found, pos is at match and return matching delim
else return nil */
while(this.hasNext())
{if(this.atWhitespace())
return "";
for (let delim of collDelims.zzunwrap()) {
if(this.nextEquals(delim.toString()))
return delim
}
this.skip(1);}
return null;
}
next(count) {
if(count !== undefined)
{var p1, p2, s;
p1 = this.pos();
p2 = (p1 + count) - 1;
s = this.copyFromTo(p1,p2);
this.pos(p2 + 1);
return s;}
else
{//Returns string containing next char in stream
var ans;
ans = this.peek();
this.skip(1);
return ans.asString();}
}
nextEquals(aSubstring) {
var substring, remaining, hold, sub;
substring = aSubstring.toString().zzwrap();
remaining = this.remaining();
if(!(remaining >= substring.size()))
return false;
//Let's see if we have a match
hold = this.pos();
for (var index=1; index<=substring.size(); index++)
if(!(this.objectEquals(this.next(),substring.at(index))))
{this.pos(hold);
return false;}
this.pos(hold);
return true;
}
nextLine() {
/* ans next line,
look for cr-lf, or lf, post-pos is beyond lf */
var line;
line = this.upTo(this.tools().lf());
line = line.zzwrap();
if(this.objectEquals(line.size(),0))
return line;
if(this.objectEquals(line.last(),this.tools().cr()))
line = line.sliceTo(- 1);
return line.zzunwrap();
}
nextPutAll(obj) {
this.basicShow(obj);
}
nl() {
return this.nextLine();
}
ns() {
/* ns is abbrev for next whitespace stream
final pos is one beyond end of string */
var p1, p2;
this.skipWhite();
p1 = this.pos();
this.skipToWhite();
p2 = this.pos() - 1;
return this.copyFromTo(p1,p2);
}
nsAll() {
var coll;
coll = this.newColl();
while(!(this.atEnd()))
{coll.add(this.ns());
this.sw();}
return coll;
}
nsq() {
//nextQuotedOrWhiteString
//TODO -- CHECK IN SMALLTALK
var bracket;
this.sw();
if(!(this.atQuote()))
return this.ns();
bracket = this.peek();
this.skip(1);
return this.upTo(bracket);
}
nsqDelim(aDelims) {
/* next string that is any of:
quoted (checked first)
or whitespace string or delimited (whichever is found first) */
//TODO -- CHECK IN SMALLTALK
var delims, bracket, value, match;
this.sw();
delims = this.collClass().coerce(aDelims);
if(!(this.atQuote()))
{value = this.upToWhitespaceOrAny(delims);
this.sw();
match = delims.findFor(delim => this.nextEquals(delim));
if(match != null)
{this.skip(match.toString().size());
this.sw();}
return value;}
bracket = this.peek();
this.skip(1);
value = this.upTo(bracket);
//Now skip delim after trailing quote (if present)
this.sw();
match = delims.findFor(delim => this.nextEquals(delim));
if(match != null)
this.skip(match.toString().size());
return value;
}
parseAllCsv() {
var coll;
coll = this.newColl();
while(this.hasNext())
{coll.add(this.parseNextCsv());
this.sw();}
return coll;
}
parseAllDelimitedBy(delim) {
var coll;
//Tabs/crs should be parsed strictly (spaces loosely)
if(this.objectEquals(delim.asString()," "))
return this.nsAll();
coll = this.newColl();
while(this.hasNext())
{var str;
str = this.upTo(delim);
str = str.trim();
coll.add(str);
this.sw();}
return coll;
}
parseArgs() {
return this.basicParseArgs(null);
}
parseKeyValuePairs() {
/* a=10 b=20
NOTE: values may be quoted */
var result, eq;
result = this.newColl();
eq = "=";
this.sw();
while(this.hasNext())
{var key, value;
key = this.upTo(eq);
value = this.nsq();
result.add(this.collClass().withWith(key,value));
this.sw();}
return result;
}
parseNextCsv() {
var dq;
this.skipWhite();
dq = "\"";
if(this.nextEquals(dq))
{var str;
this.skip(dq.size());
str = this.upTo(dq);
this.upTo(",");
return str;}
return this.upTo(",").zzwrap().trim();
}
parseQuotedWhiteStrings() {
/* each value is whitespace string, or quoted
10 20 'me foo' 30 40
(Streamx on: '10 20 ''me foo'' 30 40')
parseQuotedWhiteValues
zzunwrap
vs. */
var result;
result = this.newColl();
this.sw();
while(this.hasNext())
{result.add(this.nsq());
this.sw();}
return result;
}
parseTypedArgs() {
return this.basicParseArgs(value => this.tools().coercePrimTypeFromString(value));
}
parseWhiteStrings() {
/* each value is whitespace string, or quoted
10 20 'me foo' 30 40
(Streamx on: '10 20 ''me foo'' 30 40')
parseQuotedWhiteValues
zzunwrap
vs. */
var result;
result = this.newColl();
this.sw();
while(this.hasNext())
{result.add(this.ns());
this.sw();}
return result;
}
peek() {
return this.atSafely(this.pos());
}
peekAtOffset(offset) {
return this.at(this.pos() + offset);
}
pos(aintPos) {
if(aintPos !== undefined)
{this.fintPos = aintPos}
else
{return this.fintPos}
}
posSafely(pos) {
//set position, if out-of-bounds, set to start or end
if(pos < 0)
this.reset()
else
{if(pos > this.lastPos())
this.setToEnd()
else
this.pos(pos)};
}
prAt(index) {
//index is pre-conformed
return this.buffer()[index];
}
rejectFor(fct) {
/* Note -- we use #rejectFor: here rather than #reject:
to bypass a forced unwrap (not desired in this case) for #reject: */
return this.selectFor(each => !this.tools().evaluateWith(fct,each));
}
rem() {
return this.next(this.remaining());
}
remaining() {
return this.size() - this.pos();
}
reset() {
this.pos(0);
}
selectFor(fct) {
/* Note -- we use #selectFor: here rather than #select
to bypass a forced unwrap (not desired in this case) for #select */
var newStrm;
newStrm = this.newStream("");
while(!(this.atEnd()))
{var next;
next = this.next();
if(this.tools().evaluateWith(fct,next))
newStrm.show(next);}
newStrm.reset();
return newStrm;
}
setToEnd() {
//also see 'reset'
this.pos(this.lastPos() + 1);
}
show(obj) {
this.basicShow(obj);
}
showl(str) {
this.showLine(str);
}
showLine(strMe) {
this.show(strMe);
this.cr();
}
size() {
return this.buffer().size();
}
skip(arg) {
//Manual needed to allow method overloading -- #skip() and skip(by)
var by = (arg != null) ? arg : 1;
this.pos(this.pos() + by);
}
skipToWhite() {
//skip past whitespace, ans true if non-whitespace found
while(this.hasNext())
{if(this.atWhitespace())
return true;
this.skip(1);}
return false;
}
skipWhite() {
//skip past whitespace, ans true if non-whitespace found
while(this.hasNext())
{if(!(this.atWhitespace()))
return true;
this.skip(1);}
return false;
}
sw() {
return this.skipWhite();
}
toString() {
return this.contents();
}
upTo(searchString) {
/* ans from current pos up to but not incuding <char>.
Pos beyond <char>. */
var s, p1, match;
s = this.buffer();
p1 = this.pos();
match = s.indexOf(searchString, p1);
if (match == -1) {
this.setToEnd();
return s.substring(p1);
}
this.pos(match + searchString.length);
return s.substring(p1, match);
}
upToPass(s, passMatch) {
/* ans from current pos up to but not incuding <char>.
Pos beyond <char>. */
//Manual
var p1, p2;
p1 = this.pos();
p2 = null;
if(this.moveTo(s))
{p2 = this.pos() - 1;
if(passMatch)
this.skip(s.asString().size());}
else
{p2 = this.lastPos();
this.setToEnd();}
return this.copyFromTo(p1,p2);
}
upToAny(collDelims) {
/* Ans from current pos up to but not incuding match.
Pos beyond match. If not found ans remaining and set to end of stream */
var p1, matchingDelim, p2;
p1 = this.pos();
matchingDelim = this.moveToAny(collDelims);
p2 = this.pos() - 1;
if(matchingDelim != null)
this.skip(matchingDelim.asString().size());
return this.copyFromTo(p1,p2);
}
upToAnyStopping(delims) {
/* Ans string up to closest match.
Stop at match (i.e. to NOT move past match like we do for #upTo). */
var p1, hold, matchPositions, min;
p1 = this.pos();
hold = this.pos();
matchPositions = (delims.zzunwrap().map(ea => this.matchPosOf(ea))).zzwrap();
min = matchPositions.min();
this.pos(min);
return this.copyFromTo(p1,this.pos() - 1);
}
upToSticking(searchKey) {
//Like #upTo except my position is at match, rather than beyond it
return this.upToPass(searchKey,false);
}
upToWhitespaceOrAny(collDelims) {
/* Ans from current pos up to but not including match.
Pos beyond match. If not found ans remaining and set to end of stream */
var p1, matchingDelim, p2;
p1 = this.pos();
matchingDelim = this.moveToWhitespaceOrAny(collDelims);
p2 = this.pos() - 1;
//Note: Empty matchingDelim indicates whitespace match
if(matchingDelim != null)
{if(matchingDelim.zzwrap().isEmpty())
this.sw()
else
this.skip(matchingDelim.asString().size())}
return this.copyFromTo(p1,p2);
}
//Class Methods
static conformCopyToLastIndex(lastIndex) {
/* Smalltalk copies to lastIndex because index2 is INCLUDED
Js must be (lastIndex+1) because that index is EXCLUDED */
return this.tools().isSmalltalk() ? lastIndex : lastIndex + 1;
}
static sq() {
//Manual
return "'";
}
static from(buffer) {
var temp1;
temp1 = new this();
temp1.buffer(buffer);
return temp1;
}
static coerce(arg) {
return this.tools().isKindOf(arg,Streamjs) ? arg : this.on(arg)
}
static on(buffer) {
return this.from(buffer)
}
static coerceBracketPair(arg) {
return arg.isString() ? this.collClass().withWith(arg,arg) : arg.zzwrap()
}
static dq() {
//Manual
return '"';
}
static isWhitespace(strChar) {
//Manual
return (new RegExp(/^\s*$/)).test(strChar);
}
}
exports.Streamx = Streamx;