Scripting in AL - attempt #2.
Business Central extension adding a code editor for running scripts.
Now with code highlighting and IntelliSense!
IntelliSense is implemented naively - the main unresolved issues are:
- no autocomplete if the code preceding the current position is broken
- field name suggestions do not work in record methods like
SetRange
- for now you can use the
Record.SetRange(Record.FieldName, ...)
syntax
- for now you can use the
- no suggestions for function arguments
Currently? Not much. Code is parsed, semantically analyzed and executed. Message with the memory state is displayed at the end of each procedure execution.
- Support copy-and-pasting existing AL code and running it without any changes
- Add new features to make life easier
!TODO describe current implementation limitations
The entry point is a parameterless trigger
called OnRun
. You can define additional procedures with var
parameters and (named) return values.
Global variables are not supported (yet?).
procedure WriteLineMultiplied(text: text; count: integer)
begin
WriteLine(text * count);
end;
trigger OnRun()
var
hello: boolean;
text: text;
begin
text := 'h' + 'e' + 'l' * 2 + 'o';
hello := 'hello' = text;
if hello then
text += ' ' + 'world!';
WriteLine(text);
WriteLineMultiplied(text + ' ', 3);
end;
type | status | remark |
---|---|---|
boolean |
✅ | |
text |
✅ | |
code |
✅ | |
guid |
✅ | |
enum |
⭕️ | Planned (but maybe not possible outside of record fields?). |
option |
⭕️ | Planned. |
integer |
✅ | |
decimal |
✅ | |
char |
✅ | |
byte |
⭕️ | |
date |
✅ | |
time |
✅ | |
datetime |
✅ | |
dateformula |
✅ | |
duration |
⭕️ | |
record |
✅ ⭕️ | Temporary records are not supported - yet. |
recordid |
⭕️ | |
recordref & fieldref & keyref |
❓ | Should be possible to implement - at least partially. |
blob & outstream & instream |
⭕️ | |
textbuilder |
⭕️ | |
variant |
❓ | Should be possible to implement - at least partially. |
dialog |
❓ | Low priority. |
dictionary |
❓ | Low priority. |
list |
❓ | Low priority. |
codeunit |
❌ ❓ | Runtime reflection for codeunits is not possible (at least as far as I know). Codenit.Run(Integer) is possible. It could be possible by generating another extension with "bindings"? |
page |
❌ ❓ | Same as codeunits. Page.Run(Integer) is possible. |
report |
❌ ❓ | Same as codeunits. Report.Run(Integer) is possible. |
json* |
❓ | Low priority. |
xml* |
❓ | Low priority. |
http* |
❓ | Low priority. |
others | ❓ | No priority. |
- ✅ - supported
- ⭕️ - planned/partially supported
- ❓ - not supported/not planned
- ❌ - not possible
type | status | remark |
---|---|---|
AddLink(URL: Text, [Description: Text]): Integer |
⭕️ | |
AddLoadFields([Field: Identifier, ...]) |
⭕️ | |
AreFieldsLoaded(Field: Identifier, ...): Boolean |
⭕️ | |
Ascending([Ascending: Boolean]): Boolean |
⭕️ | |
CalcFields(Field: Identifier, [Field: Identifier, ...]) |
⭕️ | |
CalcSums(Field: Identifier, [Field: Identifier, ...]) |
⭕️ | |
ChangeCompany([Company: Text])[: Boolean] |
⭕️ | |
ClearMarks() |
⭕️ | |
Consistent(Consistent: Boolean) |
⭕️ | |
Copy(FromRecord: Record, [ShareTable: Boolean]) |
⭕️ | |
CopyFilter(Any, Any) |
⭕️ | |
CopyFilters(var Record) |
⭕️ | |
CopyLinks(var Record) |
⭕️ | |
CopyLinks(RecordRef) |
⭕️ | |
Count(): Integer |
✅ | |
CountApprox(): Integer |
⭕️ | |
CurrentCompany(): Text |
⭕️ | |
CurrentKey(): Text |
⭕️ | |
Delete([RunTrigger: Boolean])[: Boolean] |
✅ | |
DeleteAll([Boolean]) |
⭕️ | |
DeleteLink(Integer) |
⭕️ | |
DeleteLinks() |
⭕️ | |
FieldActive(Any) |
⭕️ | |
FieldCaption(Any) |
⭕️ | |
FieldError(Any [, Text]) |
⭕️ | |
FieldError(Any, ErrorInfo) |
⭕️ | |
FieldName(Any) |
⭕️ | |
FieldNo(Field: Identifier): Integer |
✅ | |
FilterGroup([Integer]) |
⭕️ | |
Find([Text]) |
⭕️ | |
FindFirst()[: Boolean] |
✅ | |
FindLast()[: Boolean] |
✅ | |
FindSet()[: Boolean] |
✅ | |
FindSet([Boolean]) |
⭕️ | |
FindSet(Boolean, Boolean) |
⭕️ | |
Get([Any,...]) |
⭕️ | |
GetAscending(Any) |
⭕️ | |
GetBySystemId(Guid) |
⭕️ | |
GetFilter(Any) |
⭕️ | |
GetFilters(): Text |
✅ | |
GetPosition([Boolean]) |
⭕️ | |
GetRangeMax(Any) |
⭕️ | |
GetRangeMin(Any) |
⭕️ | |
GetView([UseNames: Boolean]): Text |
✅ | |
HasFilter() |
⭕️ | |
HasLinks() |
⭕️ | |
Init() |
✅ | |
Insert([RunTrigger: Boolean])[: Boolean] |
✅ | |
Insert(Boolean, Boolean) |
⭕️ | |
IsEmpty(): Boolean |
✅ | |
IsTemporary() |
⭕️ | |
LoadFields(Any,...) |
⭕️ | |
LockTable([Boolean] [, Boolean]) |
⭕️ | |
Mark([Boolean]) |
⭕️ | |
MarkedOnly([Boolean]) |
⭕️ | |
Modify([RunTrigger: Boolean])[: Boolean] |
✅ | |
ModifyAll(Any, Any [, Boolean]) |
⭕️ | |
Next([Steps: Integer]): Integer |
✅ | |
ReadConsistency() |
⭕️ | |
ReadIsolation([IsolationLevel]) |
⭕️ | |
ReadPermission() |
⭕️ | |
RecordId() |
⭕️ | |
RecordLevelLocking() |
⭕️ | |
Relation(Any) |
⭕️ | |
Rename(Any [, Any,...]) |
⭕️ | |
Reset() |
✅ | |
SecurityFiltering([SecurityFilter]) |
⭕️ | |
SetAscending(Any, Boolean) |
⭕️ | |
SetAutoCalcFields([Any,...]) |
⭕️ | |
SetCurrentKey(Any [, Any,...]) |
⭕️ | |
SetFilter(Field: Identifier, Filter: Tex, [Substitution: Any, ...]) |
✅ | only up to 10 substitutions |
SetLoadFields([Any,...]) |
⭕️ | |
SetPermissionFilter() |
⭕️ | |
SetPosition(Text) |
⭕️ | |
SetRange(Field: Identifier, [FromValue: FieldType, [ToValue: FieldType]]) |
✅ | |
SetRecFilter() |
✅ | |
SetView(View: Text) |
✅ | |
TableCaption(): Text |
✅ | |
TableName(): Text |
✅ | |
TestField(Any) |
⭕️ | |
TestField(Any, ErrorInfo) |
⭕️ | |
TransferFields(var Record [, Boolean]) |
⭕️ | |
TransferFields(var Record, Boolean, Boolean) |
⭕️ | |
Validate(Field: Identifier, [FromValue: FieldType]) |
✅ | |
WritePermission() |
⭕️ |
type | status | remark |
---|---|---|
ConvertStr(Text, Text, Text) |
✅ | |
CopyStr(Text, Integer [, Integer]) |
✅ | |
DelChr(Text [, Text] [, Text]) |
✅ | |
DelStr(Text, Integer [, Integer]) |
✅ | |
IncStr(Text) |
✅ | |
InsStr(Text, Text, Integer) |
✅ | |
LowerCase(Text) |
✅ | |
MaxStrLen(Text) |
✅ | |
PadStr(Text, Integer [, Text]) |
✅ | |
SelectStr(Integer, Text) |
✅ | |
StrCheckSum(Text [, Text] [, Integer]) |
✅ | |
StrLen(Text) |
✅ | |
StrPos(Text, Text) |
✅ | |
StrSubstNo(Text [, Any,...]) |
✅ | only up to 10 substitutions |
UpperCase(Text) |
✅ | |
Contains(Text: Text): Boolean |
✅ | |
EndsWith(Text) |
✅ | |
IndexOf(Text [, Integer]) |
✅ | |
IndexOfAny(Text [, Integer]) |
✅ | |
IndexOfAny(List of [Char] [, Integer]) |
⭕️ | |
LastIndexOf(Text [, Integer]) |
✅ | |
PadLeft(Integer [, Char]) |
✅ | |
PadRight(Integer [, Char]) |
✅ | |
Remove(Integer [, Integer]) |
✅ | |
Replace(Text, Text) |
✅ | |
Split([Text,...]) |
⭕️ | |
Split(List of [Text]) |
⭕️ | |
Split(List of [Char]) |
⭕️ | |
StartsWith(Text) |
✅ | |
Substring(Integer [, Integer]) |
✅ | |
ToLower(): Text |
✅ | |
ToUpper(): Text |
✅ | |
Trim() |
✅ | |
TrimEnd([Text]) |
✅ | |
TrimStart([Text]) |
✅ |
- assignment (
:=
,+=
,-=
,*=
,/=
) while
loopfor
loop (bothto
anddownto
)repeat-until
loopif
andif-else
statementexit
statement - both variants, with and without an expression
!TODO list unsupported statements - case
, foreach
, break
, ?
- unary operators
- numeric (
+
,-
) - boolean (
not
)
- numeric (
- binary operators
- comparison (
<
,<=
,<>
,>=
,>
,=
) - numeric (
+
,-
,*
,/
,mod
,div
) - boolean (
and
,or
,xor
) - text (
+
,*
)- !TODO explain
*
operator
- !TODO explain
- date (
+
,-
) - time (
+
,-
) - datetime (
+
,-
)
- comparison (
Message(Text: Text, [Substitution: Any, ...])
(up to 10 substitutions)Error(Text: Text, [Substitution: Any, ...])
(up to 10 substitutions)WriteLine(Text: Text, [Substitution: Any, ...])
(up to 10 substitutions)Abs(Number: Decimal): Decimal
Power(Number: Decimal, Power: Decimal): Decimal
Format(Input: Any, [Length: Integer, [FormatNumber: Integer]]): Text
Format(Input: Any, [Length: Integer, [FormatString: Text]]): Text
CalcDate(Formula: Text, [Date: Date]): Date
CalcDate(Formula: DateFormula, [Date: Date]): Date
ClosingDate(Date: Date): Date
CreateDateTime(Date: Date, Time: Time): DateTime
CurrentDateTime(): DateTime
NormalDate(Date: Date): Date
Time(): Time
Today(): Date
WorkDate([WorkDate: Date]): Date
Date2DMY(Date: Date, Part: Integer): Integer
Date2DWY(Date: Date, Part: Integer): Integer
DMY2Date(Day: Integer, [Month: Integer, [Year: Integer]]): Date
DWY2Date(WeekDay: Integer, [Week: Integer, [Year: Integer]]): Date
DT2Date(DateTime: DateTime): Date
DT2Time(DateTime: DateTime): Time
CreateGuid(): Guid
IsNullGuid(Guid: Guid): Boolean
Evaluate(var Value: Any, Input: Text, [FormatNumber: Integer])[: Boolean]
!TODO list unsupported and planned
- editor improvements
- better intellisense
- ui - hide editor before symbols are received
- remaining record methods
- blobs + streams + file management
- case, continue
- more built-in functions and methods
option
,enum
, others- allow
Record.SetRange(Record.Field, ...);
- strlen as a method?
React app addin using the Monaco Editor for script input.