This commit is contained in:
Basile Burg 2014-07-01 11:48:52 +02:00
parent 9529b65b4d
commit 24fba590fa
10 changed files with 1088 additions and 163 deletions

View File

@ -128,7 +128,7 @@
<PackageName Value="LCL"/>
</Item5>
</RequiredPackages>
<Units Count="15">
<Units Count="17">
<Unit0>
<Filename Value="coedit.lpr"/>
<IsPartOfProject Value="True"/>
@ -228,6 +228,16 @@
<ResourceBaseClass Value="Form"/>
<UnitName Value="ce_search"/>
</Unit14>
<Unit15>
<Filename Value="..\src\ce_dlang.pas"/>
<IsPartOfProject Value="True"/>
<UnitName Value="ce_dlang"/>
</Unit15>
<Unit16>
<Filename Value="..\src\ce_dlangutils.pas"/>
<IsPartOfProject Value="True"/>
<UnitName Value="ce_dlangutils"/>
</Unit16>
</Units>
</ProjectOptions>
<CompilerOptions>

View File

@ -9,7 +9,7 @@ uses
Interfaces, Forms, lazcontrols, runtimetypeinfocontrols, ce_widget,
ce_dmdwrap, ce_common, ce_synmemo, ce_main, ce_messages, ce_editor,
ce_projinspect, ce_projconf, jsonparser, ce_project,
ce_widgettypes, ce_staticexplorer, ce_search;
ce_widgettypes, ce_staticexplorer, ce_search, ce_dlang, ce_dlangutils;
{$R *.res}

View File

