Recent

Author Topic: [SOLVED] Is this possible in Free Pascal, Delphi or is it AI hallucination ?  (Read 2123 times)

Gustavo 'Gus' Carreno

  • Hero Member
  • *****
  • Posts: 1353
  • Professional amateur ;-P
Hey Y'All,

Was having a good session about tokenisation, AST building and expression evaluation, and on the Expression Parser unit that ChatGPT suggested I got

Code: Pascal  [Select][+][-]
  1. uses
  2.   SysUtils, Classes, Variants;
  3.  
  4. type
  5.   TExpressionNodeType = (entBinary, entUnary, entLiteral, entVariable);
  6.  
  7.   TBinaryOperator = (boEqual, boNotEqual, boAnd, boOr, boLess, boLessEqual, boGreater, boGreaterEqual);
  8.   TUnaryOperator = (uoNot);
  9.  
  10.   TExpressionNode = class
  11.     NodeType: TExpressionNodeType;
  12.     case TExpressionNodeType of
  13.       entBinary: (Op: TBinaryOperator; Left, Right: TExpressionNode);
  14.       entUnary:  (UnOp: TUnaryOperator; Operand: TExpressionNode);
  15.       entLiteral: (LiteralValue: String);
  16.       entVariable: (VariableName: String);
  17.   end;

This, of course, gives an error because it's not expecting a case right there.

I'm also aware that we can use a case in a record, when I changed that declaration from class to record, it complained that the record was not completely defined, which makes sense because it's referencing itself.

So, my question is as follows: Is there a way to achieve this in Free Pascal, or is this a Delphi-ism, or is this just the AI hallucinating?

Right now I changed it thus:
Code: Pascal  [Select][+][-]
  1. { TExpressionNode }
  2.   TExpressionNode = class
  3.   private
  4.     FNodeType: TExprNodeType;
  5.     FOp: TBinaryOperator;
  6.     FLeft: TExpressionNode;
  7.     FRight: TExpressionNode;
  8.     FUnOp: TUnaryOperator;
  9.     FOperand: TExpressionNode;
  10.     FLiteralValue: String;
  11.     FVariableName: String;
  12.   protected
  13.   public
  14.     property NodeType: TExprNodeType read FNodeType write FNodeType;
  15.     property Op: TBinaryOperator read FOp write FOp;
  16.     property Left: TExpressionNode read FLeft write FLeft;
  17.     property Right: TExpressionNode read FRight write FRight;
  18.     property UnOp: TUnaryOperator read FUnOp write FUnOp;
  19.     property Operand: TExpressionNode read FOperand write FOperand;
  20.     property LiteralValue: String read FLiteralValue write FLiteralValue;
  21.     property VariableName: String read FVariableName write FVariableName;
  22.   published
  23.   end;

And it works. At least it compiles with no error and from a couple of simple tests, doesn't go boom.

Cheers,
Gus
« Last Edit: August 04, 2025, 03:01:54 am by Gustavo 'Gus' Carreno »

egsuh

  • Hero Member
  • *****
  • Posts: 1800
Re: Is this possible in Free Pascal, Delphi or is it AI hallucination ?
« Reply #1 on: July 31, 2025, 06:49:33 am »
My solution is OOP approach --- using polymorphism.

TExpressionNodeType enumerates all kind of node types.

Code: Pascal  [Select][+][-]
  1. type
  2.     TExpressionNode = class
  3.         LeftChild,
  4.         RichtChild : TExpressionNode;
  5.  
  6.         property NodeType : TExpressionNodeType;  
  7.         function Evaluate: TExpressionNode; virtual;
  8.     end;
  9.  
  10.      TVariable = class (TExpressionNode)
  11.          ...
  12.          function Evaluate: TExpressionNode; override;
  13.      end;
  14.  
  15.  
  16.     TOperand = class(TExpressionNode);
  17.           ....
  18.  
  19.          function Evaluate: TExpressionNode; override;
  20.      end;  

This requires a little bit imagination. You have to decide, e.g. whether to put LiteralValue as a TExpressionNode or define a descendant like

           TLiteralValue = class (TExpressionNode)

depending on how you will use them.

