Skip to content

Commit

Permalink
- Added CTCPMessageFactory
Browse files Browse the repository at this point in the history
- Removed (incorrect) DCC parsing
- Updated grammar
  • Loading branch information
sebaFlame committed Mar 11, 2024
1 parent 73161a5 commit 4413f76
Show file tree
Hide file tree
Showing 7 changed files with 587 additions and 337 deletions.
13 changes: 0 additions & 13 deletions data/ll1_irc_grammar.txt
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,6 @@ UTF8WithoutNullBellCrLfSpaceCommaAndColon -> UTF8WithoutNullCrLFBase | 0x3B | 0x
# Comma seperated middle list (key)
UTF8WithoutNullCrLfCommaSpaceListTerminal -> UTF8WithoutNullCrLFBase | 0x07 | 0x3A | 0x3B | 0x40 | Special .

# DCC Filename
DCCFileNameTerminalsBase -> UTF8WithoutAlphaNumericFormatCTCPNullCrLFBase | AlphaNumeric | 0x01 | 0x02 | 0x03 | 0x04 | 0x0F | 0x11 | 0x1D | 0x1E | 0x1F | 0x07 | 0x3B | 0x40 | Special .
DCCFileNameTerminals -> DCCFileNameTerminalsBase | 0x20 .

# Params base terminal collection
MiddlePrefixListFormatBaseTerminals -> UTF8WithoutAlphaNumericFormatCTCPNullCrLfSpaceCommaColon MiddlePrefixListFormatBaseTerminals | LowerCaseLetter MiddlePrefixListFormatBaseTerminals | UpperCaseLetter MiddlePrefixListFormatBaseTerminals .
MiddlePrefixListFormatBase -> Format MiddlePrefixListFormatBase | MiddlePrefixListFormatBaseTerminals MiddlePrefixListFormatBase .
Expand Down Expand Up @@ -286,12 +282,3 @@ CTCPParams -> 0x20 CTCPParamsSuffix | .
CTCPParamsSuffix -> CTCPParamsMiddle | .
CTCPParamsMiddle -> CTCPMiddle CTCPParamsMiddle | 0x20 CTCPParamsMiddle .
CTCPMessageSuffix -> 0x01 | .

