Livt packages
Eccelerators.Cli
Version 1.4.1
Transport-independent, bounded command-line input, parsing, editing, and output for Livt FPGA applications.
Published files (23)
src/CliParser.lvt
namespace Eccelerators.Cli
using Livt.IO
/**
* Parses command and argument token spans from a completed CLI line.
* Owns B with at least 64 byte addresses in the inherited clock/reset context.
* One caller serializes operations; only written, logically valid cells are read.
* Clear discards metadata without requiring payload reset. Calls may take multiple cycles.
* Defaults to block RAM; consumers may select another compatible IRam backend.
*/
component CliParser<B: IRam<byte> & new() = Ram<byte, 64, MemoryStyle.Block>>
{
// Maximum retained argument count and command-plus-argument span count.
public const MAX_ARGUMENTS: int = 8
const MAX_TOKENS: int = 9
// Only the written prefix below lineLength is readable.
line: B
lineLength: byte
// Parsed spans into the initialized line prefix.
tokenStarts: byte[9]
tokenLengths: byte[9]
tokenCount: byte
tooManyArguments: bool
/**
* Creates a right-sized line store; length metadata guards unwritten cells.
*/
new()
{
this.line = new B()
}
/**
* Appends one byte to the parser line snapshot.
*/
public fn AppendByte(value: byte) bool
{
if ((this.lineLength as int) >= CliLineEditor.MAX_LINE_LENGTH) { return false }
var index: int = this.lineLength as int
this.line.Write(index, value)
this.lineLength = (index + 1) as byte
return true
}
/**
* Removes the last byte from the parser line snapshot.
*/
public fn RemoveLastByte() bool
{
if (this.lineLength == 0) { return false }
var nextLength: int = (this.lineLength as int) - 1
this.lineLength = nextLength as byte
return true
}
/**
* Parses the current line snapshot into whitespace-separated token spans.
*/
public fn Parse()
{
var index: int = 0
this.tokenCount = 0
this.tooManyArguments = false
for (var clearIndex = 0; clearIndex < MAX_TOKENS; clearIndex++) {
this.tokenStarts[clearIndex] = 0
this.tokenLengths[clearIndex] = 0
}
while (index < (this.lineLength as int)) {
var current: byte = this.line.Read(index)
while (index < (this.lineLength as int) && (current == 0x20 || current == 0x09)) {
index = index + 1
if (index < (this.lineLength as int)) { current = this.line.Read(index) }
}
if (index < (this.lineLength as int)) {
var start: int = index
current = this.line.Read(index)
while (index < (this.lineLength as int) && current != 0x20 && current != 0x09) {
index = index + 1
if (index < (this.lineLength as int)) { current = this.line.Read(index) }
}
if ((this.tokenCount as int) < MAX_TOKENS) {
this.tokenStarts[this.tokenCount as int] = start as byte
this.tokenLengths[this.tokenCount as int] = (index - start) as byte
this.tokenCount = ((this.tokenCount as int) + 1) as byte
} else {
this.tooManyArguments = true
}
}
}
}
/**
* Returns the command token length, or zero for an empty line.
*/
public fn GetCommandLength() int
{
if (this.tokenCount == 0) { return 0 }
return this.tokenLengths[0] as int
}
/**
* Returns the complete retained line length before token parsing.
*/
public fn GetLineLength() int
{
return this.lineLength as int
}
/**
* Returns one byte from the complete retained line, or zero outside it.
*/
public fn GetLineByte(index: int) byte
{
if (index < 0 || index >= (this.lineLength as int)) { return 0x00 }
return this.line.Read(index)
}
/**
* Returns true when the command token exactly matches expected.
*/
public fn CommandEquals(expected: byte[]) bool
{
var expectedLength: int = expected.Length()
if (this.GetCommandLength() != expectedLength) { return false }
var start: int = this.tokenStarts[0] as int
for (var i = 0; i < expectedLength; i++) {
if (this.line.Read(start + i) != expected[i]) { return false }
}
return true
}
/**
* Returns the number of parsed argument tokens.
*/
public fn GetArgumentCount() int
{
if (this.tokenCount == 0) { return 0 }
return (this.tokenCount as int) - 1
}
/**
* Returns one argument length, or zero for an invalid argument index.
*/
public fn GetArgumentLength(index: int) int
{
if (index < 0 || index >= MAX_ARGUMENTS) { return 0 }
var tokenIndex: byte = (index + 1) as byte
if (tokenIndex >= this.tokenCount) { return 0 }
return this.tokenLengths[tokenIndex as int] as int
}
/**
* Returns one argument byte, or zero for an invalid index or offset.
*/
public fn GetArgumentByte(index: int, offset: int) byte
{
if (index < 0 || index >= MAX_ARGUMENTS) { return 0 }
var tokenIndex: byte = (index + 1) as byte
if (tokenIndex >= this.tokenCount) { return 0x00 }
if (offset < 0 || offset >= (this.tokenLengths[tokenIndex as int] as int)) { return 0x00 }
return this.line.Read((this.tokenStarts[tokenIndex as int] as int) + offset)
}
/**
* Returns true when one argument exactly matches expected.
*/
public fn ArgumentEquals(index: int, expected: byte[]) bool
{
var expectedLength: int = expected.Length()
if (this.GetArgumentLength(index) != expectedLength) { return false }
for (var i = 0; i < expectedLength; i++) {
if (this.GetArgumentByte(index, i) != expected[i]) { return false }
}
return true
}
/**
* Returns true when tokens exceeded the supported argument count.
*/
public fn HasTooManyArguments() bool
{
return this.tooManyArguments
}
/**
* Clears all parsed token metadata.
*/
public fn Clear()
{
this.lineLength = 0
this.tokenCount = 0
this.tooManyArguments = false
}
}