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
	}
}