Thaddy

  • Hero Member
  • *****
  • Posts: 19269
  • Glad to be alive.
Re: Is this possible in Free Pascal, Delphi or is it AI hallucination ?
« Reply #2 on: July 31, 2025, 06:55:18 am »
The record can be a member of the class? I expect your original proposal will work as you expected.
Like so?:
Code: Pascal  [Select][+][-]
  1. {$ifdef fpc}{$mode delphi}{$endif}
  2. uses classes;
  3. type
  4.   TExpressionNodeType = (entBinary, entUnary, entLiteral, entVariable);
  5.  
  6.   TBinaryOperator = (boEqual, boNotEqual, boAnd, boOr, boLess, boLessEqual, boGreater, boGreaterEqual);
  7.   TUnaryOperator = (uoNot);
  8.   TLiteralValue = type ShortString;
  9.   TVariableName = type ShortString;
  10.  
  11.   IExpressionNode= Interface
  12.   ['{F0DE9818-97BF-4A57-8838-67F72F402707}']
  13.   // Add methodss you need;
  14.   // Interface handles memory management
  15.   // The expressions can become quite complex
  16.   end;
  17.  
  18.   TExpressionNode = class(TInterfacedObject, IExpressionNode)
  19.     NodeType:record
  20.       case TExpressionNodeType of
  21.         entBinary: (Op: TBinaryOperator; Left, Right: TExpressionNode);
  22.         entUnary:  (UnOp: TUnaryOperator;  Operand: TExpressionNode);
  23.         entLiteral: (LiteralValue: ShortString);
  24.         entVariable: (VariableName: ShortString);
  25.       end;
  26.    public
  27.       constructor Create(const operand:TBinaryOperator); overload;
  28.       constructor Create(const operand:TUnaryOperator); overload;
  29.       constructor Create(const operand:TLiteralValue);overload;
  30.       constructor Create(const operand:TVariableName);overload;
  31.       constructor Create(const operand:TBinaryOperator;const left,right:IExpressionNode); overload;
  32.       constructor Create(const operand:TUnaryOperator;const node:IExpressionNode); overload;
  33.    end;
  34.  
  35. { TExpressionNode }
  36.  
  37. constructor TExpressionNode.Create(const operand: TBinaryoperator);
  38. begin
  39.   Inherited create;
  40.   NodeType.OP := operand;
  41. end;
  42.  
  43. constructor TExpressionNode.Create(const operand: TUnaryoperator);
  44. begin
  45.   Inherited create;
  46.   NodeType.UnOP := operand;
  47. end;
  48.  
  49. constructor TExpressionNode.Create(const operand: TLiteralValue);
  50. begin
  51.   Inherited create;
  52.   Nodetype.LiteralValue := operand;
  53. end;
  54.  
  55. constructor TExpressionNode.Create(const operand: TVariableName);
  56. begin
  57.   Inherited create;
  58.   Nodetype.VariableName := operand;
  59. end;
  60.  
  61. constructor TExpressionNode.Create(const operand: TUnaryOperator;
  62.   const node: IExpressionNode);
  63. begin
  64.   create(operand);
  65.   // do something with node
  66. end;
  67.  
  68. constructor TExpressionNode.Create(const operand: TBinaryOperator; const left,
  69.   right: IExpressionNode);
  70. begin
  71.   create(operand);
  72.   // do something with left, right
  73. end;
  74.  
  75. var
  76.   EN:IExpressionNode; // The interface, not the class!
  77. begin
  78.   EN:= TExpressionNode.Create(boEqual);
  79. end.
I did not look at the other examples yet, but the above compiles.
« Last Edit: July 31, 2025, 09:13:27 am by Thaddy »
objects are fine constructs. You can even initialize them with constructors.

Thaddy

  • Hero Member
  • *****
  • Posts: 19269
  • Glad to be alive.
Re: Is this possible in Free Pascal, Delphi or is it AI hallucination ?
« Reply #3 on: July 31, 2025, 08:34:42 am »
Added constructors and interface.
« Last Edit: July 31, 2025, 08:56:59 am by Thaddy »
objects are fine constructs. You can even initialize them with constructors.

