Variable getPositionConst

getPosition: Simple.Parser<[number, number]> = A.getPosition

Code editors treat code like a grid, with rows and columns. The start is row=1 and col=1. As you chomp characters, the col increments. When you run into a \n character, the row increments and col goes back to 1.

Example

In the Elm compiler, they track the start and end position of every expression like this:

type Located<A> = {
start: [number, number],
value: A,
end: [number, number]
}

const Located = <A>(start: [number, number]) => value: A) => (end: [number, number]): Located<A> => ({
start: start,
value: value,
end: end
})

const located = <A>(parser: Parser<A>): Parser<Located<A>> => {
return succeed(Located<A>)
.apply(getPosition)
.apply(parser)
.apply(getPosition)
}

So if there is a problem during type inference, they use this saved position information to underline the exact problem!

Note: Tabs count as one character, so if you are parsing something like Python, I recommend sorting that out after parsing. So if I wanted the ^^^^ underline like in Elm, I would find the row in the source code and do something like this:

function toUnderlineChar(
minCol: number,
maxCol: number,
col: number,
char: string
): string {
if (minCol <= col && col <= maxCol) {
return "^";
} else if (char === "\t") {
return "\t";
} else {
return " ";
}
}

function makeUnderline(row: string, minCol: number, maxCol: number): string {
const listOfChars: string[] = [...row];
const underline: string[] = listOfChars.map((char, index) =>
toUnderlineChar(minCol, maxCol, index, char)
);
return underline.join("");
}

So it would preserve any tabs from the source line. There are tons of other ways to do this though. The point is just that you handle the tabs after parsing but before anyone looks at the numbers in a context where tabs may equal 2, 4, or 8.