@ -6,7 +6,8 @@ interface
uses
Classes, SysUtils, Graphics,
SynEditHighlighter, SynEditHighlighterFoldBase, SynEditTypes;
SynEditHighlighter, SynEditHighlighterFoldBase, SynEditTypes,
ce_dlangutils;
const
@ -152,95 +153,6 @@ type
implementation
function isWhite(const c: Char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
begin
result := c in [#0..#32];
end;
function isSpace(const c: Char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
begin
result := c in [#9,' '];
end;
function isAlpha(const c: Char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
begin
result := (c in ['a'..'z']) or (c in ['A'..'Z']);
end;
function isNumber(const c: Char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
begin
result := (c in ['0'..'9']);
end;
function isDigit(const c: Char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
begin
result := (c in ['0'..'1']);
end;
function isAlNum(const c: Char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
begin
result := isAlpha(c) or isNumber(c);
end;
function isHex(const c: Char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
begin
result := isNumber(c) or (c in ['A'..'F']) or (c in ['a'..'f']);
end;
function isSymbol(const c: char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
begin
result := c in [';', '{', '}', '(', ')', '[', ']', ',', '.', ':' , '"', #39, '?'];
end;
function isOperator(const c: char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
begin
result := c in ['/', '*', '-', '+', '%', '>', '<', '=', '!',
'&', '|', '^', '~', '$'];
end;
function isDoubleOperator(const s: string): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
begin
result := false;
case s[1] of
'.': result := (s[2] = '.');
'>': result := s[2] in ['>', '='];
'<': result := s[2] in ['<', '=', '>'];
'=': result := s[2] in ['=', '>'];
'!': result := s[2] in ['=', '>', '<'];
'+': result := s[2] in ['+', '='];
'-': result := s[2] in ['-', '='];
'/': result := s[2] in ['='];
'*': result := s[2] in ['='];
'%': result := s[2] in ['='];
'~': result := s[2] in ['='];
'&': result := s[2] in ['&', '='];
'|': result := s[2] in ['|', '='];
'^': result := s[2] in ['^', '='];
end;
end;
function isTripleOperator(const s: string): boolean; {$IFNDEF DEBUG} inline; {$ENDIF}
begin
result := false;
case s[1] of
'.': result := (s[2] = '.') and (s[3] = '.');
'^': result := (s[2] = '^') and (s[3] = '=');
'>': result := (s[2] = '>') and (s[3] in ['>', '=']);
'<': result := ((s[2] = '<') and (s[3] in ['<', '=']))
or (s[2] = '>') and (s[3] = '=');
'!': result := ((s[2] = '<') and (s[3] in ['>', '=']))
or ((s[2] = '>')and (s[3] = '='));
end;
end;
function isQuadOperator(const s: string): boolean; {$IFNDEF DEBUG} inline; {$ENDIF}
begin
result := (s = '>>>=') or (s = '!<>=');
end;
{$IFDEF USE_DICT_LINKEDCHARMAP}
constructor TD2Dictionary.create;
var
@ -526,6 +438,7 @@ TODO:
- string literals: escape bug: std.path/std.regex: "\\"
- comments: correct nested comments handling.
}
{$BOOLEVAL ON}
procedure TSynD2Syn.next;
label
_postString1;
@ -670,7 +583,7 @@ begin
fRange := rkNone;
readNext;
// check postfix
if readCurr in ['c','w','d'] then
if isStringPostfix(readCurr) then
readNext;
exit;
end;
@ -679,7 +592,7 @@ begin
begin
if fRange = rkNone then
begin
// check hex/WYSIWYG prefix
// check WYSIWYG/hex prefix
if readCurr in ['r','x'] then
begin
if not (readNext = '"') then
@ -695,7 +608,7 @@ begin
begin
readNext;
// check postfix
if readCurr in ['c','w','d'] then
if isStringPostfix(readCurr) then
readNext;
end;
fTokKind := tkStrng;
@ -720,7 +633,7 @@ begin
fRange := rkNone;
readNext;
// check postfix
if readCurr in ['c','w','d'] then
if isStringPostfix(readCurr) then
readNext;
exit;
end;
@ -736,7 +649,7 @@ begin
begin
readNext;
// check postfix
if readCurr in ['c','w','d'] then
if isStringPostfix(readCurr) then
readNext;
end;
fTokKind := tkStrng;
@ -778,30 +691,30 @@ begin
end;
// symbols 2: operators
if isOperator(readCurr) then
if isOperator1(readCurr) then
begin
fTokKind := tkSymbl;
while isOperator(readNext) do (*!*);
while isOperator1(readNext) do (*!*);
case fTokStop - fTokStart of
1:begin
if not isOperator(readCurr) then exit
if not isOperator1(readCurr) then exit
else Dec(fTokStop);
end;
2:begin
if (not isOperator(readCurr)) and
isDoubleOperator(fLineBuf[fTokStart..fTokStop-1])
if (not isOperator1(readCurr)) and
isOperator2(fLineBuf[fTokStart..fTokStop-1])
then exit
else Dec(fTokStop, 2);
end;
3:begin
if (not isOperator(readCurr)) and
isTripleOperator(fLineBuf[fTokStart..fTokStop-1])
if (not isOperator1(readCurr)) and
isOperator3(fLineBuf[fTokStart..fTokStop-1])
then exit
else Dec(fTokStop, 3);
end;
4:begin
if (not isOperator(readCurr)) and
isQuadOperator(fLineBuf[fTokStart..fTokStop-1])
if (not isOperator1(readCurr)) and
isOperator4(fLineBuf[fTokStart..fTokStop-1])
then exit
else Dec(fTokStop, 4);
end;
@ -817,7 +730,7 @@ begin
begin
if isWhite(readNext) then break;
if isSymbol(readCurr) then break;
if isOperator(readCurr) then break;
if isOperator1(readCurr) then break;
end;
if fKeyWords.find(fLineBuf[FTokStart..fTokStop-1]) then
fTokKind := tkKeywd
@ -832,6 +745,7 @@ begin
// Should not happend
assert(false);
end;
{$BOOLEVAL OFF}
function TSynD2Syn.GetEol: Boolean;
begin

831
src/ce_dlang.pas Normal file
View File

@ -0,0 +1,831 @@
unit ce_dlang;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, ce_dlangutils;
const
D2Kw: array[0..109] of string =
( 'abstract', 'alias', 'align', 'asm', 'assert', 'auto',
'body', 'bool', 'break', 'byte',
'case', 'cast', 'catch', 'cdouble', 'cent', 'cfloat', 'char', 'class',
'const', 'continue', 'creal',
'dchar', 'debug', 'default', 'delegate', 'delete', 'deprecated', 'do', 'double',
'else', 'enum', 'export', 'extern',
'false', 'final', 'finally', 'float', 'for', 'foreach',
'foreach_reverse', 'function',
'goto',
'idouble', 'if', 'ifloat', 'immutable', 'import', 'in', 'inout', 'int',
'interface', 'invariant', 'ireal', 'is',
'lazy', 'long',
'macro', 'mixin', 'module',
'new', 'nothrow', 'null',
'out', 'override',
'package', 'pragma', 'private', 'protected', 'ptrdiff_t', 'public', 'pure',
'real', 'ref', 'return',
'size_t', 'scope', 'shared', 'short', 'static', 'string', 'struct',
'super', 'switch', 'synchronized',
'template', 'this', 'throw', 'true', 'try', 'typedef', 'typeid', 'typeof',
'ubyte', 'ucent', 'uint', 'ulong', 'union', 'unittest', 'ushort',
'version', 'void', 'volatile',
'wchar', 'while', 'with',
'__FILE__', '__MODULE__', '__LINE__', '__FUNCTION__', '__PRETTY_FUNCTION__'
);
type
(**
* sector for an array of Keyword with a common hash.
*)
TD2DictionaryEntry = record
filled: Boolean;
values: array of string;
end;
(**
* Dictionary for the D2 keywords.
*)
TD2Dictionary = object
private
fLongest, fShortest: NativeInt;
fEntries: array[Byte] of TD2DictionaryEntry;
function toHash(const aValue: string): Byte; {$IFNDEF DEBUG}inline;{$ENDIF}
procedure addEntry(const aValue: string);
public
constructor create;
destructor destroy; // do not remove even if empty (compat with char-map version)
function find(const aValue: string): boolean;
end;
(**
* Represents the pointer in a source file.
* Automatically updates the line and the column.
*)
TReaderHead = object
private
fLineIndex: Integer;
fColumnIndex: Integer;
fAbsoluteIndex: Integer;
fReaderHead: PChar;
function getColAndLine: TPoint;
public
constructor create(const aText: PChar; const aColAndLine: TPoint);
procedure setReader(const aText: PChar; const aColAndLine: TPoint);
//
function next: PChar;
function previous: PChar;
//
property AbsoluteIndex: Integer read fAbsoluteIndex;
property LineIndex: Integer read fLineIndex;
property ColumnIndex: Integer read fColumnIndex;
property LineAnColumn: TPoint read getColAndLine;
//
property head: PChar read fReaderHead;
end;
TLexTokenKind = (ltkIllegal, ltkChar, ltkComment, ltkIdentifier, ltkKeyword,
ltkNumber, ltkOperator, ltkString, ltkSymbol);
const
LexTokenKindString : array[TLexTokenKind] of string =
( 'Illegal', 'Character', 'Comment', 'Identifier', 'Keyword',
'Number', 'Operator', 'String', 'Symbol');
type
(*****************************************************************************
* Lexer token
*)
PLexToken = ^TLexToken;
TLexToken = record
position: TPoint;
kind: TLexTokenKind;
data: string;
end;
(*****************************************************************************
* List of lexer tokens
*)
TLexTokenList = class(TList)
private
function getToken(index: integer): TLexToken;
public
procedure clear; override;
procedure addToken(aValue: PLexToken);
property token[index: integer]: TLexToken read getToken;
end;
TLexTokenEnumerator = class
fList: TLexTokenList;
fIndex: Integer;
function GetCurrent: TLexToken;
function MoveNext: Boolean;
property Current: TLexToken read GetCurrent;
end;
(*****************************************************************************
* Error record
*)
PLexError = ^TLexError;
TLexError = record
position: TPoint;
msg: string;
end;
(*****************************************************************************
* Error list
*)
TLexErrorList = class(TList)
private
function getError(index: integer): TLexError;
public
procedure clear; override;
procedure addError(aValue: PLexError);
property error[index: integer]: TLexError read getError;
end;
TLexErrorEnumerator = class
fList: TLexErrorList;
fIndex: Integer;
function GetCurrent: TLexError;
function MoveNext: Boolean;
property Current: TLexError read GetCurrent;
end;
operator enumerator(aTokenList: TLexTokenList): TLexTokenEnumerator;
operator enumerator(anErrorList: TLexErrorList): TLexErrorEnumerator;
(*****************************************************************************
* Lexes aText and fills aList with the TLexToken found.
*)
procedure lex(const aText: string; const aList: TLexTokenList);
(*****************************************************************************
* Detects various syntaxic error in a TLexTokenList
*)
procedure checkSyntaxicErrors(const aTokenList: TLexTokenList; const anErrorList: TLexErrorList);
(*****************************************************************************
* Compares two TPoints.
*)
operator = (lhs: TPoint; rhs: TPoint): boolean;
implementation
var
D2Dictionary: TD2Dictionary;
{$REGION TReaderHead------------------------------------------------------------}
operator = (lhs: TPoint; rhs: TPoint): boolean;
begin
exit( (lhs.y = rhs.y) and (lhs.x = rhs.x) );
end;
constructor TReaderHead.create(const aText: PChar; const aColAndLine: TPoint);
begin
setReader(aText,aColAndLine);
end;
procedure TReaderHead.setReader(const aText: PChar; const aColAndLine: TPoint);
begin
fLineIndex := aColAndLine.y;
fColumnIndex := aColAndLine.x;
fReaderHead := aText;
while (LineAnColumn <> aColAndLine) do next;
//
// editor not 0 based ln index
if fLineIndex = 0 then fLineIndex := 1;
end;
function TReaderHead.getColAndLine: TPoint;
begin
exit( Point(fColumnIndex, fLineIndex) );
end;
function TReaderHead.next: PChar;
begin
Inc(fReaderHead);
Inc(fAbsoluteIndex);
Inc(fColumnIndex);
if (fReaderHead^ = #10) then
begin
Inc(fLineIndex);
fColumnIndex := 0;
end;
exit(fReaderHead);
end;
function TReaderHead.previous: PChar;
begin
// note: it breaks the column but not the line count
Dec(fReaderHead);
Dec(fColumnIndex);
Dec(fAbsoluteIndex);
exit(fReaderHead);
end;
{$ENDREGION}
{$REGION TD2Dictionary----------------------------------------------------------}
constructor TD2Dictionary.create;
var
value: string;
begin
for value in D2Kw do
addEntry(value);
end;
destructor TD2Dictionary.destroy;
begin
end;
{$IFDEF DEBUG}{$R-}{$ENDIF}
function TD2Dictionary.toHash(const aValue: string): Byte;
var
i: Integer;
begin
result := 0;
for i := 1 to length(aValue) do result +=
(Byte(aValue[i]) shl (4 and (1-i))) xor 25;
end;
{$IFDEF DEBUG}{$R+}{$ENDIF}
procedure TD2Dictionary.addEntry(const aValue: string);
var
hash: Byte;
begin
if find(aValue) then exit;
hash := toHash(aValue);
fEntries[hash].filled := true;
setLength(fEntries[hash].values, length(fEntries[hash].values) + 1);
fEntries[hash].values[high(fEntries[hash].values)] := aValue;
if fLongest <= length(aValue) then
fLongest := length(aValue);
if fShortest >= length(aValue) then
fShortest := length(aValue);
end;
function TD2Dictionary.find(const aValue: string): boolean;
var
hash: Byte;
i: NativeInt;
begin
result := false;
if length(aValue) > fLongest then exit;
if length(aValue) < fShortest then exit;
hash := toHash(aValue);
if (not fEntries[hash].filled) then exit(false);
for i:= 0 to high(fEntries[hash].values) do
if fEntries[hash].values[i] = aValue then exit(true);
end;
{$ENDREGION}
{$REGION Lexing-----------------------------------------------------------------}
function TLexTokenList.getToken(index: integer): TLexToken;
begin
result := PLexToken(Items[index])^;
end;
procedure TLexTokenList.clear;
begin
while Count > 0 do
begin
Dispose( PLexToken(Items[Count-1]) );
Delete(Count-1);
end;
end;
procedure TLexTokenList.addToken(aValue: PLexToken);
begin
add(Pointer(aValue));
end;
function TLexTokenEnumerator.GetCurrent: TLexToken;
begin
exit(fList.token[fIndex]);
end;
function TLexTokenEnumerator.MoveNext: Boolean;
begin
Inc(fIndex);
exit(fIndex < fList.Count);
end;
operator enumerator(aTokenList: TLexTokenList): TLexTokenEnumerator;
begin
result := TLexTokenEnumerator.Create;
result.fList := aTokenList;
result.fIndex := -1;
end;
{$BOOLEVAL ON}
procedure lex(const aText: string; const aList: TLexTokenList);
var
reader: TReaderHead;
identifier: string;
function isOutOfBound: boolean;
begin
exit(reader.AbsoluteIndex > length(aText))
end;
procedure addToken(aTk: TLexTokenKind);
var
ptk: PLexToken;
begin
ptk := new(PLexToken);
ptk^.kind := aTk;
ptk^.position := reader.LineAnColumn;
ptk^.data := identifier;
aList.Add(ptk);
end;
begin
reader.create(@aText[1], Point(0,0));
while (true) do
begin
if isOutOfBound then
exit;
identifier := '';
// skip blanks
while isWhite(reader.head^) do
begin
reader.next;
if isOutOfBound then exit;
end;
// line comment
if (reader.head^ = '/') then
begin
if (reader.next^ = '/') then
begin
if isOutOfBound then exit;
while (reader.head^ <> #10) do
begin
reader.next;
identifier += reader.head^;
if isOutOfBound then exit;
end;
reader.next;
addToken(ltkComment);
continue;
end
else
reader.previous;
end;
// block comments 1
if (reader.head^ = '/') then
begin
if (reader.next^ = '*') then
begin
if isOutOfBound then exit;
while (reader.head^ <> '*') or (reader.next^ <> '/') do
if isOutOfBound then exit;
reader.next;
addToken(ltkComment);
continue;
end
else
reader.previous;
end;
// block comments 2
if (reader.head^ = '/') then
begin
if (reader.next^ = '+') then
begin
if isOutOfBound then exit;
while (reader.head^ <> '+') or (reader.next^ <> '/') do
if isOutOfBound then exit;
reader.next;
addToken(ltkComment);
continue;
end
else
reader.previous;
end;
// string 1, note: same escape error as in SynD2Syn
if (reader.head^ in ['r', 'x']) then
begin
if not (reader.next^ = '"') then
reader.previous;
end;
if (reader.head^ = '"') then
begin
reader.next;
if isOutOfBound then exit;
if (reader.head^ = '"') then
begin
reader.next;
addToken(ltkString);
continue;
end;
while (true) do
begin
if reader.head^ = '\' then
begin
reader.next;
if (reader.head^ = '"') then
begin
reader.next;
continue;
end;
end;
if (reader.head^ = '"') then
break;
identifier += reader.head^;
reader.next;
if isOutOfBound then exit;
end;
if isStringPostfix(reader.next^) then
reader.next;
addToken(ltkString);
continue;
end;
// string 2
if (reader.head^ = '`') then
begin
reader.next;
if isOutOfBound then exit;
while (reader.head^ <> '`') do
begin
identifier += reader.head^;
reader.next;
if isOutOfBound then exit;
end;
if isStringPostfix(reader.next^) then
reader.next;
if isOutOfBound then exit;
addToken(ltkString);
continue;
end;
//chars, note: same escape error as in SynD2Syn
if (reader.head^ = #39) then
begin
reader.next;
if isOutOfBound then exit;
if (reader.head^ = #39) then
begin
reader.next;
addToken(ltkString);
continue;
end;
while (true) do
begin
if reader.head^ = '\' then
begin
reader.next;
if (reader.head^ = #39) then
begin
reader.next;
continue;
end;
end;
if (reader.head^ = #39) then
break;
identifier += reader.head^;
reader.next;
if isOutOfBound then exit;
end;
reader.next;
addToken(ltkChar);
continue;
end;
// check negative float '-0.'
if (reader.head^ = '-') then
begin
identifier += reader.head^;
if reader.next^ = '0' then
begin
if reader.next^ = '.' then
reader.previous // back to 0, get into "binary/hex numbr/float"
else
begin
reader.previous;
reader.previous; // back to -
identifier := '';
end;
end
else
begin
reader.previous; // back to -
identifier := '';
end;
end;
// + suffixes
// + exponent
// float .xxxx
// binary/hex numbr/float
if (reader.head^ = '0') then
begin
identifier += reader.head^;
if (reader.next^ in ['b','B']) then
begin
identifier += reader.head^;
while isBit(reader.next^) or (reader.head^ = '_') do
begin
if isOutOfBound then exit;
identifier += reader.head^;
end;
addToken(ltkNumber);
continue;
end
else reader.previous;
if (reader.next^ in ['x','X']) then
begin
identifier += reader.head^;
while isHex(reader.next^) or (reader.head^ = '_') do
begin
if isOutOfBound then exit;
identifier += reader.head^;
end;
addToken(ltkNumber);
continue;
end
else reader.previous;
if (reader.next^ = '.') then
begin
identifier += reader.head^;
while isNumber(reader.next^) do
begin
if isOutOfBound then exit;
identifier += reader.head^;
end;
addToken(ltkNumber);
continue;
end
else reader.previous;
identifier := '';
end;
// check negative float/int '-xxx'
if (reader.head^ = '-') then
begin
identifier += reader.head^;
if not isNumber(reader.next^) then
begin
reader.previous; // back to '-'
identifier := '';
end;
end;
// numbers
if isNumber(reader.head^) then
begin
identifier += reader.head^;
while isNumber(reader.next^) or (reader.head^ = '_') do
begin
if isOutOfBound then exit;
identifier += reader.head^;
end;
addToken(ltkNumber);
continue;
end;
// symbols
if isSymbol(reader.head^) then
begin
identifier += reader.head^;
reader.next;
addToken(ltkSymbol);
if isOutOfBound then exit;
continue;
end;
// operators
if isOperator1(reader.head^) then
begin
identifier += reader.head^;
while isOperator1(reader.next^) do
begin
if isOutOfBound then exit;
identifier += reader.head^;
end;
case length(identifier) of
4:begin
if (not isOperator1(reader.head^)) and
isOperator4(identifier) then
begin
addToken(ltkOperator);
continue;
end;
end;
3:begin
if (not isOperator1(reader.head^)) and
isOperator3(identifier) then
begin
addToken(ltkOperator);
continue;
end;
end;
2:begin
if (not isOperator1(reader.head^)) and
isOperator2(identifier) then
begin
addToken(ltkOperator);
continue;
end;
end;
1:begin
if not isOperator1(reader.head^)
then
begin
addToken(ltkOperator);
continue;
end;
end;
end;
end;
// identifier accum
if isFirstIdentifier(reader.head^) then
begin
while isIdentifier(reader.head^) do
begin
identifier += reader.head^;
reader.next;
if isOutOfBound then exit;
end;
if D2Dictionary.find(identifier) then
addToken(ltkKeyword)
else
addToken(ltkIdentifier);
continue;
end;
// error
identifier += ' (unrecognized lexer input)';
addToken(ltkIllegal);
end;
end;
{$BOOLEVAL OFF}
{$ENDREGION}
{$REGION Syntaxic errors}
function TLexErrorList.getError(index: integer): TLexError;
begin
result := PLexError(Items[index])^;
end;
procedure TLexErrorList.clear;
begin
while Count > 0 do
begin
Dispose( PLexError(Items[Count-1]) );
Delete(Count-1);
end;
end;
procedure TLexErrorList.addError(aValue: PLexError);
begin
add(Pointer(aValue));
end;
function TLexErrorEnumerator.GetCurrent: TLexError;
begin
exit(fList.error[fIndex]);
end;
function TLexErrorEnumerator.MoveNext: Boolean;
begin
Inc(fIndex);
exit(fIndex < fList.Count);
end;
operator enumerator(anErrorList: TLexErrorList): TLexErrorEnumerator;
begin
result := TLexErrorEnumerator.Create;
result.fList := anErrorList;
result.fIndex := -1;
end;
procedure checkSyntaxicErrors(const aTokenList: TLexTokenList; const anErrorList: TLexErrorList);
const
errPrefix = 'syntaxic error: ';
var
tk, old1, old2: TLexToken;
err: PLexError;
tkIndex: NativeInt;
pareCnt, curlCnt, squaCnt: NativeInt;
procedure addError(const aMsg: string);
begin
err := new(PLexError);
err^.msg := errPrefix + aMsg;
err^.position := aTokenList.token[tkIndex].position;
anErrorList.addError(err);
end;
label
_preSeq;
begin
tkIndex := -1;
pareCnt := 0;
curlCnt := 0;
squaCnt := 0;
for tk in aTokenList do
begin
// token index
Inc(tkIndex);
// brackets count
if tk.kind = ltkSymbol then
begin
case tk.data of
'(': Inc(pareCnt);
'{': Inc(curlCnt);
'[': Inc(squaCnt);
')': Dec(pareCnt);
'}': Dec(curlCnt);
']': Dec(squaCnt);
end;
// only for the first occurence
if pareCnt = -1 then
addError('a left parenthesis is missing');
if curlCnt = -1 then
addError('a left curly bracket is missing');
if squaCnt = -1 then
addError('a left square bracket is missing');
// at the end
if (tkIndex = aTokenList.Count-1) then
begin
if pareCnt > 0 then
addError('a right parenthesis is missing');
if curlCnt > 0 then
addError('a right curly bracket is missing');
if squaCnt > 0 then
addError('a right square bracket is missing');
end;
goto _preSeq;
end;
// lexer invalid token
if tk.kind = ltkIllegal then
begin
addError(tk.data);
goto _preSeq;
end;
// invalid numbers
if tk.kind = ltkNumber then
begin
goto _preSeq;
end;
_preSeq:
// invalid sequences
if tkIndex > 0 then // can use old1
begin
if (old1.kind = ltkKeyword) and (tk.kind = ltkKeyword) then
if old1.data = tk.data then
addError('keyword is duplicated');
// needs negative numbr to be tokenized correctly: ... = -1; '-' is currently token as an operator.
if (old1.kind = ltkOperator) and (tk.kind = ltkOperator) then
addError('operator rhs cannot be an operator');
if (old1.kind = ltkNumber) and (tk.kind = ltkNumber) then
addError('symbol or operator expected after number');
end;
if tkIndex > 1 then // can use old2
begin
end;
old1 := tk;
old2 := old1;
end;
end;
{$ENDREGION}
initialization
D2Dictionary.create;
finalization
D2Dictionary.destroy;
end.

135
src/ce_dlangutils.pas Normal file
View File

@ -0,0 +1,135 @@
unit ce_dlangutils;
{$mode objfpc}{$H+}
interface
uses
SysUtils;
function isWhite(const c: Char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
function isSpace(const c: Char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
function isAlpha(const c: Char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
function isNumber(const c: Char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
function isBit(const c: Char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
function isAlNum(const c: Char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
function isHex(const c: Char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
function isSymbol(const c: char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
function isOperator1(const c: char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
function isOperator2(const s: string): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
function isOperator3(const s: string): boolean; {$IFNDEF DEBUG} inline; {$ENDIF}
function isOperator4(const s: string): boolean; {$IFNDEF DEBUG} inline; {$ENDIF}
function isStringPostfix(const c: char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
function isIdentifier(const c: char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
function isFirstIdentifier(const c: char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
implementation
{$BOOLEVAL ON}
function isWhite(const c: Char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
begin
exit(c in [#0..#32]);
end;
function isSpace(const c: Char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
begin
exit(c in [#9,' ']);
end;
function isAlpha(const c: Char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
begin
exit((c in ['a'..'z']) or (c in ['A'..'Z']));
end;
function isNumber(const c: Char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
begin
exit(c in ['0'..'9']);
end;
function isBit(const c: Char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
begin
exit((c in ['0'..'1']));
end;
function isAlNum(const c: Char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
begin
exit(isAlpha(c) or isNumber(c));
end;
function isHex(const c: Char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
begin
exit(isNumber(c) or (c in ['A'..'F']) or (c in ['a'..'f']));
end;
function isSymbol(const c: char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
begin
exit(c in [';', '{', '}', '(', ')', '[', ']', ',', '.', ':' , '"', #39, '?', '$']);
end;
function isOperator1(const c: char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
begin
exit(c in ['/', '*', '-', '+', '%', '>', '<', '=', '!',
'&', '|', '^', '~']);
end;
function isOperator2(const s: string): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
begin
result := false;
case s[1] of
'.': result := (s[2] = '.');
'>': result := s[2] in ['>', '='];
'<': result := s[2] in ['<', '=', '>'];
'=': result := s[2] in ['=', '>'];
'!': result := s[2] in ['=', '>', '<'];
'+': result := s[2] in ['+', '='];
'-': result := s[2] in ['-', '='];
'/': result := s[2] in ['='];
'*': result := s[2] in ['='];
'%': result := s[2] in ['='];
'~': result := s[2] in ['='];
'&': result := s[2] in ['&', '='];
'|': result := s[2] in ['|', '='];
'^': result := s[2] in ['^', '='];
end;
end;
function isOperator3(const s: string): boolean; {$IFNDEF DEBUG} inline; {$ENDIF}
begin
result := false;
case s[1] of
'.': result := (s[2] = '.') and (s[3] = '.');
'^': result := (s[2] = '^') and (s[3] = '=');
'>': result := (s[2] = '>') and (s[3] in ['>', '=']);
'<': result := ((s[2] = '<') and (s[3] in ['<', '=']))
or (s[2] = '>') and (s[3] = '=');
'!': result := ((s[2] = '<') and (s[3] in ['>', '=']))
or ((s[2] = '>')and (s[3] = '='));
end;
end;
function isOperator4(const s: string): boolean; {$IFNDEF DEBUG} inline; {$ENDIF}
begin
result := (s = '>>>=') or (s = '!<>=');
end;
function isStringPostfix(const c: char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
begin
exit(c in ['c', 'w', 'd']);
end;
function isIdentifier(const c: char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
begin
exit((not isSymbol(c)) and (not isOperator1(c)) and (not isWhite(c)));
end;
function isFirstIdentifier(const c: char): boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
begin
exit(isIdentifier(c) and (not isNumber(c)));
end;
{$BOOLEVAL OFF}
end.

View File

@ -8,7 +8,8 @@ uses
Classes, SysUtils, FileUtil, ExtendedNotebook, Forms, Controls, lcltype,
Graphics, SynEditKeyCmds, ComCtrls, SynEditHighlighter, ExtCtrls, Menus,
SynEditHighlighterFoldBase, SynMacroRecorder, SynPluginSyncroEdit, SynEdit,
SynHighlighterLFM, ce_widget, ce_d2syn, ce_synmemo, ce_common, AnchorDocking;
SynHighlighterLFM, ce_widget, ce_d2syn, ce_synmemo, ce_common, AnchorDocking,
ce_dlang;
type
{ TCEEditorWidget }
@ -26,7 +27,6 @@ type
fKeyChanged: boolean;
fSyncEdit: TSynPluginSyncroEdit;
procedure memoKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure memoKeyPress(Sender: TObject; var Key: Char);
procedure memoMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
procedure memoChange(Sender: TObject);
procedure memoMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
@ -138,7 +138,6 @@ begin
memo.OnMouseDown := @memoMouseDown;
memo.OnChange := @memoChange;
memo.OnMouseMove := @memoMouseMove;
memo.OnKeyPress := @memoKeyPress;
//
pageControl.ActivePage := sheet;
//http://bugs.freepascal.org/view.php?id=26320
@ -158,15 +157,13 @@ end;
procedure TCEEditorWidget.memoKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
end;
procedure TCEEditorWidget.memoKeyPress(Sender: TObject; var Key: Char);
begin
if (sender is TCESynMemo) then
if (sender is TCESynMemo) then
identifierToD2Syn(TCESynMemo(Sender));
fKeyChanged := true;
case Byte(Key) of
VK_UNKNOWN..VK_BACK: exit;
VK_PRIOR..VK_HELP: exit;
VK_UNKNOWN..VK_XBUTTON2: exit;
VK_SHIFT..VK_HELP: fKeyChanged := false;
VK_LWIN..VK_SLEEP: exit;
VK_F1..$91: exit;
end;
fKeyChanged := true;
@ -200,6 +197,9 @@ const
modstr: array[boolean] of string = ('...', 'MODIFIED');
var
ed: TCESynMemo;
tokLst: TLexTokenList;
errLst: TLexErrorList;
err: TLexError;
begin
ed := getCurrentEditor;
if ed <> nil then
@ -210,8 +210,31 @@ begin
end;
//
if fKeyChanged then if editorIndex <> -1 then
begin
mainForm.docChangeNotify(Self, editorIndex);
mainForm.MessageWidget.List.Clear;
tokLst := TLexTokenList.Create;
errLst := TLexErrorList.Create;
try
lex( ed.Lines.Text, tokLst );
checkSyntaxicErrors( tokLst, errLst);
for err in errLst do
mainForm.MessageWidget.addMessage(format(
'%s (@line:%4.d @char:%.4d)',[err.msg, err.position.y, err.position.x]));
mainForm.MessageWidget.scrollToBack;
finally
tokLst.Free;
errLst.Free;
end;
end;
fKeyChanged := false;
end;
end.

View File

@ -2547,39 +2547,39 @@ object CEMainForm: TCEMainForm
0000000000330000003300000033000000332D73BAAF1B3D60523F93D4FF3F93
D4FF102438413578BAC300000024000000000000000000000000000000000000
0000000000000000000000000000000000000000001F00000008000000330000
0033000000040000002400000000B3B3B1EFB0B0ADFFAEAEACFFAEAEABFFAEAE
ABFFAEAEABFFADAEABFFAEAEABFFAFAFADFFB0B0AEEAB3B3B100B5B5B300B5B5
B300B5B5B300B5B5B300B5B5B300AFAFADFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9E9E9FFAEAEACA6B3B3B100B5B5
B300B5B5B300B5B5B300B5B5B300AEAEACFFFFFFFFFFE0DFDEFFE1DFDEFFE1E0
DFFFE1DFDEFFE0DFDEFFFFFFFFFFA5A5A3FFFFFFFFFFE8E8E8FFAFAFACA7B3B3
B100B5B5B300B5B5B300B6B6B400AEAEABFFFFFFFFFFE3E3E2FFE4E4E3FFE4E5
E4FFE4E4E3FFE3E3E2FFFFFFFFFFA7A7A5FFECECEBFFFFFFFFFFEAEAEAFFB1B1
AFACB6B6B400B8B8B600B9B9B700ADAEABFFFFFFFFFFE7E5E4FFE8E7E6FFE8E7
E6FFE8E7E6FFE7E6E5FFFFFFFFFFCDCDCCFFAAAAA8FFADADABFFFFFFFFFFB3B3
B1FFB8B9B7008A8886008C898700ADADABFFFFFFFFFFE9E9E8FFEAEAE9FFEAEA
E9FFEAEAE9FFECECEBFFFAFAF9FFFFFFFFFFFFFFFFFF7B7976FF7E7B79FFB8B8
B6FF979593008D8B89008F8D8B00ADADABFFFFFFFFFFECEBEAFFEDECEBFFEDEC
EBFFEEEDECFFF3F2F1FF7B7977FF8C8987FFCBCAC8FFB2AFADFFB5B2B0FFA3A3
A1FF979593FF8D8B89FF908E8B00ADADABFFFFFFFFFFEFEFEEFFEFEFEEFFEFEF
EEFFF1F1F0FFF7F8F7FF8B8987FFD8D5D5FF9B9996FFD0CECDFFD1CFCEFF9D9B
99FFDCDAD9FF918F8DFF94929000ADADABFFFFFFFFFFF2F1F0FFF2F1F0FFF3F1
F0FFF6F5F4FFFFFEFDFFB6B3B3FF9B9996FFBCBAB9FF92908FFF92908FFFBCBA
B9FF9E9C9AFF6C6B699B908E8C00ADADABFFFFFFFFFFF4F4F3FFF4F4F3FFF5F6
F4FFFCFCFBFF73706EFFABA9A8FFCCCAC9FF918F8DFFE1E1E1FFECECEBFF918F
8CFFCDCBCBFFB1AFAEFF8F8D8BFFADADABFFFFFFFFFFF6F6F6FFF6F6F6FFF7F8
F8FFFEFFFFFF726F6DFFAAA8A7FFCAC8C7FF8E8C8BFFFFFFFFFFFFFFFFFF8E8C
8BFFCBC9C8FFB0AEADFF8E8C8AFFADADABFFFFFFFFFFFAF8F8FFF9F8F8FFFAF9
F9FFFEFDFDFFE1E1E0FFC2C1C0FF959392FFB5B3B2FF8D8B89FF8D8B88FFB5B3
B2FF999796FF646260894847465BAEAEABFFFFFFFFFFFDFDFDFFFCFCFDFFFCFD
FDFFFFFFFFFFFFFFFFFF84817FFFCCCAC9FF949291FFC5C3C2FFC6C4C3FF9694
93FFD0CECDFF8B8988FF0000000AB0B0ADFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFFFFFF787573FF817E7CFFDDDCDDFFA5A3A2FFA7A5A4FFA4A3
A1FF878583FF868482FF88868400A4A4A2C0AFAFADFFAEAEABFFADADABFFADAD
ABFFAEAEACFFB1B1AEFFB6B6B4FFB7B8B6FFB9BAB7FF797776FF7A7877FFADAE
ACB9000000330000003388868400000000000000003300000033000000330000
0033000000330000003300000033000000330000003300000033000000330000
0022000000000000000000000000FFFFFF000000000000000000000000000000
0033000000040000002400000000BB871F00BB871F00BB871E00B9841A00B67E
0FEAC4973BFFC79D49FFC39538FFB37904FFB47A07FFB47A07FFB47A08FFB57C
0AFFB67F0FFFB88114FFBA851B23BB871F00BB871F00BA861D00B7801283E4CF
A7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
FFFFE9DABAFFEEE2C9FFB67F14BFBB871F00BB871F00B9851B00B27A09E5FFFF
FFFFFFFFFFFFFEFEFBFFFEFEFBFFFEFEFCFFFEFEFCFFFEFEFCFFFFFFFEFFFFFF
FFFFC69941FFECDFC2FFC19234FFBB871F00BB871E00B9831800C89E49FFFFFF
FFFFFCF9F3FFFBF8F1FFFBF8F1FFFBF8F1FFFBF8F1FFFCF9F3FFFDFBF6FFFFFF
FFFFB27701FFBA851BFFBC8921FFBB871F00BB861E00B882150CCCA556FFFFFF
FFFFF9F6EBFFF9F5EAFFFAF6ECFFFBF7EDFFFDF9EEFFFFFCF1FFFFFFFCFFFFFF
FFFFAB7405CE0000003300000033BB871F00BB861D00B07B1134DBBF88FFFFFF
FFFFF7F2E5FFFAF4E6FFFEF7E9FFFFF9EAFFFFFCEDFF767574FF777778FFF8E9
CAFFAA7200A8888D99008B8E9700BB871F00BA861D00AD790E73EADBBBFFFAF7
EEFFF6EFDEFFFCF4E2FF7B797AFF8B8A89FFD2CCBFFFB1AFAEFFB1B1B1FFC3B2
8EFF939499FF8C8B8EFF8E8E8E00BB871F00BA851C00AA760C9BF0E4CCFFF6EF
E0FFF4ECD7FFFAF2DCFF8B8A8AFFD7D6D6FF9A9997FFD0CECEFFD0CFCFFF9C9A
9AFFDBDADBFF918F8EFF94929000BB871F00BA851C00B0790BD2FFFFFFFFF1E9
D2FFF4EBD2FFFCF2D7FFB4AFA2FF9B9997FFBCBABAFF939191FF929191FFBCBA
BAFF9E9C9AFF6C6B699B908E8C00BB871E00B9841900B8831BEFFFFFFFFFF2E9
D3FFF7EDD4FF747577FFABA9AAFFCCCACAFF92908FFFDCD2B9FFE9E7E2FF918F
8EFFCECCCBFFB1AFAEFF8F8D8BFFB98419FFB67E0EFFB67F0FFFC0902EFFC08E
29FFC59128FF747578FFAAA8A9FFCAC8C7FF8F8D8EFFFFF2D2FFFFFFF5FF8F8D
8DFFCBC9C9FFB0AEADFF8E8C8AFFB78012FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
FFFFFFFFFFFFE3E3DFFFC2C1BBFF959493FFB5B3B4FF8D8C8CFF8D8C8BFFB6B4
B3FF999796FF646260894847465BAC7A14CADCC189FFF5F0E0FFF4EDDBFFF4ED
DBFFF6EFDBFFFDF4DCFF878789FFCECDCEFF969698FFC6C5C6FFC7C5C5FF9896
95FFD1CFCEFF8B8988FF0000000A60440E44B88114FFB67E0FFFB57D0DFFB57D
0CFFB67E0CFFBC7F06FF7F8188FF86878AFFA87F2DFFA8A8ABFFAAA8A8FF5250
4F698A8886FF868482FF88868400000000070000003300000033000000330000
003300000033000000330000003300000033000000337C7C81FF7E7D7DFF0000
000E000000330000003388868400000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000033000000330000
0000000000000000000000000000FFFFFF000000000000000000000000000000
0000000000000000000000000000A6A4A133A5A39FE5AEACA9FFB9B6B5FFABA9
A5FFA7A5A2D2A8A6A323FFFFFF00FFFFFF000000000000000000000000000000
00000000000000000000A19F9C48AEACAAFFDADAD9FFF0EEEFFFECEBEAFFE2E0

View File

@ -16,7 +16,7 @@ type
TCEMainForm = class;
(**
* Encapsulates the options.
* Encapsulates the options in a writable component.
* note: likely to change however needed to test correctly Coedit.
*)
TCEOptions = class(TComponent)
@ -45,7 +45,7 @@ type
procedure saveToFile(const aFilename: string);
procedure loadFromFile(const aFilename: string);
procedure beforeSave;
procedure afterSave;
procedure afterLoad;
procedure DefineProperties(Filer: TFiler); override;
end;
@ -375,8 +375,8 @@ begin
actEdIndent.Enabled := true;
actEdUnIndent.Enabled := true;
//
actFileCompAndRun.Enabled := true;
actFileCompAndRunWithArgs.Enabled := true;
actFileCompAndRun.Enabled := curr.isDSource;
actFileCompAndRunWithArgs.Enabled := curr.isDSource;
actFileSave.Enabled := true;
actFileSaveAs.Enabled := true;
actFileClose.Enabled := true;
@ -694,7 +694,7 @@ var
begin
if fEditWidg = nil then exit;
if fEditWidg.editorIndex < 0 then exit;
if fEditWidg.editor[fEditWidg.editorIndex].Highlighter = LfmSyn
if fEditWidg.editor[fEditWidg.editorIndex].isProjectSource
then exit;
//
str := fEditWidg.editor[fEditWidg.editorIndex].fileName;
@ -1327,10 +1327,10 @@ begin
except
exit;
end;
afterSave;
afterLoad;
end;
procedure TCEOptions.afterSave;
procedure TCEOptions.afterLoad;
var
widg: TCEWidget;
begin

View File

@ -15,12 +15,17 @@ type
fFilename: string;
fModified: boolean;
fAssocProject: TCEProject;
function getIfDSource: Boolean;
function getIfConfig: Boolean;
public
constructor Create(aOwner: TComponent); override;
//
property fileName: string read fFilename write fFilename;
property modified: boolean read fModified write fModified;
property project: TCEProject read fAssocProject write fAssocProject;
//
property isDSource: boolean read getIfDSource;
property isProjectSource: boolean read getIfConfig;
end;
var
@ -29,7 +34,7 @@ var
implementation
uses
graphics;
graphics, ce_main;
constructor TCESynMemo.Create(aOwner: TComponent);
begin
@ -52,6 +57,16 @@ begin
Highlighter := D2Syn;
end;
function TCESynMemo.getIfDSource: Boolean;
begin
exit(Highlighter = D2Syn);
end;
function TCESynMemo.getIfConfig: Boolean;
begin
exit(Highlighter = mainForm.LfmSyn);
end;
initialization
D2Syn := TSynD2Syn.create(nil);
finalization

View File

@ -100,9 +100,6 @@ type
implementation
{$R *.lfm}
uses
ce_main;
(*******************************************************************************
* TCEWidget
*)
@ -119,8 +116,8 @@ begin
fUpdaterAuto.OnTimer := @updaterAutoProc;
fUpdaterDelay := TTimer.Create(self);
updaterByLoopInterval := 50;
updaterByDelayDuration := 1000;
updaterByLoopInterval := 70;
updaterByDelayDuration := 1250;
DockMaster.MakeDockable(Self, true, true, true);
DockMaster.GetAnchorSite(Self).Header.HeaderPosition := adlhpTop;