Howto series: complex numbers

This is intended to be a small series on how to implement
"real things", thus introducing several aspects of Pliant,
which are not all obvious. Today: complex numbers
Message posted by pom on 2002/06/12 19:04:13

# How to implement complex numbers?

This piece of code has been initialy written for PLEAC Site. It deserves some explanations, and is quite interesting by itself (I think).

First we have to define the data type: a complex number is composed of two floating point numbers, namely its real part and its imaginary part.

```type Complex
field Float real imaginary
```

Complex numbers are extensions of floating point numbers. A complex number is a floating point number if its imaginary part is 0:

```function 'cast Complex' f -> z      # extension cast Float -> Complex
arg Float f; arg Complex z
extension
z real := f
z imaginary := 0

function 'cast Float' z -> f        # reduction cast Complex -> Float
arg Float f; arg Complex z
reduction
if z:imaginary<>0
error error_id_arithmetic "The value is not a real number"
else
f := z real
```
Message posted by pom on 2002/06/12 19:17:00
Now, we would like to be able to write things like var Complex c := 1+5i. Thatfor, we need to be able to read the imaginary part (here 5i), the addition we will define on complex later on plus the implicit extension cast from Float to Complex will then do the full job. We choose not to allow to simply write i for a complex number with imaginary part equal to 1, as it would really create problems with a possible idenfier i. We will ensure a notation like 1i.

We begin by writing a function to parse something like datai, where data is a floating point number:

```function parse_imaginary context line parameter
arg_rw ParserContext context ; arg Str line ; arg Address parameter
if not (line parse (var Float data) word:"i" offset:(var Int offset))
return
var Link:Complex z :> new Complex
z real := 0
z imaginary := data
context forward offset
```
Here, line is the string which might begin with an imaginary part, parameter is of no use for us, and context is a structure which gives access to the parser context.

If line does not parse as a Float followed by a "word" equal to string "i", we do nothing (nothing recognized). Otherwise, we create a new Complex object and add it to the context as a new token. Remark the use of offset:(var Int offset) to mark the ending position of the recognized substring, which enables us to give the offset for the next "token" (i.e. syntactic atom) to be recognized by the parser.

Now, we shall tell Pliant to use this new parser function in addition to all of these it already knows:

```gvar ParserFilter img_filter
img_filter function :> the_function parse_imaginary ParserContext Str Address
constant 'pliant parser basic types' img_filter
export 'pliant parser basic types'
```

Here, img_filter will "point" to the filter function, and is added to the global dictionary internal list "pliant parser basic types", by simply executing the "constant" command. The parser filter list so modified is exported for use by other modules linking to the one we presently write.

Message posted by pom on 2002/06/12 19:19:03
Now, basic operations:

```function '+' z1 z2 -> z
arg Complex z1 z2 z
z real := z1:real+z2:real
z imaginary := z1:imaginary+z2:imaginary

function '-' z1 z2 -> z
arg Complex z1 z2 z
z real := z1:real-z2:real
z imaginary := z1:imaginary-z2:imaginary

function '*' z1 z2 -> z
arg Complex z1 z2 z
z real := z1:real*z2:real-z1:imaginary*z2:imaginary
z imaginary := z1:real*z2:imaginary+z2:real*z1:imaginary
```

And also a function to convert a complex number to a string (generic method used by console, for instance)

```method z 'to string' options -> s
arg Complex z ; arg Str options ; arg Str s
if z:imaginary=0
return (string z:real)
eif z:real=0
return (string z:imaginary)+"i"
return (string z:real)+"+"+(string z:imaginary)+"i"
```
Message posted by pom on 2002/06/12 19:22:26

Now, several small things should be added at the top of our file:

```public
```
This means that the declaration will be accessible from any other module. We might also alternatively choose to only export the functions and methods we choose, using export.

Moreover, as we use the_function meta, we shall link to the compiler module. As we modify the parser, we shall link to the parser module:

```module "/pliant/language/compiler.pli"
module "/pliant/language/parser.pli"
```
Message posted by pom on 2002/06/12 19:24:31
Now, if our file is "/pom.ehess.fr/sample/complex.pli", we may use it in a Pliant program file as follows:
```module "/pom.ehess.fr/sample/complex.pli"

gvar Complex a := 3+5i
gvar Complex b := 2-2i
gvar Complex c := a*b
console "c = " c eol
```
Which will write (hopefully)
```c = 16+4i
```
Message posted by maybe Hubert Tonneau on 2002/06/12 19:53:35
On the technical side, in the 'parse_imaginary', you should change to:
if not (line eparse (var Float data) word:"i" offset:(var Int offset) any)