Thaddy

  • Hero Member
  • *****
  • Posts: 19269
  • Glad to be alive.
Re: Is this possible in Free Pascal, Delphi or is it AI hallucination ?
« Reply #4 on: July 31, 2025, 09:25:15 am »
Forgot to explain why I chose the interface approach.
Since expressions can become quite complex, I designed it such that the memory management is done through the interface.
You can then use code like this:
Code: Pascal  [Select][+][-]
  1. var
  2.   EN:IExpressionNode;
  3. begin
  4.  EN:= TExpressionNode.Create(uoNot,TExpressionNode.Create(boEqual) as IExpressionNode);
  5.  
No leaks.

I like your original idea a lot. I think I use it and finish it sometime.
I think I will extend it with operators, such that more complexity is hidden.
« Last Edit: July 31, 2025, 09:33:56 am by Thaddy »
objects are fine constructs. You can even initialize them with constructors.

PascalDragon

  • Hero Member
  • *****
  • Posts: 6398
  • Compiler Developer
Re: Is this possible in Free Pascal, Delphi or is it AI hallucination ?
« Reply #5 on: July 31, 2025, 09:23:51 pm »
So, my question is as follows: Is there a way to achieve this in Free Pascal, or is this a Delphi-ism, or is this just the AI hallucinating?

