This is easy to implement using a simple state machine:
function TryParseStringLiteral(const ALiteral: string; out AString: string): Boolean;
var
LLiteral: string;
i, c: Integer;
InQuotes: Boolean;
InCharCode: Boolean;
NumCodeStr: string;
NumVal: Integer;
begin
LLiteral := ALiteral.Trim;
SetLength(AString, LLiteral.Length);
InQuotes := False;
InCharCode := False;
NumCodeStr := '';
NumVal := 0;
c := 0;
for i := 1 to LLiteral.Length do
begin
if InCharCode then
begin
if (LLiteral[i] = '#') or (LLiteral[i] = '''') then
begin
if TryStrToInt(NumCodeStr, NumVal) and InRange(NumVal, Word.MinValue, Word.MaxValue) then
begin
Inc(c);
AString[c] := Chr(NumVal);
end
else
Exit(False);
InCharCode := LLiteral[i] = '#';
InQuotes := LLiteral[i] = '''';
NumCodeStr := '';
NumVal := 0;
end
else
NumCodeStr := NumCodeStr + LLiteral[i];
end
else if InQuotes then
begin
if (LLiteral[i] = '''') and (i < LLiteral.Length) and (LLiteral[Succ(i)] = '''') then
begin
InQuotes := False; // a bit of a hack
Inc(c);
AString[c] := '''';
end
else if LLiteral[i] = '''' then
InQuotes := False
else
begin
Inc(c);
AString[c] := LLiteral[i];
end;
end
else if LLiteral[i] = '''' then
InQuotes := True
else if LLiteral[i] = '#' then
InCharCode := True
else
Exit(False);
end;
if InCharCode then
if TryStrToInt(NumCodeStr, NumVal) and InRange(NumVal, Word.MinValue, Word.MaxValue) then
begin
Inc(c);
AString[c] := Chr(NumVal);
end
else
Exit(False);
if InQuotes then
Exit(False);
SetLength(AString, c);
Result := True;
end;
To try it:
procedure TForm1.Edit1Change(Sender: TObject);
var
S: string;
begin
if TryParseStringLiteral(Edit1.Text, S) then
Edit2.Text := S
else
Edit2.Text := ' -- Invalid string literal -- ';
end;
Some examples:
'This is a test!' This is a test!
'What''s up?' What's up?
'alpha'#64'beta'#63 alpha@beta?
'alpha'#$40'beta'#$3F alpha@beta?
#94#94#94 ^^^
#94#94#94'ABC' ^^^ABC
'ABC'#94#94#94 ABC^^^
'ABC'#94#94#94'abc' ABC^^^abc
'ABC'#94#94#94'abc'#63'!' ABC^^^abc?!
'test'#32'again' test again
'test'#32#32#$20'again' test again
''
'''' '
'''''''' '''
'a' a
#65 A
#65#$21 A!
'''test'''#65 'test'A