The 'eparse' instead of 'parse' will prevent to recognize something like '5 i'
and the 'any' at the end will recognize the token even if it's not at the end
of the line.

Now it seems that this sample is a very good one for a Pliant book.

Message posted by marcus on 2002/06/12 20:46:59
Awesome!

I have a suggestion, not technical, but didactical: since you are actually
using the complex numbers case study to illustrate the more subtle and important
notion of how to extent Pliant's syntax and semantics, a more appropriate title
would be "How to extend Pliant: a case study using complex numbers"

Patrice, it would be nice to have this documented as part of the Pliant HowTos.
Would you be willing to send me the .page file(s) of these examples for revision,
so that I later post them at the PDI site?
Message posted by pom on 2002/06/13 09:50:38
fixed and posted in the Patch forum
Message posted by maybe David Ge on 2002/06/20 17:04:25
Hi, pom:

A question confused me. In the complex.pli, the imaginary part was parsed by parse_imaginary function.
Then, when and how the real was parsed and assigned?
Message posted by maybe pom on 2002/06/20 17:33:58
When you write something like:
var Complex z := 23+45i

The parser will build the following expression:

(:= (var Complex z) (+ 23 45i))

where 23 stands for the constant integer "23" and "45i" for the constant complex
number 45i parsed by parse_imaginary.

Then, Pliant will consider that the addition may be performed at compile time,
because it only concerns constants. It will do the following:

uInt 23 -> Float 23 -> Complex 23
Then call the function '+' Complex Complex -> Complex
on 23 and 45i, what will result in Complex constant 23+45i
Message posted by maybe David Ge on 2002/06/21 18:45:33
Oh, I c now.

It seems that the key point is the way how the parser build the expression. And
The parse_imaginary function in your example is used to assign the imaginary part of the complex.

What now confuse me is the way to affect the parser to build expression? For example,
if I want to create a new type such as a 2D coordinate: (10.2, -20.3) ,
how can I let the parser create the following expression as I hope:
(:= (var 2DCoordinate z) (, 10.2 -20.3))

And can I know the content of "line" in the parse_imaginary function? It will
let me know what will be parsed in the function.

Many thanks for your help.

David

Message posted by maybe pom on 2002/06/22 10:32:53
Actually, there are roughly two cases:

- Either (what you may always do) you write a parser filter to recognize the
whole notation. In your case, it would be something like
if (line parse "(" (var Float x) "," (var Float y) ")")
# create a token with x and y

- Or you rely on some operator to create your data type. For instance,

function ',' x y -> pt
arg Float x y; arg Point pt
# ...

operator ',' 180h 1 1  # define a very low priority operator ','
# from function ','

This way, the operator ',' may now be used to compose a Point from to Float
values. Here, you need no parser filter at all (but the one defined by
"operator"),  except if you want to
introduce some notation from special values (like imaginary numbers in the
Complex sample).
Message posted by maybe pom on 2002/06/22 10:43:51
When you parse a file, and the parser last recognized, for instance,
some sequence ending at the 31th character of the 24th line, then line
will be all the remaining characters of the 24th line (starting from the 32th)
if any, or the whole 25th line if no character remains of the 24th.

For instance, if a line to be parsed is "var x:=3+4", the parameter 'line'
will have successive values:

line = "var x:=3+4"          # nothing parsed yet, var will be recognized
line = " x:=3+4"             # space will be recognized
line = "x:=3+4"              # x will be recognized
line = ":=3+4"               # := will be reognized
line = "3+4"                 # 3 will be recognized
line = "+4"                  # + will be recognized
line = "4"                   # 4 will be recognized
line = ... (next line)

line is sent to all the parser filters (in some order precised by the sections
in which the filters have been placed) till one recognizes something.
As soon as a token is recognized, the "cursor" moves accordingly to the 'offset'
value set by the filter.

Message posted by davidge on 2002/06/29 01:01:25
Hi, Pom, many thanks.

For your first suggestion,

*- Either (what you may always do) you write a parser filter to recognize the
*whole notation. In your case, it would be something like
*  if (line parse "(" (var Float x) "," (var Float y) ")")
*     # create a token with x and y