The provided solution as-is is a hallucination. For a proper solution see Thaddy's or egsuh's suggestions (I'd go with the polymorphism, the compiler for example does as well).

JdeHaan

  • Full Member
  • ***
  • Posts: 171
Re: Is this possible in Free Pascal, Delphi or is it AI hallucination ?
« Reply #6 on: July 31, 2025, 09:33:24 pm »
I once created a similar solution with interfaces:

Code: Pascal  [Select][+][-]
  1. program variantclassrec;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses //heaptrc,
  6.   {$IFDEF UNIX}
  7.   cthreads,
  8.   {$ENDIF}
  9.   Classes
  10.   { you can add units after this };
  11.  
  12. type
  13.  
  14.   IExpression = interface
  15.   end;
  16.  
  17.   TExpression = class(TInterfacedObject, IExpression)
  18.   public type
  19.     TExprKind = (ekNumber, ekAdd, ekMul);
  20.  
  21.   public
  22.     constructor Num(const aValue: Integer);
  23.     constructor Add(Op1, Op2: IExpression);
  24.     constructor Mul(Op1, Op2: IExpression);
  25.  
  26.   public
  27.     Value: record
  28.       case Kind: TExprKind of
  29.         ekNumber: (Number: Integer);
  30.         ekAdd,
  31.         ekMul:    (Op1, Op2: TExpression);
  32.       end;
  33.   end;
  34.  
  35.  
  36. { TExpression }
  37.  
  38. constructor TExpression.Num(const aValue: Integer);
  39. begin
  40.   Value.Kind := ekNumber;
  41.   Value.Number := aValue;
  42. end;
  43.  
  44. constructor TExpression.Add(Op1, Op2: IExpression);
  45. begin
  46.   Value.Kind := ekAdd;
  47.   Value.Op1 := Op1 as TExpression;
  48.   Value.Op2 := Op2 as TExpression;
  49. end;
  50.  
  51. constructor TExpression.Mul(Op1, Op2: IExpression);
  52. begin
  53.   Value.Kind := ekMul;
  54.   Value.Op1 := Op1 as TExpression;
  55.   Value.Op2 := Op2 as TExpression;
  56. end;
  57.  
  58. function Evaluate(Expr: IExpression): Integer;
  59. begin
  60.   with (Expr as TExpression).Value do
  61.   case Kind of
  62.     ekNumber: Result := Number;
  63.     ekAdd: Result := Evaluate(Op1) + Evaluate(Op2);
  64.     ekMul: Result := Evaluate(Op1) * Evaluate(Op2);
  65.   end;
  66. end;
  67.  
  68. var
  69.   four, five, two: IExpression;
  70.   sum, product: IExpression;
  71.  
  72. begin
  73.   four := TExpression.Num(4);
  74.   five := TExpression.Num(5);
  75.  
  76.   sum := TExpression.Add(four, five);
  77.  
  78.   two := TExpression.Num(2);
  79.   product := TExpression.Mul(sum, two);
  80.  
  81.   WriteLn(Evaluate(product));
  82. end.
  83.  

Warfley

  • Hero Member
  • *****
  • Posts: 2066
Re: Is this possible in Free Pascal, Delphi or is it AI hallucination ?
« Reply #7 on: July 31, 2025, 11:12:44 pm »
See the example in here: https://forum.lazarus.freepascal.org/index.php/topic,68839.15.html

Its not quite possible yet but I made a merge request for the FPC which allows exactly that.

It should also be noted that this is exactly the kind of use case which extended pascal had on mind when extending on variant records. Extended pascal exactly defines the lifetimes of the branches through the selector

Edit: my bad, after getting some sleep I saw you are using a class, not a record. I thought you were asking about being able to use string in the variant part.

For classes the main polymorphic mechanism is inheritance, while for records it's variant parts. So short answer, no it doesn't work like that, but if you want something like that for locality of functionality (classes spread their polymorphism over multiple virtual functions, whereas with records you'll end up with a single case statement), use a record
« Last Edit: August 01, 2025, 09:14:32 am by Warfley »

Gustavo 'Gus' Carreno

  • Hero Member
  • *****
  • Posts: 1353
  • Professional amateur ;-P
Re: Is this possible in Free Pascal, Delphi or is it AI hallucination ?
« Reply #8 on: August 04, 2025, 03:01:32 am »
Hey Y'all,

Took me a while to decide to watch this: The Big OOPS due to the fact that it's 2 and 1/2 hours long.

Now that I did, there is mention of this being a possible thing implemented in like the 60s or even the 50s in something called SketchPad.

In the video, this stuff takes about 1% of it.

So, yeah, maybe a sideways note on this, but I think we should watch that video. We are prone to be OOP beats, some of us, and we should know about the path not taken in terms of the things we now crave but were removed by someone in a very early stage. And the video is pack full of the background and the decisions that make our current programming context.

This video is also something all programming language creators/maintainers should watch. It does give these peeps a bit more ideas on how to correct some of the decisions that have us kinda stuck right now. Just my humble opinion!!

Cheers,
Gus

Gustavo 'Gus' Carreno

  • Hero Member
  • *****
  • Posts: 1353
  • Professional amateur ;-P
Hey Y'all,

BTW, I completely forgot my manners and did not thank anyone for the AWESOME solutions you peeps provided!!
That's on me, and I profusely apologise for this faux pas!!!

To the peeps that gave me solutions, I'm ever so thankful !!!

Side note:
I've discovered that we have something called TFPExpressionParser, in our included batteries.
If I can solve my expression needs with that, then the AI provided expression parser is completely superfluous!!!
I need to do some testing, and if it ticks all my boxes, I'll replace my AI suggested one with TFPExpressionParser.
I'm hoping that it will solve the fact that the AI one is not able to understand array variables. And that is something I definitely want to be provided.

Cheers,
Gus

Gustavo 'Gus' Carreno

  • Hero Member
  • *****
  • Posts: 1353
  • Professional amateur ;-P
Hey Y'All,

I've recently had two events that made me have a better grasp at tokenizing, parsing tokens and OOP history:
  • In a session with ChatGPT, I finally broke my bad mental construct about tokenizing and parsing tokens: They should be 2 different steps. My previous, bad, notion was that they were one and the same and that gave me quite the paralises when attempting stuff as simple as doing a DSL for templates
  • I watched the talk from Casey Muratori called The Big OOPS

Especially the "Big OOPS" video was the thing that gave me some background to now say with confidence:
  • Classes do not have case, they have virtual functions
  • It is Records that have the case

And I now know where all that came from!!

Like I mentioned in my first message on this thread, I did try to replace class with record, cuz even before the above two events, I knew that the case only made sense in a record.
But then I was too lazy to suss out how to resolve the circular reference that the AI hallucinated, making the record fold on itself.

I'll leave solving that problem for a future exercise.

Just wanted to mention the above, and give others the pleasure of learning some history behind the entire process that gave birth to OOP as we now know it.

Cheers,
Gus

 

TinyPortal © 2005-2018