# DCC
DCCMessage -> DCCType Spaces DCCQuotedArgument Spaces DCCArgument Spaces DCCArgument .
DCCType -> CTCPMiddle .
DCCArgument -> CTCPMiddle .
DCCQuotedArgument -> DCCQuotedFilename | DCCFilenameList .
DCCFilenameList -> DCCFileNameTerminalsBase DCCFilenameList .
DCCFilenameSpaceList -> DCCFileNameTerminals DCCFilenameSpaceList .
DCCQuotedFilename -> 0x22 DCCFilenameSpaceList 0x22 .
120 changes: 119 additions & 1 deletion src/NippyWard.IRC.Parser/BaseIRCMessageFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ public BaseIRCMessageFactory Parameter(Utf8String parameter)
{
CheckIfConstructed(this);

//paramter can be empty
//parameter can be empty
if(parameter is null)
{
return this;
Expand Down Expand Up @@ -579,6 +579,124 @@ out int extraLength
return this;
}

public BaseIRCMessageFactory Parameter(Action<ICTCPMessageFactory> tokenFactory)
{
CheckIfConstructed(this);

//paramter can be empty
if (tokenFactory is null)
{
return this;
}

Token message = this.GetMessage();

if (!message.TryGetFirstTokenOfType
(
TokenType.Params,
out Token parameters
))
{
throw new InvalidOperationException
(
"No Params token found, no command has been defined yet"
);
}

//check if too many parameters
if (ParametersWillExceedMax(parameters))
{
throw ThrowTooManyParameters();
}

CTCPMessageFactory fac = new CTCPMessageFactory();
tokenFactory(fac);
Token ctcpToken = fac.Verify();

MessageLengthVisitor lengthVisitor = GetMessageLengthVisitor();
int messageLength = lengthVisitor.ComputeTokenLength
(
message
);

int ctcpLength = lengthVisitor.ComputeTokenLength
(
ctcpToken
);

Token space = Token.Create
(
TokenType.Space,
Space
);

Token paramsSuffix = Token.Create(TokenType.ParamsSuffix);

//link space and suffix together to form the parameter
space.Combine(paramsSuffix);

Token paramsPrefix = Token.Create
(
TokenType.ParamsPrefix,
space
);

int extraLength = 0;
//CTCP message can be embedded into a ParamsSuffix
if (ctcpToken.TryGetFirstTokenOfType(TokenType.CTCPParams, out Token par)
&& par.IsEmpty) //TODO: needs MessageLengthVisitor
{
paramsSuffix.Child = ctcpToken;

extraLength = 1; //account for ' '
}
//if parameters is not empty, the CTCP message needs to be embedded
//into a trailing
else
{
//initialize a colon for the trailing parameter
Token colon = Token.Create
(
TokenType.Colon,
Colon
);

//provide an empty trailing prefix
Token trailingPrefix = Token.Create
(
TokenType.TrailingPrefix,
ctcpToken
);

colon.Combine(trailingPrefix);

paramsSuffix.Child = Token.Create
(
TokenType.Trailing,
colon
);

extraLength = 2; //account for ':' and ' '
}

if (MessageWillExceedMaxLength
(
message,
messageLength,
ctcpLength + extraLength,
out _
))
{
paramsPrefix.Dispose();

throw ThrowParameterTooLong();
}

LinkConstructedParameter(parameters, paramsPrefix);

return this;
}

/// <summary>
/// add a new message, using same source/tags if configured
/// </summary>
Expand Down
240 changes: 240 additions & 0 deletions src/NippyWard.IRC.Parser/CTCPMessageFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
using NippyWard.IRC.Parser.Tokens;
using NippyWard.Text;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace NippyWard.IRC.Parser
{
internal class CTCPMessageFactory : ICTCPMessageFactory
{
private Token _ctcp;

private static readonly byte[] _Seperators = new byte[]
{
0x01, //CTCP data marker
0x20 //space
};

private static ReadOnlyMemory<byte> Delimiter
=> new ReadOnlyMemory<byte>(_Seperators, 0, 1);

private static ReadOnlyMemory<byte> Space
=> new ReadOnlyMemory<byte>(_Seperators, 1, 1);

public CTCPMessageFactory()
{
this._ctcp = this.CreateNewMessage();
}

private Token CreateNewMessage()
{
Token delim = Token.Create
(
TokenType.CTCP,
Delimiter
);

delim
.Combine(Token.Create(TokenType.CTCPCommand))
.Combine(Token.Create(TokenType.CTCPParams))
.Combine
(
Token.Create
(
TokenType.CTCPMessageSuffix,
Token.Create(TokenType.CTCP, Delimiter)
)
);

return Token.Create
(
TokenType.CTCPMessage,
delim
);
}

public ICTCPMessageFactory Command(string command)
=> this.Command((Utf8String)command);

public ICTCPMessageFactory Command(Utf8String command)
{
if (command is null
|| command.IsEmpty)
{
return this;
}

if (!this._ctcp.TryGetFirstTokenOfType
(
TokenType.CTCPCommand,
out Token cmd
))
{
throw new InvalidOperationException
(
"No CTCP command token found!"
);
}

if (!cmd.IsEmpty)
{
throw new ArgumentException
(
"CTCP Command has already been defined"
);
}

SequenceReader<byte> reader = command.CreateSequenceReader();
if(!IRCParser.TryParseCTCPMiddle
(
ref reader,
out Token middle
))
{
throw new NotSupportedException
(
"CTCP command could not be parsed"
);
}

//link the new command
cmd.Child = middle;

return this;
}

public ICTCPMessageFactory Parameter(string parameter)
=> this.Parameter((Utf8String)parameter);

public ICTCPMessageFactory Parameter(Utf8String parameter)
{
if (parameter is null)
{
return this;
}

//find the CTCPParams
if (!this._ctcp.TryGetFirstTokenOfType
(
TokenType.CTCPParams,
out Token pars
))
{
throw new InvalidOperationException
(
"No CTCP parameter token found!"
);
}

//create a CTCPParamsSuffix if necessary
Token suffix;
if (!pars.TryGetFirstTokenOfType
(
TokenType.CTCPParamsSuffix,
out suffix
))
{
Token space = Token.Create
(
TokenType.Space,
Space
);

suffix = Token.Create
(
TokenType.CTCPParamsSuffix
);

space.Combine(suffix);

pars.Child = space;
}

SequenceReader<byte> reader = parameter.CreateSequenceReader();
//first check if empty, then leave CTCPParamsSuffix empty
if (parameter.IsEmpty)
{
//NOP
}
else if (IRCParser.TryParseCTCPParamsMiddle
(
ref reader,
out Token middleParam
))
{
if (!suffix.TryGetFirstTokenOfType
(
TokenType.CTCPParamsMiddle,
out Token paramsMiddle
))
{
//first parameter, link in the newly parsed middleParam
suffix.Child = middleParam;
}
else
{
//get the first child of the newly parsed middleParam
Token firstChild = middleParam.Child;

//reset the child for correct disposal
middleParam.Child = null;
middleParam.Dispose();

if (paramsMiddle.IsEmpty)
{
paramsMiddle.Child = firstChild;
}
else
{
//seperate tokens by space
Token space = Token.Create
(
TokenType.Space,
Space
);

//link the space with the newly parsed middle token
space.Combine(firstChild);

//find the last child of paramsMiddle
Token lastChild = paramsMiddle.Child.GetLastToken();

//link in the newly parsed middleParam
lastChild.Combine(space);
}
}
}
else
{
throw new ArgumentException("CTCP parameter could not be parsed");
}

return this;
}

internal Token Verify()
{
//a CTCP command can not be empty
if (this._ctcp.TryGetFirstTokenOfType(TokenType.CTCPCommand, out Token cmd)
&& cmd.IsEmpty)
{
ThrowCommandNotAssigned();
}

return this._ctcp;
}

[DoesNotReturn]
protected static void ThrowCommandNotAssigned()
=> throw new ArgumentException
(
"No command has been assigned to the CTCP message"
);
}
}
Loading

0 comments on commit 4413f76

Please sign in to comment.