actually I have tried. I created a parser function to parse a new 2D type (x,y):

module "/pliant/language/compiler.pli"
module "/pliant/language/parser.pli"

type Coord
field Float x y

function parse_coord context line parameter
arg_rw ParserContext context ; arg Str line ; arg Address parameter
if not (line eparse "(" (var Float datax) "," (var Float datay) ")" offset:(var Int offset) any)
return
var Link:Coord z :> new Coord
z x := datax
z y := datay
context forward offset

gvar ParserFilter img_filter
img_filter function :> the_function parse_coord ParserContext Str Address
constant 'pliant parser basic types' img_filter
export 'pliant parser basic types'

gvar ParserFilter parse_coord

But when I create a Coord type data as var Coord a := (12.0,-12.2) in a  .page file, the pliant system report a parser error message: can not recognize the token ,12.2) .

So it seems that the pliatn system have already parse the (12.0 into an expression in stead of the created parse_coord function and leave the ,-12.0 in the line parameter. How can we let the parse_coord to work when parsing the  (12.0,-12.2)?

David

Message posted by maybe pom on 2002/07/01 07:48:55
The reason why this does not work is that the parser actually works with sections
handling the priorities between filters, and that 'pliant parser basic types'
is the less prioritoray section.
So, the filter which recognizes "(" (this is parser_filter_openop which is
in section 'pliant parser 1 char operators') is checked before your filter, and
it will actually recognize "(". The parser does no backtracking, so your
filter will never recognize anything.

Thus, you have to put your filter in a more prioritary section. As it works
like a kind of operator, you may put it, for instance, in
'pliant parser several chars operators' section.

To look at the list of the filters and sections of the parser, you may look
at http://pr.cams.ehess.fr/parser/intro11.html

Last, you may also insert a new section, by inserting it in 'pliant parser sections'
untyped list, but it is a more tricky.
Message posted by maybe pom on 2002/07/01 07:52:06
So, you should create a .pli with something like:

######################## coord.pli ########################
module "/pliant/language/compiler.pli"
module "/pliant/language/parser.pli"

type Coord
field Float x y
export Coord # exports the type for further use by a .page

function parse_coord context line parameter
arg_rw ParserContext context ; arg Str line ; arg Address parameter
if not (line eparse "(" (var Float datax) "," (var Float datay) ")" offset:(var Int offset) any)
return
var Link:Coord z :> new Coord
z x := datax
z y := datay
context forward offset

gvar ParserFilter my_filter
my_filter function :> the_function parse_coord ParserContext Str Address
constant 'pliant parser several chars operators' my_filter
export 'pliant parser several chars operators'

##############################################

And link to it in your .page:

########################## mypage.page ################

module "/mydir/subdir/coord.pli" # pliant path to .pli
#...
Message posted by maybe pom on 2002/07/01 07:52:36
So, you should create a .pli with something like:

######################## coord.pli ########################
module "/pliant/language/compiler.pli"
module "/pliant/language/parser.pli"

type Coord
field Float x y
export Coord # exports the type for further use by a .page

function parse_coord context line parameter
arg_rw ParserContext context ; arg Str line ; arg Address parameter
if not (line eparse "(" (var Float datax) "," (var Float datay) ")" offset:(var Int offset) any)
return
var Link:Coord z :> new Coord
z x := datax
z y := datay
context forward offset

gvar ParserFilter my_filter
my_filter function :> the_function parse_coord ParserContext Str Address
constant 'pliant parser several chars operators' my_filter
export 'pliant parser several chars operators'

##############################################

And link to it in your .page:

########################## mypage.page ################

module "/mydir/subdir/coord.pli" # pliant path to .pli
#...
Message posted by maybe pom on 2002/07/01 07:59:35
Actually, this solution is not so good, as it breaks a bit the semantic of "(".
The same may be done in a simplest way, also allowing notation like
gvar Coord z := 2.3,-1.2

How to do it? create a "," operator.

##################################################
module "/pliant/language/compiler.pli"
module "/pliant/language/parser.pli"

type Coord
field Float x y
export Coord

function ',' x y -> z # operator makes a Coord from two Floats
arg Float x y; arg Coord z
z x := x
z y := y

operator ',' 180h 1 1 # low priority, takes one arg before it, and one after.
Message posted by maybe David Ge on 2002/07/03 00:44:59
Yes, this solution is much easier.